├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── add_user.sh ├── app.js ├── cypress.json ├── cypress ├── fixtures │ ├── example.json │ ├── profile.json │ └── users.json ├── integration │ ├── chat_spec.js │ ├── example_spec.js │ └── friendlist_spec.js ├── plugins │ └── index.js └── support │ ├── commands.js │ └── index.js ├── index.js ├── package-lock.json ├── package.json ├── public ├── images │ ├── avatar_placeholder.png │ ├── avatars │ │ ├── 018817e0-0fdf-11e8-b851-978e6f4d47cd-1518431111263.jpeg │ │ ├── 09ffe820-10f8-11e8-ab13-776dd62a40f7-1518551814050.png │ │ ├── 0eeacdf0-1099-11e8-920f-9bfc5d0a82a5-1518511020111.png │ │ ├── 122f4d40-11db-11e8-9b2a-ede91997dfc5-1518649323542.png │ │ ├── 1933c4d0-0f7a-11e8-ac79-9d50ad2ac2fe-1518387771805.png │ │ ├── 24739040-10f7-11e8-ab13-776dd62a40f7-1518551428932.png │ │ ├── 28ec4d80-10ff-11e8-bf22-fbaa2666b36b-1518554872409.png │ │ ├── 31a65d80-1181-11e8-a61a-855227501956-1518610721625.png │ │ ├── 324b0ec0-10cd-11e8-83db-7daa3da16bdd-1518533413293.png │ │ ├── 35f71410-10ff-11e8-bf22-fbaa2666b36b-1518554894289.png │ │ ├── 394f2890-11d7-11e8-94f5-e52160c67fe6-1518647671193.png │ │ ├── 4d27a9a0-0f52-11e8-bc13-7f7d8d72e683-1518370679098.jpeg │ │ ├── 541b3470-11d7-11e8-b40f-93249d525b4e-1518647716152.png │ │ ├── 5a7c19dc0e5bc8265486a549.jpeg │ │ ├── 5a7c3582d8e324513e560f61.jpeg │ │ ├── 5a7c3878d8e324513e560f64.jpeg │ │ ├── 5a7c390fd8e324513e560f66.jpeg │ │ ├── 5a7c401ed8e324513e560f68.jpeg │ │ ├── 5c50a2a0-117e-11e8-8751-c53ac2f1f265-1518609504714.png │ │ ├── 5f3cd970-10f7-11e8-a62e-cbfa99f8b88d-1518551527559.png │ │ ├── 70e37680-100b-11e8-a5ac-79b32154e784-1518450195945.png │ │ ├── 71068860-118b-11e8-ad2c-a3677d6a5da0-1518615122918.png │ │ ├── 7932fef0-100b-11e8-a5ac-79b32154e784-1518450209887.jpeg │ │ ├── 8150a1f0-100b-11e8-a5ac-79b32154e784-1518450223503.jpeg │ │ ├── 82f02040-10ff-11e8-92ab-a96c9fca111b-1518555023428.png │ │ ├── 831f3dc0-1187-11e8-9bbb-23efb34ceeb9-1518613435292.png │ │ ├── 86f0c4b0-1172-11e8-9fc1-df184eb3e1d0-1518604422268.png │ │ ├── 8742b230-0fe6-11e8-83b7-1f2f387546fb-1518434342099.jpeg │ │ ├── 8b2e26a0-117a-11e8-957b-a905a1e777ea-1518607865354.png │ │ ├── 8ca4b780-116e-11e8-a1db-49f49c42a420-1518602713848.png │ │ ├── 925382a0-1098-11e8-920f-9bfc5d0a82a5-1518510811082.jpeg │ │ ├── 934ed970-11d3-11e8-8575-5d05c91c6f97-1518646104199.png │ │ ├── 96c810d0-1106-11e8-b8b9-ab0bf403fbd1-1518558063197.png │ │ ├── a0848090-11d3-11e8-8575-5d05c91c6f97-1518646126362.png │ │ ├── a130f8d0-1105-11e8-b3e6-b34192e721fb-1518557651165.png │ │ ├── a5045790-100b-11e8-a5ac-79b32154e784-1518450283401.jpeg │ │ ├── avatar.jpg │ │ ├── avatar.png │ │ ├── b82e42a0-1177-11e8-808f-096791b2f992-1518606652362.png │ │ ├── bc46fa60-11d3-11e8-8575-5d05c91c6f97-1518646172934.png │ │ ├── ca8a7360-0f33-11e8-8abd-cd457cbeefcd-1518357575062-26195817_2028128537214124_8149219415562015760_n.jpg │ │ ├── cb019140-117a-11e8-8e6c-7373f06f28da-1518607972436.png │ │ ├── ce036220-11d3-11e8-8575-5d05c91c6f97-1518646202690.png │ │ ├── d033dbf0-10fd-11e8-b176-579eb3197efc-1518554294063.png │ │ ├── d4889c60-1187-11e8-9bbb-23efb34ceeb9-1518613571878.png │ │ ├── d7090380-1178-11e8-92c3-d7a5e0353d9c-1518607133625.png │ │ ├── d78c58a0-1099-11e8-920f-9bfc5d0a82a5-1518511356714.jpeg │ │ ├── d78fdd40-10a8-11e8-aad4-0b62eb8ffde8-1518517799188.jpeg │ │ ├── e1674b20-10dd-11e8-a826-1f2a2d53c76b-1518540579026.png │ │ ├── f30c3590-11d5-11e8-b616-95e4ae195525-1518647123817.png │ │ └── index.html │ ├── favicon │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── mstile-150x150.png │ │ ├── safari-pinned-tab.svg │ │ └── site.webmanifest │ ├── index.html │ └── who_logo.png └── index.html ├── server ├── auth │ ├── facebook.js │ ├── github.js │ ├── google.js │ ├── index.js │ ├── local.js │ ├── logout.js │ ├── signin.js │ ├── signup.js │ └── twitter.js ├── config │ └── config.js ├── handlers │ ├── contact.js │ ├── friend.js │ ├── message.js │ ├── profile.js │ └── user.js ├── middleware │ ├── authenticated.js │ └── file-upload.js ├── mock-data.js ├── models │ ├── chat.model.js │ ├── message.model.js │ └── user.model.js └── routers │ ├── auth-routers.js │ └── user-routers.js ├── src ├── App.css ├── App.js ├── App.test.js ├── Modal.css ├── actions │ ├── changeSetting.js │ ├── filterAction.js │ ├── login.js │ ├── setCurrentFriendAction.js │ └── userActions.js ├── components │ ├── Addcontact.js │ ├── ImageCropper.js │ ├── Modal.js │ ├── ProfileSettings.js │ ├── SingIn.js │ ├── SingUp.js │ ├── SocialMedia.js │ ├── UserAvatar.js │ ├── UserData.js │ ├── UserPictureAndState.js │ ├── WellcomePage.js │ ├── addFriendConfirmation.js │ ├── contactDetail.js │ ├── contactList.js │ ├── dialog.js │ ├── emoji.js │ ├── mainPage.css │ ├── mainPage.jpg │ ├── mainPage.js │ ├── mainPage1.jpg │ ├── messageBaloun.js │ ├── messagesLog.js │ ├── newMessage.js │ ├── searchList.js │ ├── skypeAvatar.js │ ├── test.js │ ├── userDataSetting.js │ ├── vedioCall.js │ ├── wellcome.css │ └── wellcomePage.jpg ├── config │ └── config.js ├── container │ ├── AddFriends.js │ └── FriendsSearchBar.js ├── images │ ├── avatar-penguin.png │ ├── bear.png │ ├── cat.png │ ├── fox.svg │ └── rabbit.png ├── index.css ├── index.js ├── reducers │ ├── changeSettingReducer.js │ ├── contactListFilterReducer.js │ ├── contactListReducers.js │ ├── index.js │ └── setCurrentFriendReducer.js ├── registerServiceWorker.js ├── store.js ├── style.css └── utiles │ └── Api.js └── temp └── data.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (http://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Typescript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | *.sw? 64 | 65 | cypress/screenshots 66 | cypress/videos 67 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "iojs" 4 | - "7" 5 | script: 6 | - cypress run --record 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Aemal Sayer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SkypeClone 2 | Building SkypeClone using Material-UI, React.js, Express.js, Node.js, MongoDB & WebRTC 3 | 4 | ## Installation 5 | 6 | ``` 7 | git clone https://github.com/aemal/SkypeClone.git 8 | cd skypeClone 9 | npm install 10 | ``` 11 | ## Running the app 12 | ### Run client & server side 13 | ``` 14 | npm start 15 | ``` 16 | 17 | ### Run tests 18 | open test console `./node_modules/.bin/cypress open` 19 | 20 | run `dashboard_spec.js` 21 | 22 | ### Demos 23 | [Demo 1](https://youtu.be/KweSZ6xMDNc) 24 | [Demo 2](https://youtu.be/6ULYoaBvBqA) 25 | [Demo 3](https://youtu.be/QPCOMQGg858) 26 | 27 | ### Misc Links 28 | [Skype Clone Heroku App Link](https://skypeclone.herokuapp.com) 29 | 30 | [Specifications Video](https://www.youtube.com/watch?v=veXSDyUSEhU&t=120s) 31 | 32 | [Specifications Text](https://docs.google.com/document/d/1OKEbxG-_T5YPyowL-Aj-ObFq34ijqBRq7_JPOJXZqkA/edit?usp=sharing) 33 | 34 | [Balsamiq Wireframes](https://balsamiq.cloud/ssflf/p94t/r3C88) 35 | -------------------------------------------------------------------------------- /add_user.sh: -------------------------------------------------------------------------------- 1 | curl http://localhost:8080/user/register -X POST --data "firstName=Mike&lastName=Real&emailAddress=koudelka.michal@gmail.com&password=abcdefgh&dateOfBirth=456012000000" 2 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require("express"); 4 | const bodyParser = require("body-parser"); 5 | const mongo = require("mongodb"); 6 | const mongoose = require("mongoose"); 7 | const passport = require("passport"); 8 | const cookieParser = require("cookie-parser"); 9 | const cors = require("cors"); 10 | 11 | const app = express(); 12 | 13 | const router = express.Router(); 14 | 15 | const mockData = require("./server/mock-data"); 16 | const config = require("./server/config/config"); 17 | const SerialAuthenticator = require("./server/auth/index"); 18 | const User = require("./server/models/user.model"); 19 | const Message = require("./server/models/message.model"); 20 | 21 | const authRoutes = require("./server/routers/auth-routers")(passport); 22 | const userRoutes = require("./server/routers/user-routers")(); 23 | 24 | 25 | 26 | const db = "mongodb://test:test@ds119988.mlab.com:19988/skypeclone"; 27 | 28 | //const db = config.DB_Connection.URL; 29 | const port = process.env.PORT || 3001; 30 | 31 | mongoose.Promise = global.Promise; 32 | mongoose.connection.openUri(db); 33 | 34 | app.use(express.static('public')) 35 | 36 | //Enable all CORS requests 37 | app.use(cors()); 38 | 39 | app.use(router); 40 | app.use("/auth", authRoutes); // login/out authentication routes 41 | app.use("/user", userRoutes); // user authentication 42 | 43 | router.use(cookieParser()); 44 | router.use(bodyParser.json()); 45 | router.use(bodyParser.urlencoded({ extended: true })); 46 | router.use( 47 | require("express-session")({ 48 | secret: config.SESSION_SECRET.SECRET_KEY, 49 | resave: true, 50 | saveUninitialized: true 51 | }) 52 | ); 53 | 54 | router.use(passport.initialize()); 55 | router.use(passport.session()); 56 | 57 | // passport de/serialize and local strategy 58 | SerialAuthenticator(passport); 59 | 60 | router.get("/", (req, res, next) => res.send("Home")); 61 | 62 | // ErrorHandler, pass errors to the next function 63 | app.use((err, req, res, next) => { 64 | res.send(err); 65 | next(); 66 | }); 67 | 68 | const server = app.listen(port, () => { 69 | console.log("Server started on port....." + port); 70 | }); 71 | 72 | 73 | const io = require('socket.io').listen(server); 74 | 75 | io.sockets.on('connection', socket => { 76 | /*const sessionid = socket.id; 77 | console.log("Socket Connected: %s", sessionid); 78 | socket.on('message', body => { 79 | socket.broadcast.emit('message', { 80 | body, 81 | id: sessionid, 82 | }) 83 | }) 84 | 85 | socket.on('joinRoom', (roomInfo)=>{ 86 | console.log('joining room', roomInfo); 87 | //Save into chatmodel. 88 | socket.join(roomInfo.roomID); 89 | }); 90 | */ 91 | 92 | socket.on('joinRoom', (chatID) => { 93 | console.log('joining room', chatID); 94 | //Save into chatmodel. 95 | socket.join(chatID); 96 | }); 97 | 98 | socket.on('privateMessage', function (data) { 99 | console.log('chatMessages', data); 100 | 101 | Message.create({ 102 | chatID: data.chatID, 103 | userID: data.userID, 104 | message: data.messageBody 105 | }) 106 | .then(data => console.log(data)) 107 | .catch(err => console.log(err)); 108 | 109 | socket.broadcast.to(data.chatID).emit('chatMessages', data); 110 | }); 111 | 112 | }); 113 | 114 | 115 | console.log(`SkypeClone's Socket server is running at port 8080... 116 | Wait! for a while to start client side server.`); -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /cypress/fixtures/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 8739, 3 | "name": "Jane", 4 | "email": "jane@example.com" 5 | } -------------------------------------------------------------------------------- /cypress/fixtures/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Leanne Graham", 5 | "username": "Bret", 6 | "email": "Sincere@april.biz", 7 | "address": { 8 | "street": "Kulas Light", 9 | "suite": "Apt. 556", 10 | "city": "Gwenborough", 11 | "zipcode": "92998-3874", 12 | "geo": { 13 | "lat": "-37.3159", 14 | "lng": "81.1496" 15 | } 16 | }, 17 | "phone": "1-770-736-8031 x56442", 18 | "website": "hildegard.org", 19 | "company": { 20 | "name": "Romaguera-Crona", 21 | "catchPhrase": "Multi-layered client-server neural-net", 22 | "bs": "harness real-time e-markets" 23 | } 24 | }, 25 | { 26 | "id": 2, 27 | "name": "Ervin Howell", 28 | "username": "Antonette", 29 | "email": "Shanna@melissa.tv", 30 | "address": { 31 | "street": "Victor Plains", 32 | "suite": "Suite 879", 33 | "city": "Wisokyburgh", 34 | "zipcode": "90566-7771", 35 | "geo": { 36 | "lat": "-43.9509", 37 | "lng": "-34.4618" 38 | } 39 | }, 40 | "phone": "010-692-6593 x09125", 41 | "website": "anastasia.net", 42 | "company": { 43 | "name": "Deckow-Crist", 44 | "catchPhrase": "Proactive didactic contingency", 45 | "bs": "synergize scalable supply-chains" 46 | } 47 | }, 48 | { 49 | "id": 3, 50 | "name": "Clementine Bauch", 51 | "username": "Samantha", 52 | "email": "Nathan@yesenia.net", 53 | "address": { 54 | "street": "Douglas Extension", 55 | "suite": "Suite 847", 56 | "city": "McKenziehaven", 57 | "zipcode": "59590-4157", 58 | "geo": { 59 | "lat": "-68.6102", 60 | "lng": "-47.0653" 61 | } 62 | }, 63 | "phone": "1-463-123-4447", 64 | "website": "ramiro.info", 65 | "company": { 66 | "name": "Romaguera-Jacobson", 67 | "catchPhrase": "Face to face bifurcated interface", 68 | "bs": "e-enable strategic applications" 69 | } 70 | }, 71 | { 72 | "id": 4, 73 | "name": "Patricia Lebsack", 74 | "username": "Karianne", 75 | "email": "Julianne.OConner@kory.org", 76 | "address": { 77 | "street": "Hoeger Mall", 78 | "suite": "Apt. 692", 79 | "city": "South Elvis", 80 | "zipcode": "53919-4257", 81 | "geo": { 82 | "lat": "29.4572", 83 | "lng": "-164.2990" 84 | } 85 | }, 86 | "phone": "493-170-9623 x156", 87 | "website": "kale.biz", 88 | "company": { 89 | "name": "Robel-Corkery", 90 | "catchPhrase": "Multi-tiered zero tolerance productivity", 91 | "bs": "transition cutting-edge web services" 92 | } 93 | }, 94 | { 95 | "id": 5, 96 | "name": "Chelsey Dietrich", 97 | "username": "Kamren", 98 | "email": "Lucio_Hettinger@annie.ca", 99 | "address": { 100 | "street": "Skiles Walks", 101 | "suite": "Suite 351", 102 | "city": "Roscoeview", 103 | "zipcode": "33263", 104 | "geo": { 105 | "lat": "-31.8129", 106 | "lng": "62.5342" 107 | } 108 | }, 109 | "phone": "(254)954-1289", 110 | "website": "demarco.info", 111 | "company": { 112 | "name": "Keebler LLC", 113 | "catchPhrase": "User-centric fault-tolerant solution", 114 | "bs": "revolutionize end-to-end systems" 115 | } 116 | }, 117 | { 118 | "id": 6, 119 | "name": "Mrs. Dennis Schulist", 120 | "username": "Leopoldo_Corkery", 121 | "email": "Karley_Dach@jasper.info", 122 | "address": { 123 | "street": "Norberto Crossing", 124 | "suite": "Apt. 950", 125 | "city": "South Christy", 126 | "zipcode": "23505-1337", 127 | "geo": { 128 | "lat": "-71.4197", 129 | "lng": "71.7478" 130 | } 131 | }, 132 | "phone": "1-477-935-8478 x6430", 133 | "website": "ola.org", 134 | "company": { 135 | "name": "Considine-Lockman", 136 | "catchPhrase": "Synchronised bottom-line interface", 137 | "bs": "e-enable innovative applications" 138 | } 139 | }, 140 | { 141 | "id": 7, 142 | "name": "Kurtis Weissnat", 143 | "username": "Elwyn.Skiles", 144 | "email": "Telly.Hoeger@billy.biz", 145 | "address": { 146 | "street": "Rex Trail", 147 | "suite": "Suite 280", 148 | "city": "Howemouth", 149 | "zipcode": "58804-1099", 150 | "geo": { 151 | "lat": "24.8918", 152 | "lng": "21.8984" 153 | } 154 | }, 155 | "phone": "210.067.6132", 156 | "website": "elvis.io", 157 | "company": { 158 | "name": "Johns Group", 159 | "catchPhrase": "Configurable multimedia task-force", 160 | "bs": "generate enterprise e-tailers" 161 | } 162 | }, 163 | { 164 | "id": 8, 165 | "name": "Nicholas Runolfsdottir V", 166 | "username": "Maxime_Nienow", 167 | "email": "Sherwood@rosamond.me", 168 | "address": { 169 | "street": "Ellsworth Summit", 170 | "suite": "Suite 729", 171 | "city": "Aliyaview", 172 | "zipcode": "45169", 173 | "geo": { 174 | "lat": "-14.3990", 175 | "lng": "-120.7677" 176 | } 177 | }, 178 | "phone": "586.493.6943 x140", 179 | "website": "jacynthe.com", 180 | "company": { 181 | "name": "Abernathy Group", 182 | "catchPhrase": "Implemented secondary concept", 183 | "bs": "e-enable extensible e-tailers" 184 | } 185 | }, 186 | { 187 | "id": 9, 188 | "name": "Glenna Reichert", 189 | "username": "Delphine", 190 | "email": "Chaim_McDermott@dana.io", 191 | "address": { 192 | "street": "Dayna Park", 193 | "suite": "Suite 449", 194 | "city": "Bartholomebury", 195 | "zipcode": "76495-3109", 196 | "geo": { 197 | "lat": "24.6463", 198 | "lng": "-168.8889" 199 | } 200 | }, 201 | "phone": "(775)976-6794 x41206", 202 | "website": "conrad.com", 203 | "company": { 204 | "name": "Yost and Sons", 205 | "catchPhrase": "Switchable contextually-based project", 206 | "bs": "aggregate real-time technologies" 207 | } 208 | }, 209 | { 210 | "id": 10, 211 | "name": "Clementina DuBuque", 212 | "username": "Moriah.Stanton", 213 | "email": "Rey.Padberg@karina.biz", 214 | "address": { 215 | "street": "Kattie Turnpike", 216 | "suite": "Suite 198", 217 | "city": "Lebsackbury", 218 | "zipcode": "31428-2261", 219 | "geo": { 220 | "lat": "-38.2386", 221 | "lng": "57.2232" 222 | } 223 | }, 224 | "phone": "024-648-3804", 225 | "website": "ambrose.net", 226 | "company": { 227 | "name": "Hoeger LLC", 228 | "catchPhrase": "Centralized empowering task-force", 229 | "bs": "target end-to-end models" 230 | } 231 | } 232 | ] -------------------------------------------------------------------------------- /cypress/integration/chat_spec.js: -------------------------------------------------------------------------------- 1 | describe('Send and receive messages over the chat', function() { 2 | it('Types messages in the input and see the message appear in the chat protocol', function() { 3 | //be sure the viewport is big enough to show all the friends 4 | 5 | cy.viewport(950, 750) 6 | cy.visit('localhost:3000/') 7 | 8 | //login with cypress 9 | cy.get('#email') 10 | //.wait(2000) 11 | .type('josearmando.jacq@gmail.com', { force: true },{ delay: 2000 }) 12 | //.wait(2000) 13 | .get('#password') 14 | //.wait(2000) 15 | .type('123456789', { force:true},{ delay: 2000 }) 16 | //.wait(1000) 17 | .get('.login-button') 18 | .click() 19 | 20 | //testing the chat 21 | cy.get('#multiline-flexible') 22 | .type('Hi There Who is working', {force:true},{ delay: 2000 }) 23 | //.wait(1000) 24 | .should('have.value', 'Hi There Who is working') 25 | //.type('{enter}', {force: true}) 26 | .get('#emoji') 27 | .click() 28 | //.wait(1000) 29 | .get('.emoji') 30 | //.should('have.attr', 'tabindex') 31 | .eq(110) 32 | .click({force: true}) 33 | .get('#multiline-flexible') 34 | //.wait(1000) 35 | .type('{enter}', {force: true}) 36 | 37 | cy.get('#multiline-flexible') 38 | .type('We are proud of our project maybe is it not perfect but is a lot of work', {force:true},{ delay: 2000 }) 39 | //.wait(1000) 40 | .should('have.value', 'We are proud of our project maybe is it not perfect but is a lot of work') 41 | //.type('{enter}', {force: true}) 42 | .get('#emoji') 43 | .click() 44 | //.wait(2000) 45 | .get('.emoji') 46 | //.should('have.attr', 'tabindex') 47 | .eq(112) 48 | .click({force: true}) 49 | .get('#multiline-flexible') 50 | //.wait(2000) 51 | .type('{enter}', {force: true}) 52 | 53 | cy.get('#cypress-friend-search-button') 54 | .click() 55 | //.wait(2000) 56 | .get('#searchContact') 57 | //.wait(2000) 58 | .type('h') 59 | .should('have.value', 'h') 60 | //.wait(2000) 61 | cy.get('#friend-list li').contains('h') 62 | .click({force: true}) 63 | //.wait(2000) 64 | 65 | cy.get('#cypress-vedio') 66 | .click({force: true}) 67 | .wait(4000) 68 | .get('#close-cypress') 69 | .click({force:true}) 70 | 71 | cy.get('#cypress-settings') 72 | .click({force:true}) 73 | 74 | 75 | cy.get('#cypress-add-freind') 76 | //.wait(2000) 77 | .click({force: true}) 78 | .get('#close-cypress') 79 | .click({force:true}) 80 | 81 | cy.get('#cypress-add-new') 82 | .type('hus', {force: true},{delay:2000}) 83 | .wait(2000) 84 | .get('#cypress-ser-new-freind') 85 | .click({force: true}) 86 | .wait(2000) 87 | 88 | cy.get('#close-cypress') 89 | .click({force: true}) 90 | .wait(2000) 91 | 92 | cy.visit('localhost:3000/auth') 93 | //logout app 94 | .wait(2000) 95 | .get('#cypress-logout') 96 | .click({force: true}) 97 | /*.find('') 98 | .click('center', {force: true}) 99 | .should('have.value', ':smiley:') 100 | .click({force: true})*/ 101 | //.should('have.value', 'Tester') 102 | //.type('{enter}', { force: true }) 103 | } 104 | )} 105 | ) 106 | 107 | -------------------------------------------------------------------------------- /cypress/integration/friendlist_spec.js: -------------------------------------------------------------------------------- 1 | describe('Search for a friend in the friendlist', function() { 2 | it('Types a name in the search field and see just the corrosponding friend', function() { 3 | //be sure the viewport is big enough to show all the friends 4 | cy.viewport(1600, 800) 5 | 6 | cy.visit('localhost:3000/main') 7 | cy.get('#cypress-friend-search-button').click() // Click on button 8 | 9 | //TODO Create Frieds-seeds with Username 10 | cy.get('#friend-list li').contains('Glenna Reichert') 11 | cy.get('#friend-list li').contains('Ervin Howell') 12 | cy.get('#friend-list li').contains('Clementine Bauch') 13 | 14 | //type beginning of one name in the search bar 15 | cy.get('#searchContact') 16 | .type('Er') 17 | .should('have.value', 'Er') 18 | 19 | cy.get('#friend-list li').its('length').should('eq', 2) 20 | cy.get('#friend-list li').contains('Glenna Reichert') 21 | cy.get('#friend-list li').contains('Ervin Howell') 22 | cy.get('#friend-list li').should('not.contain', 'Clementine Bauch') 23 | 24 | //Type the rest of the name 25 | cy.get('#searchContact') 26 | .type('vin') 27 | .should('have.value', 'Ervin') 28 | cy.get('#friend-list li').its('length').should('eq', 1) 29 | cy.get('#friend-list li').contains('Ervin Howell') 30 | cy.get('#friend-list li').should('not.contain', 'Glenna Reichert') 31 | cy.get('#friend-list li').should('not.contain', 'Clementine Bauch') 32 | 33 | // press backspace and see one more result 34 | cy.get('#searchContact') 35 | .type('{backspace}{backspace}{backspace}') 36 | 37 | cy.get('#friend-list li').its('length').should('eq', 2) 38 | 39 | cy.get('#friend-list li').contains('Glenna Reichert') 40 | cy.get('#friend-list li').contains('Ervin Howell') 41 | cy.get('#friend-list li').should('not.contain', 'Clementine Bauch') 42 | 43 | cy.get('#searchContact') 44 | .type('{backspace}{backspace}{backspace}') 45 | 46 | cy.get('#friend-list li').contains('Ervin Howell') 47 | cy.get('#friend-list li').contains('Glenna Reichert') 48 | cy.get('#friend-list li').contains('Clementine Bauch') 49 | 50 | //type something stupid and check that no one is shown 51 | cy.get('#searchContact') 52 | .type('Prinzessin') 53 | 54 | cy.get('#friend-list li').should('not.contain', 'Ervin Howell') 55 | .should('not.contain', 'Glenna Reichert') 56 | .should('not.contain', 'Clementine Bauch') 57 | 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | module.exports = (on, config) => { 15 | // `on` is used to hook into various events Cypress emits 16 | // `config` is the resolved Cypress config 17 | } 18 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // const express = require('express'); 2 | 3 | /* const express = require('express'); 4 | >>>>>>> 8a681bfafd7dc7376ae08305539c03344a0f4ed1 5 | 6 | // const app = express(); 7 | 8 | // app.use(express.static(__dirname + '/public')); 9 | 10 | */ 11 | /*const server = app.listen(8080); 12 | 13 | const io = require('socket.io').listen(server); 14 | 15 | io.sockets.on('connection', socket => { 16 | const sessionid = socket.id; 17 | console.log("Socket Connected: %s", sessionid); 18 | socket.on('message', body => { 19 | socket.broadcast.emit('message', { 20 | body, 21 | id: sessionid, 22 | }) 23 | }) 24 | 25 | socket.on('abc', body => { 26 | socket.broadcast.emit('abc', body); 27 | }) 28 | 29 | socket.on('subscribe', function(room) { 30 | console.log('joining room', room); 31 | socket.join(room); 32 | }); 33 | 34 | socket.on('send message', function(data) { 35 | console.log('sending room post', data.room); 36 | socket.broadcast.to(data.room).emit('conversation private post', { 37 | message: data.message 38 | }); 39 | }); 40 | 41 | 42 | 43 | 44 | }); 45 | 46 | 47 | console.log(`SkypeClone's Socket server is running at port 8080... 48 | Wait! for a while to start client side server.`);*/ -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "skypeclone", 3 | "version": "1.0.0", 4 | "description": "Building SkypeClone using Material-UI, React.js, Express.js, Node.js, MongoDB & WebRTC", 5 | "main": "app.js", 6 | "engines": { 7 | "node": "v8.6.0" 8 | }, 9 | "scripts": { 10 | "start": "concurrently --kill-others \"npm run server\" \"npm run socket\" \"npm run react\"", 11 | "heroku-postbuild": "npm run build", 12 | "server": "nodemon app.js", 13 | "socket": "nodemon index.js", 14 | "react": "react-scripts start", 15 | "test": "react-scripts test --env=jsdom", 16 | "eject": "react-scripts eject" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/aemal/SkypeClone.git" 21 | }, 22 | "author": "", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/aemal/SkypeClone/issues" 26 | }, 27 | "homepage": "https://github.com/aemal/SkypeClone#readme", 28 | "dependencies": { 29 | "axios": "^0.17.1", 30 | "bcryptjs": "^2.4.3", 31 | "body-parser": "^1.18.2", 32 | "cookie-parser": "^1.4.3", 33 | "cors": "^2.8.4", 34 | "cypress": "^1.4.1", 35 | "emoji-js": "^3.4.0", 36 | "emoji-picker-react": "^2.0.2", 37 | "express": "^4.16.2", 38 | "express-session": "^1.15.6", 39 | "font-awesome": "^4.7.0", 40 | "history": "^4.7.2", 41 | "jsonwebtoken": "^8.1.1", 42 | "jwt-decode": "^2.2.0", 43 | "material-ui": "^1.0.0-beta.30", 44 | "material-ui-icons": "^1.0.0-beta.17", 45 | "material-ui-pickers": "^1.0.0-beta.12", 46 | "material-ui-search-bar": "^1.0.0-beta.3", 47 | "moment": "^2.20.1", 48 | "mongoose": "^4.13.2", 49 | "multiparty": "^4.1.3", 50 | "passport": "^0.4.0", 51 | "passport-facebook": "^2.1.1", 52 | "passport-github2": "^0.1.11", 53 | "passport-google-oauth20": "^1.0.0", 54 | "passport-local": "^1.0.0", 55 | "passport-twitter": "^1.0.4", 56 | "qs": "^6.5.1", 57 | "react": "^16.2.0", 58 | "react-avatar-image-cropper": "^1.1.6", 59 | "react-chat-bubble": "^0.8.7", 60 | "react-dom": "^16.2.0", 61 | "react-emoji-picker": "^1.0.13", 62 | "react-emojione": "^5.0.0", 63 | "react-favicon": "0.0.13", 64 | "react-image-cropper": "^1.3.0", 65 | "react-page-click": "^4.0.2", 66 | "react-redux": "^5.0.6", 67 | "react-router": "^4.2.0", 68 | "react-router-dom": "^4.2.2", 69 | "react-router-redux": "^5.0.0-alpha.9", 70 | "react-scripts": "1.0.17", 71 | "redux": "^3.7.2", 72 | "redux-thunk": "^2.2.0", 73 | "socket.io": "^2.0.4", 74 | "socket.io-client": "*", 75 | "uuid": "^3.1.0" 76 | }, 77 | "devDependencies": { 78 | "concurrently": "^3.5.1", 79 | "faker": "^4.1.0", 80 | "nodemon": "^1.14.8" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /public/images/avatar_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatar_placeholder.png -------------------------------------------------------------------------------- /public/images/avatars/018817e0-0fdf-11e8-b851-978e6f4d47cd-1518431111263.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/018817e0-0fdf-11e8-b851-978e6f4d47cd-1518431111263.jpeg -------------------------------------------------------------------------------- /public/images/avatars/09ffe820-10f8-11e8-ab13-776dd62a40f7-1518551814050.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/09ffe820-10f8-11e8-ab13-776dd62a40f7-1518551814050.png -------------------------------------------------------------------------------- /public/images/avatars/0eeacdf0-1099-11e8-920f-9bfc5d0a82a5-1518511020111.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/0eeacdf0-1099-11e8-920f-9bfc5d0a82a5-1518511020111.png -------------------------------------------------------------------------------- /public/images/avatars/122f4d40-11db-11e8-9b2a-ede91997dfc5-1518649323542.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/122f4d40-11db-11e8-9b2a-ede91997dfc5-1518649323542.png -------------------------------------------------------------------------------- /public/images/avatars/1933c4d0-0f7a-11e8-ac79-9d50ad2ac2fe-1518387771805.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/1933c4d0-0f7a-11e8-ac79-9d50ad2ac2fe-1518387771805.png -------------------------------------------------------------------------------- /public/images/avatars/24739040-10f7-11e8-ab13-776dd62a40f7-1518551428932.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/24739040-10f7-11e8-ab13-776dd62a40f7-1518551428932.png -------------------------------------------------------------------------------- /public/images/avatars/28ec4d80-10ff-11e8-bf22-fbaa2666b36b-1518554872409.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/28ec4d80-10ff-11e8-bf22-fbaa2666b36b-1518554872409.png -------------------------------------------------------------------------------- /public/images/avatars/31a65d80-1181-11e8-a61a-855227501956-1518610721625.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/31a65d80-1181-11e8-a61a-855227501956-1518610721625.png -------------------------------------------------------------------------------- /public/images/avatars/324b0ec0-10cd-11e8-83db-7daa3da16bdd-1518533413293.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/324b0ec0-10cd-11e8-83db-7daa3da16bdd-1518533413293.png -------------------------------------------------------------------------------- /public/images/avatars/35f71410-10ff-11e8-bf22-fbaa2666b36b-1518554894289.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/35f71410-10ff-11e8-bf22-fbaa2666b36b-1518554894289.png -------------------------------------------------------------------------------- /public/images/avatars/394f2890-11d7-11e8-94f5-e52160c67fe6-1518647671193.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/394f2890-11d7-11e8-94f5-e52160c67fe6-1518647671193.png -------------------------------------------------------------------------------- /public/images/avatars/4d27a9a0-0f52-11e8-bc13-7f7d8d72e683-1518370679098.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/4d27a9a0-0f52-11e8-bc13-7f7d8d72e683-1518370679098.jpeg -------------------------------------------------------------------------------- /public/images/avatars/541b3470-11d7-11e8-b40f-93249d525b4e-1518647716152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/541b3470-11d7-11e8-b40f-93249d525b4e-1518647716152.png -------------------------------------------------------------------------------- /public/images/avatars/5a7c19dc0e5bc8265486a549.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/5a7c19dc0e5bc8265486a549.jpeg -------------------------------------------------------------------------------- /public/images/avatars/5a7c3582d8e324513e560f61.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/5a7c3582d8e324513e560f61.jpeg -------------------------------------------------------------------------------- /public/images/avatars/5a7c3878d8e324513e560f64.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/5a7c3878d8e324513e560f64.jpeg -------------------------------------------------------------------------------- /public/images/avatars/5a7c390fd8e324513e560f66.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/5a7c390fd8e324513e560f66.jpeg -------------------------------------------------------------------------------- /public/images/avatars/5a7c401ed8e324513e560f68.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/5a7c401ed8e324513e560f68.jpeg -------------------------------------------------------------------------------- /public/images/avatars/5c50a2a0-117e-11e8-8751-c53ac2f1f265-1518609504714.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/5c50a2a0-117e-11e8-8751-c53ac2f1f265-1518609504714.png -------------------------------------------------------------------------------- /public/images/avatars/5f3cd970-10f7-11e8-a62e-cbfa99f8b88d-1518551527559.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/5f3cd970-10f7-11e8-a62e-cbfa99f8b88d-1518551527559.png -------------------------------------------------------------------------------- /public/images/avatars/70e37680-100b-11e8-a5ac-79b32154e784-1518450195945.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/70e37680-100b-11e8-a5ac-79b32154e784-1518450195945.png -------------------------------------------------------------------------------- /public/images/avatars/71068860-118b-11e8-ad2c-a3677d6a5da0-1518615122918.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/71068860-118b-11e8-ad2c-a3677d6a5da0-1518615122918.png -------------------------------------------------------------------------------- /public/images/avatars/7932fef0-100b-11e8-a5ac-79b32154e784-1518450209887.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/7932fef0-100b-11e8-a5ac-79b32154e784-1518450209887.jpeg -------------------------------------------------------------------------------- /public/images/avatars/8150a1f0-100b-11e8-a5ac-79b32154e784-1518450223503.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/8150a1f0-100b-11e8-a5ac-79b32154e784-1518450223503.jpeg -------------------------------------------------------------------------------- /public/images/avatars/82f02040-10ff-11e8-92ab-a96c9fca111b-1518555023428.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/82f02040-10ff-11e8-92ab-a96c9fca111b-1518555023428.png -------------------------------------------------------------------------------- /public/images/avatars/831f3dc0-1187-11e8-9bbb-23efb34ceeb9-1518613435292.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/831f3dc0-1187-11e8-9bbb-23efb34ceeb9-1518613435292.png -------------------------------------------------------------------------------- /public/images/avatars/86f0c4b0-1172-11e8-9fc1-df184eb3e1d0-1518604422268.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/86f0c4b0-1172-11e8-9fc1-df184eb3e1d0-1518604422268.png -------------------------------------------------------------------------------- /public/images/avatars/8742b230-0fe6-11e8-83b7-1f2f387546fb-1518434342099.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/8742b230-0fe6-11e8-83b7-1f2f387546fb-1518434342099.jpeg -------------------------------------------------------------------------------- /public/images/avatars/8b2e26a0-117a-11e8-957b-a905a1e777ea-1518607865354.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/8b2e26a0-117a-11e8-957b-a905a1e777ea-1518607865354.png -------------------------------------------------------------------------------- /public/images/avatars/8ca4b780-116e-11e8-a1db-49f49c42a420-1518602713848.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/8ca4b780-116e-11e8-a1db-49f49c42a420-1518602713848.png -------------------------------------------------------------------------------- /public/images/avatars/925382a0-1098-11e8-920f-9bfc5d0a82a5-1518510811082.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/925382a0-1098-11e8-920f-9bfc5d0a82a5-1518510811082.jpeg -------------------------------------------------------------------------------- /public/images/avatars/934ed970-11d3-11e8-8575-5d05c91c6f97-1518646104199.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/934ed970-11d3-11e8-8575-5d05c91c6f97-1518646104199.png -------------------------------------------------------------------------------- /public/images/avatars/96c810d0-1106-11e8-b8b9-ab0bf403fbd1-1518558063197.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/96c810d0-1106-11e8-b8b9-ab0bf403fbd1-1518558063197.png -------------------------------------------------------------------------------- /public/images/avatars/a0848090-11d3-11e8-8575-5d05c91c6f97-1518646126362.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/a0848090-11d3-11e8-8575-5d05c91c6f97-1518646126362.png -------------------------------------------------------------------------------- /public/images/avatars/a130f8d0-1105-11e8-b3e6-b34192e721fb-1518557651165.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/a130f8d0-1105-11e8-b3e6-b34192e721fb-1518557651165.png -------------------------------------------------------------------------------- /public/images/avatars/a5045790-100b-11e8-a5ac-79b32154e784-1518450283401.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/a5045790-100b-11e8-a5ac-79b32154e784-1518450283401.jpeg -------------------------------------------------------------------------------- /public/images/avatars/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/avatar.jpg -------------------------------------------------------------------------------- /public/images/avatars/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/avatar.png -------------------------------------------------------------------------------- /public/images/avatars/b82e42a0-1177-11e8-808f-096791b2f992-1518606652362.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/b82e42a0-1177-11e8-808f-096791b2f992-1518606652362.png -------------------------------------------------------------------------------- /public/images/avatars/bc46fa60-11d3-11e8-8575-5d05c91c6f97-1518646172934.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/bc46fa60-11d3-11e8-8575-5d05c91c6f97-1518646172934.png -------------------------------------------------------------------------------- /public/images/avatars/ca8a7360-0f33-11e8-8abd-cd457cbeefcd-1518357575062-26195817_2028128537214124_8149219415562015760_n.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/ca8a7360-0f33-11e8-8abd-cd457cbeefcd-1518357575062-26195817_2028128537214124_8149219415562015760_n.jpg -------------------------------------------------------------------------------- /public/images/avatars/cb019140-117a-11e8-8e6c-7373f06f28da-1518607972436.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/cb019140-117a-11e8-8e6c-7373f06f28da-1518607972436.png -------------------------------------------------------------------------------- /public/images/avatars/ce036220-11d3-11e8-8575-5d05c91c6f97-1518646202690.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/ce036220-11d3-11e8-8575-5d05c91c6f97-1518646202690.png -------------------------------------------------------------------------------- /public/images/avatars/d033dbf0-10fd-11e8-b176-579eb3197efc-1518554294063.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/d033dbf0-10fd-11e8-b176-579eb3197efc-1518554294063.png -------------------------------------------------------------------------------- /public/images/avatars/d4889c60-1187-11e8-9bbb-23efb34ceeb9-1518613571878.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/d4889c60-1187-11e8-9bbb-23efb34ceeb9-1518613571878.png -------------------------------------------------------------------------------- /public/images/avatars/d7090380-1178-11e8-92c3-d7a5e0353d9c-1518607133625.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/d7090380-1178-11e8-92c3-d7a5e0353d9c-1518607133625.png -------------------------------------------------------------------------------- /public/images/avatars/d78c58a0-1099-11e8-920f-9bfc5d0a82a5-1518511356714.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/d78c58a0-1099-11e8-920f-9bfc5d0a82a5-1518511356714.jpeg -------------------------------------------------------------------------------- /public/images/avatars/d78fdd40-10a8-11e8-aad4-0b62eb8ffde8-1518517799188.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/d78fdd40-10a8-11e8-aad4-0b62eb8ffde8-1518517799188.jpeg -------------------------------------------------------------------------------- /public/images/avatars/e1674b20-10dd-11e8-a826-1f2a2d53c76b-1518540579026.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/e1674b20-10dd-11e8-a826-1f2a2d53c76b-1518540579026.png -------------------------------------------------------------------------------- /public/images/avatars/f30c3590-11d5-11e8-b616-95e4ae195525-1518647123817.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/avatars/f30c3590-11d5-11e8-b616-95e4ae195525-1518647123817.png -------------------------------------------------------------------------------- /public/images/avatars/index.html: -------------------------------------------------------------------------------- 1 |

Hey! How are you doing?

2 | -------------------------------------------------------------------------------- /public/images/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/images/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/images/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /public/images/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/images/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /public/images/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /public/images/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/favicon/favicon.ico -------------------------------------------------------------------------------- /public/images/favicon/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | React App 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/images/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /public/images/favicon/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /public/images/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /public/images/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | Who 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/images/who_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/public/images/who_logo.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | Who Messenger 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /server/auth/facebook.js: -------------------------------------------------------------------------------- 1 | const FacebookStrategy = require('passport-facebook').Strategy; 2 | const config = require('../config/config'); 3 | 4 | module.exports = (userModel, passport)=>{ 5 | passport.use(new FacebookStrategy({ 6 | clientID: config.Facebook.APP_ID, 7 | clientSecret: config.Facebook.APP_SECRET, 8 | callbackURL: "http://localhost:8080/auth/facebook/callback", 9 | profileFields: ['id', 'displayName', 'photos', 'gender', 'birthday', 'email'] 10 | }, (accessToken, refreshToken, profile, done)=>{ 11 | userModel.findOne({ 12 | 'emailAddress': profile._json.email 13 | }, (err, user) => { 14 | if (err) { 15 | return done(err); 16 | } 17 | if (user) { 18 | return done(null, user); 19 | } else { 20 | // create the user 21 | var user = new userModel({ 22 | emailAddress: profile._json.email, 23 | // dateOfBirth: new Date(req.body.dateOfBirth), 24 | profile: { 25 | firstName: profile.displayName, 26 | lastName: profile.name.familyName || 'Noun', 27 | gender: profile.gender.charAt(0).toUpperCase() + profile.gender.slice(1) || 'Other', 28 | avatarURL: profile.photos?profile.photos[0].value: '' 29 | }, 30 | loginStrategy: 'facebook' 31 | }); 32 | try { 33 | user.save((err) => { 34 | if (err) throw (err); 35 | console.log('User is created'); 36 | return done(null, user); 37 | }); 38 | } catch (err) { 39 | console.log(err); 40 | return done(err); 41 | } 42 | } 43 | }); 44 | 45 | } 46 | )); 47 | } 48 | 49 | -------------------------------------------------------------------------------- /server/auth/github.js: -------------------------------------------------------------------------------- 1 | const GitHubStrategy = require('passport-github2'); 2 | const config = require('../config/config'); 3 | 4 | module.exports = function (userModel, passport){ 5 | passport.use(new GitHubStrategy({ 6 | clientID: config.GitHub.APP_ID, 7 | clientSecret: config.GitHub.APP_SECRET, 8 | callbackURL: "http://localhost:8080/auth/github/callback", 9 | scope: ['user:email'] 10 | },(accessToken, refreshToken, profile, done)=>{ 11 | console.log(profile.emails[0].value); 12 | userModel.findOne({ 13 | 'emailAddress': profile.emails[0].value 14 | }, (err, user) => { 15 | if (err) { 16 | return done(err); 17 | } 18 | if (user) { 19 | return done(null, user); 20 | } else { 21 | // create the user 22 | var user = new userModel({ 23 | emailAddress: profile.emails[0].value, 24 | profile: { 25 | firstName: profile.displayName, 26 | lastName: profile.displayName || 'Noun', 27 | avatarURL: profile.photos?profile.photos[0].value: '' 28 | }, 29 | loginStrategy: 'github', 30 | loginObject: {}, //We do not really need it for local strateggy so far 31 | contacts : { 32 | decline : [ ], 33 | pending : [ ], 34 | requested : [ ], 35 | blocked : [ ], 36 | friends : [ ] 37 | } 38 | }); 39 | try { 40 | user.save((err) => { 41 | if (err) throw (err); 42 | console.log('User is created'); 43 | return done(null, user); 44 | }); 45 | } catch (err) { 46 | //TODO We should be able to handle different kind of errors and send an appropriate error message 47 | console.log(err); 48 | } 49 | } 50 | }); 51 | } 52 | )); 53 | } -------------------------------------------------------------------------------- /server/auth/google.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const GoogleStrategy = require('passport-google-oauth20').Strategy; 4 | const config = require('../config/config'); 5 | 6 | module.exports = (userModel, passport) => { 7 | passport.use(new GoogleStrategy({ 8 | clientID: config.Google.APP_ID, 9 | clientSecret: config.Google.APP_SECRET, 10 | callbackURL: config.Google.CALLBACK_URL 11 | }, (accessToken, refreshToken, profile, done) => { 12 | userModel.findOne({ 13 | 'emailAddress': profile.emails[0].value 14 | }, (err, user) => { 15 | if (err) { 16 | return done(err); 17 | } 18 | if (user) { 19 | return done(null, user); 20 | } else { 21 | // create the user 22 | var user = new userModel({ 23 | emailAddress: profile.emails[0].value, 24 | password: 'none', 25 | dateOfBirth: new Date(), 26 | profile: { 27 | firstName: profile.name.givenName || profile.displayName, 28 | lastName: profile.name.familyName || 'none', 29 | gender: profile.gender.charAt(0).toUpperCase() + profile.gender.slice(1) || 'Other', 30 | avatarURL: profile.photos ? profile.photos[0].value : '' 31 | }, 32 | loginStrategy: 'google' 33 | }); 34 | try { 35 | user.save((err) => { 36 | if (err) throw (err); 37 | console.log('User is created'); 38 | return done(null, user); 39 | }); 40 | } catch (err) { 41 | console.error(err); 42 | done(err); 43 | } 44 | } 45 | }); 46 | 47 | })); 48 | } -------------------------------------------------------------------------------- /server/auth/index.js: -------------------------------------------------------------------------------- 1 | const User = require('../models/user.model'); 2 | const authStrategies = { 3 | local : require('../auth/local'), 4 | facebook : require('../auth/facebook'), 5 | github : require('../auth/github'), 6 | twitter: require('../auth/twitter'), 7 | google: require('../auth/google') 8 | }; 9 | 10 | module.exports = (passport)=>{ 11 | // serialize sessions 12 | passport.serializeUser((user, done)=>{ 13 | done(null, user.id) 14 | }); 15 | 16 | passport.deserializeUser((id, done)=>{ 17 | User.findById(id, (err, user)=>{ 18 | done(err, user) 19 | }) 20 | }); 21 | 22 | // use these strategies 23 | authStrategies.local(User, passport); 24 | authStrategies.facebook(User, passport); 25 | authStrategies.github(User, passport); 26 | authStrategies.twitter(User, passport); 27 | authStrategies.google(User, passport); 28 | }; -------------------------------------------------------------------------------- /server/auth/local.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcryptjs'); 2 | const LocalStrategy = require('passport-local').Strategy; 3 | 4 | module.exports = (userModel, passport)=>{ 5 | passport.use('signin', new LocalStrategy((username,password,done) => { 6 | userModel.findOne({'emailAddress' : username.toLowerCase()},(err,user) => { 7 | if (err) return done(err); 8 | if (!user) return done(null,false, { message : 'Invalid e-mail address or password' }); 9 | bcrypt.compare(password, user.password, (err, result)=>{ 10 | if (err) { return done(err); } 11 | if(result === true){ 12 | user.status.lastSeen = Date.now(); 13 | user.active = true; 14 | return done(null, user); 15 | }else{ 16 | return done(null, false, { message : 'Invalid password' }); 17 | } 18 | }); 19 | }); 20 | })); 21 | } 22 | -------------------------------------------------------------------------------- /server/auth/logout.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const decode = require('jwt-decode'); 4 | const User = require('../models/user.model'); 5 | 6 | module.exports = (req, res, next)=>{ 7 | if (req.headers.authorization.split(' ')[0] === 'TOKEN'){ 8 | let token = req.headers.authorization.split(' ')[1]; 9 | let decoded = decode(token); 10 | User.findById(decoded._id,(err, user)=>{ 11 | if(err || !user) return next(err); 12 | if(user.accessToken.token === token){ 13 | User.findOneAndUpdate({_id: decoded._id}, 14 | { 15 | $set:{ 16 | accessToken:{ token:undefined, created: Date.now()} 17 | } 18 | }, {upsert: false}, (err, user)=>{ 19 | if(err || !user) return next(err); 20 | req.logout(); 21 | if(req.params.id){ 22 | return next(); 23 | }else{ 24 | return res.json({ success : true, message : 'Logout successfully' }); 25 | } 26 | }); 27 | }else{ 28 | return res.json({ success : false, message : 'Wrong access, Please try again to logout...' }); 29 | } 30 | }); 31 | }else{ 32 | req.logout(); 33 | return res.json({ success : true, message : 'Logout successfully' }); 34 | } 35 | } -------------------------------------------------------------------------------- /server/auth/signin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const passport = require('passport'); 4 | const jwt = require('jsonwebtoken'); 5 | const jwtSecret = require('../config/config').JWT_Secret; 6 | 7 | module.exports = class { 8 | constructor(User){ 9 | this.User = User; 10 | }; 11 | 12 | signin(req, res, next){ 13 | passport.authenticate('signin', (err, user, info)=>{ 14 | if (err || !user) { return res.json({ success : false, message : 'Login failed, email or password is wrong' }); } 15 | req.logIn(user, (err)=>{ 16 | if (err) return next(err); 17 | let {emailAddress, profile, gender, dateOfBirth, status, _id, accessToken, avatarURL, contact} = user; 18 | let token = jwt.sign( 19 | {emailAddress, profile, gender, dateOfBirth, status, _id, avatarURL, contact}, 20 | jwtSecret.SECRET_KEY, 21 | { expiresIn: 14 * 24 * 60 * 60 }); 22 | 23 | this.User.findOneAndUpdate({_id: _id}, 24 | { 25 | $set:{ 26 | accessToken:{ token:token, created: Date.now()} 27 | } 28 | }, {upsert: true, new: true}, (err, user)=>{ 29 | if(err || !user) return next(err); 30 | else next(); 31 | }); 32 | res.json({token: token}); 33 | }); 34 | })(req, res, next); 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /server/auth/signup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const bcrypt = require("bcryptjs"); 4 | 5 | module.exports = class { 6 | constructor(userModel){ 7 | this.userModel = userModel; 8 | }; 9 | 10 | signup(req, res, next){ 11 | if(!req.body.password){ 12 | return res.json({ success : false, message : 'Password is required' }); 13 | } 14 | this.userModel.findOne({ 15 | 'emailAddress': req.body.email.toLowerCase() 16 | }, (err, user) => { 17 | if (err) { 18 | return next(err); 19 | } 20 | if (user) { 21 | return res.json({ success : false, message : 'Signup failed, email is already exist' }); 22 | } else { 23 | let password = req.body.password; 24 | if(password.length >= 8 && password.length <= 20){ 25 | // create the user 26 | bcrypt.genSalt(10, (err, salt) => { 27 | if(err) return next(err); 28 | bcrypt.hash(req.body.password, salt, (err, hash) => { 29 | if(err) return next(err); 30 | let user = new this.userModel({ 31 | emailAddress: req.body.email.toLowerCase(), 32 | password: hash, 33 | dateOfBirth: new Date(req.body.dateOfBirth), 34 | profile: { 35 | firstName: req.body.firstName, 36 | lastName: req.body.lastName, 37 | gender: req.body.gender, 38 | avatarURL: '' 39 | }, 40 | loginStrategy: 'signin' 41 | }); 42 | try { 43 | user.save((err) => { 44 | if (err){ 45 | return next(err); 46 | }else{ 47 | const {emailAddress, profile, gender, dateOfBirth, _id} = user; 48 | return res.json({emailAddress, profile, gender, dateOfBirth}); 49 | }; 50 | }); 51 | } catch (err) { 52 | return next(err); 53 | } 54 | }); 55 | }); 56 | }else{ 57 | return res.json({ success : false, message : 'Password is min 8 and max 20 characters' }); 58 | } 59 | } 60 | }); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /server/auth/twitter.js: -------------------------------------------------------------------------------- 1 | const TwitterStrategy = require('passport-twitter').Strategy; 2 | const config = require('../config/config'); 3 | 4 | module.exports =(userModel, passport)=>{ 5 | passport.use(new TwitterStrategy({ 6 | consumerKey: '4xHOvecu41ApJhIpAPK3HXXLw', 7 | consumerSecret: 'YukabU2gin1dlLZ96gcjEWOWGDkfCOyxHwSwi0SLLbpSC1Vh53', 8 | callbackURL: "http://localhost:8080/auth/twitter/callback" 9 | }, 10 | (token, tokenSecret, profile, done)=>{ 11 | console.log(profile); 12 | userModel.findOne({ 13 | 'emailAddress': profile._json.email 14 | }, (err, user) => { 15 | if (err) { 16 | return done(err); 17 | } 18 | if (user) { 19 | return done(null, user); 20 | } else { 21 | // create the user 22 | var user = new userModel({ 23 | emailAddress: profile._json.email, 24 | // dateOfBirth: new Date(req.body.dateOfBirth), 25 | profile: { 26 | firstName: profile.displayName, 27 | lastName: profile.name.familyName || 'Noun', 28 | gender: profile.gender.charAt(0).toUpperCase() + profile.gender.slice(1) || 'Other', 29 | avatarURL: profile.photos?profile.photos[0].value: '' 30 | }, 31 | loginStrategy: 'facebook' 32 | }); 33 | try { 34 | user.save((err) => { 35 | if (err) throw (err); 36 | console.log('User is created'); 37 | return done(null, user); 38 | }); 39 | } catch (err) { 40 | console.log(err); 41 | return done(err); 42 | } 43 | } 44 | }); 45 | 46 | } 47 | )); 48 | } 49 | -------------------------------------------------------------------------------- /server/config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | Facebook : { 5 | APP_ID: process.env.Facebook_APP_ID || "Your APP_ID", 6 | APP_SECRET: process.env.Facebook_APP_SECRET || "Your APP_SECRET", 7 | CALLBACK_URL: process.env.Facebook_CALLBACK_URL || "Your CALLBACK_URL" 8 | }, 9 | GitHub : { 10 | APP_ID: process.env.GitHub_APP_ID || "Your APP_ID", 11 | APP_SECRET: process.env.GitHub_APP_SECRET || "Your APP_SECRET", 12 | CALLBACK_URL: process.env.GitHub_CALLBACK_URL || "Your CALLBACK_URL" 13 | }, 14 | Google : { 15 | APP_ID: process.env.Google_APP_ID || "Your APP_ID", 16 | APP_SECRET: process.env.Google_APP_SECRET || "Your APP_SECRET", 17 | CALLBACK_URL: process.env.Google_CALLBACK_URL || "Your CALLBACK_URL" 18 | }, 19 | Twitter : { 20 | APP_ID: process.env.Twitter_APP_ID || "Your APP_ID", 21 | APP_SECRET: process.env.Twitter_APP_SECRET || "Your APP_SECRET", 22 | CALLBACK_URL: process.env.Twitter_CALLBACK_URL || "Your CALLBACK_URL" 23 | }, 24 | JWT_Secret : { 25 | SECRET_KEY: process.env.JWT_SECRET || "THEsweetyCateverMaloos" 26 | }, 27 | SESSION_SECRET: { 28 | SECRET_KEY: process.env.SESSION_SECRET || "THEsweetyCateverMaloos" 29 | }, 30 | DB_Connection: { 31 | URL: process.env.DB_Connection || "mongodb://localhost:27017/skypeClone" 32 | }, 33 | SERVER_PORT: { 34 | PORT: process.env.PORT || 3001 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /server/handlers/contact.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = class { 4 | constructor(userModel) { 5 | this.userModel = userModel; 6 | }; 7 | 8 | searchContact(req, res){ 9 | let regex = new RegExp(req.params.keyword, 'i'); 10 | let query = this.userModel.find({$or:[{'profile.firstName': regex}, {'profile.lastName': regex}]}); 11 | 12 | query.exec((err, users)=>{ 13 | if(err || !users){ 14 | res.json({ success : false, message : 'Can not find any match names' }); 15 | }else{ 16 | this.userModel.findById({_id: req.id}, (err, user)=>{ 17 | let usersList; 18 | user.contacts.friends.length>0? 19 | user.contacts.friends.map(item=> 20 | usersList = users.filter(el=> 21 | JSON.stringify(el._id) !== JSON.stringify(item.userId) && JSON.stringify(el._id) !== JSON.stringify(user._id)) 22 | .map(user=>({profile:user.profile, status:user.status, _id:user._id, dateOfBirth:user.dateOfBirth}))) 23 | :usersList= users.filter(el=> JSON.stringify(el._id) !== JSON.stringify(user._id)) 24 | .map(user=>({profile:user.profile, status:user.status, _id:user._id, dateOfBirth:user.dateOfBirth})); 25 | res.json({usersList}); 26 | }); 27 | } 28 | }); 29 | } 30 | } -------------------------------------------------------------------------------- /server/handlers/friend.js: -------------------------------------------------------------------------------- 1 | module.exports = class { 2 | constructor(userModel) { 3 | this.userModel = userModel; 4 | } 5 | 6 | add(req,res, next) { 7 | 8 | const friendRequest = { 9 | fullName: req.body.fullName, 10 | avatarURL: req.body.avatarURL, 11 | userId: req.body.userId 12 | } 13 | 14 | this.userModel.findById({_id: req.id}, (err, user)=>{ 15 | if (err || !user)return next(err); 16 | let friend = user.contacts.friends; 17 | friend.push(friendRequest); 18 | if(friend.length>0){ 19 | let contacts = { 20 | friends: friend, 21 | blocked: [], 22 | requested: [], 23 | pending: [], 24 | decline: [] 25 | }; 26 | this.userModel.findOneAndUpdate({_id: req.id}, 27 | { 28 | $set:{contacts : contacts 29 | } 30 | },{upsert: false ,multi: true}, (err, user)=>{ 31 | if (err || !user)return next(err); 32 | 33 | this.userModel.findById({_id: req.body.userId}, (err, user2)=>{ 34 | if (err || !user2)return next(err); 35 | let friendAccept = user2.contacts.friends; 36 | friendAccept.push({ 37 | fullName: user.profile.firstName, 38 | avatarURL: user.profile.avatarURL, 39 | userId: user._id 40 | }); 41 | if(friendAccept.length>0){ 42 | let contacts = { 43 | friends: friendAccept, 44 | blocked: [], 45 | requested: [], 46 | pending: [], 47 | decline: [] 48 | }; 49 | this.userModel.findOneAndUpdate({_id: req.body.userId}, 50 | { 51 | $set:{contacts : contacts 52 | } 53 | },{upsert: false ,multi: true}, (err, user2)=>{ 54 | if (err || !user2)return next(err); 55 | res.json({ success : true, message : 'Request is sent successfully...'}); 56 | }); 57 | } 58 | }); 59 | }); 60 | } 61 | 62 | }); 63 | } 64 | accept(req,res) { 65 | try{ 66 | let pendinRequest = req.user.contacts.requested.splice(req.user.contacts.requested.indexOf(req.params.id),1); 67 | req.user.contacts.friends.push(pendinRequest); 68 | req.user.save((err)=>{ 69 | if (err) throw(err); 70 | this.userModel.findById(req.params.id,(err, user)=>{ 71 | if (err) throw(err); 72 | let requestReciver = user.contacts.pending.splice(req.user.contacts.pending.indexOf(req.user.id),1); 73 | user.contacts.friends.push(requestReciver); 74 | user.save((err)=>{ 75 | if (err) throw(err); 76 | res.sendStatus(200); 77 | }) 78 | }) 79 | }) 80 | }catch(err){ 81 | res.sendStatus(500) 82 | consolelog(err); 83 | } 84 | } 85 | 86 | decline(req,res) { 87 | req.user.contacts.requested.splice(req.user.contacts.requested.indexOf(req.params.id),1); 88 | this.userModel.findById(req.params.id,(err, user)=>{ 89 | if (err) throw(err); 90 | user.contacts.pending.splice(req.user.contacts.pending.indexOf(req.user.id),1); 91 | user.save((err)=>{ 92 | if (err) throw(err); 93 | res.sendStatus(200); 94 | }); 95 | 96 | }); 97 | 98 | } 99 | remove(req,res) { 100 | req.user.contacts.friends.splice(req.user.contacts.requested.indexOf(req.params.id),1); 101 | this.userModel.findById(req.params.id,(err, user)=>{ 102 | if (err) throw(err); 103 | user.contacts.friends.splice(req.user.contacts.friends.indexOf(req.user.id),1); 104 | user.save((err)=>{ 105 | if (err) throw(err); 106 | res.sendStatus(200); 107 | }); 108 | 109 | }); 110 | 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /server/handlers/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = class { 4 | constructor(messageModel, chatModel) { 5 | this.messageModel = messageModel; 6 | this.chatModel = chatModel; 7 | }; 8 | get(req,res,next) { 9 | let userID = req.body.userID; 10 | let friendID = req.body.friendID; 11 | 12 | this.chatModel.findOne({$and:[{participants:{ "$in" : [userID]}}, {participants:{ "$in" : [friendID]}}]}) 13 | .exec((err, chat) => { 14 | if(err) next(err); 15 | if(!chat) { 16 | this.chatModel.create({participants:[ userID, friendID ]}) 17 | .then(data=> res.json(data)) 18 | .catch(err=> console.log(err)); 19 | } else { 20 | this.messageModel.find({chatID: chat._id}).limit(100) 21 | .exec((err, messages)=>{ 22 | if(err || !messages)next(err); 23 | console.log(messages); 24 | res.json({chat, messages}); 25 | }) 26 | } 27 | }); 28 | }; 29 | send(req,res,next) { 30 | 31 | let roomID = req.body.roomID; 32 | let userID = req.body.userID; 33 | let newMessage = req.body.message; 34 | 35 | this.messageModel.findOne({roomID: roomID}) 36 | .exec((err, chat) => { 37 | if(err) next(err); 38 | if(!chat) { 39 | this.messageModel.create({ 40 | chatID: roomID, 41 | userID: userID, 42 | message: newMessage 43 | }) 44 | .then(data=> res.json(data)) 45 | .catch(err=> console.log(err)); 46 | } else { 47 | let messages = chat.messages; 48 | messages.push(newMessage); 49 | if(messages.length>0){ 50 | this.messageModel.findOneAndUpdate({_id: chat._id}, 51 | { 52 | $set:{messages : messages 53 | } 54 | },{upsert: false ,multi: true}, (err, chat)=>{ 55 | if (err || !chat)return next(err); 56 | return res.json({ success : true, message : 'message is updated successfully...'}); 57 | }); 58 | } 59 | 60 | } 61 | }); 62 | 63 | }; 64 | messageHistory (req, res, next){ 65 | this.chatModel.collection.findOne({participants: {$all: [req.body.sender, req.body.receiver]}}, (err, chat) => { 66 | if(err) { 67 | next(err); 68 | } else { 69 | this.messageModel.find({chatID: chat._id}, (err, message) => { 70 | if(err) { 71 | next(err); 72 | } else { 73 | res.json(message); 74 | } 75 | }); 76 | } 77 | }); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /server/handlers/profile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const logout = require('../auth/logout'); 4 | const bcrypt = require('bcryptjs'); 5 | const fs = require('fs'); 6 | 7 | function deleteAvatar(req, next){ 8 | let uploadDir = __dirname + '/../../public/images/avatars'; 9 | fs.unlink(`${uploadDir}/${req.filename}`, err=>{if(err) next();}); 10 | } 11 | 12 | module.exports = class { 13 | constructor(userModel) { 14 | this.userModel = userModel; 15 | } 16 | getProfile(req,res,next) { 17 | this.userModel.findOne({_id: req.id}).exec((err, user)=>{ 18 | if(err||!user){ 19 | res.json({ success : false, message : 'User not found' }); 20 | }else{ 21 | let {emailAddress, profile, gender, dateOfBirth, status, _id, accessToken} = user; 22 | res.json({emailAddress, profile, gender, dateOfBirth, status}); 23 | } 24 | }); 25 | } 26 | 27 | updatePassword(req,res,next){ 28 | let id = req.id; 29 | let password = req.body.password; 30 | if(password.length >= 8 && password.length <= 20){ 31 | bcrypt.genSalt(10,(err, salt)=>bcrypt.hash(password, salt, (err, hash)=>{ 32 | this.userModel.findOneAndUpdate({_id: id}, 33 | { 34 | $set:{ 35 | password: hash 36 | } 37 | },{upsert: false}, (err, user)=>{ 38 | if(err || !user) return next(err); 39 | logout(req, res, next); 40 | return res.json({ success : true, message : 'Password is changed successfully, please login with new password...' }); 41 | }); 42 | })); 43 | }else{ 44 | return res.json({ success : false, message : 'Password is min 8 and max 20 characters' }); 45 | } 46 | }; 47 | 48 | editProfile(req,res,next) { 49 | let id = req.id; 50 | let user = req.user; 51 | let body = req.body; 52 | let emailAddress = body.emailAddress || user.emailAddress; 53 | let dateOfBirth = new Date(body.dateOfBirth) || user.dateOfBirth; 54 | let profile = { 55 | firstName: body.firstName || user.profile.firstName, 56 | lastName: body.lastName || user.profile.lastName, 57 | gender: body.gender || user.profile.gender, 58 | avatarURL: req.filename || user.profile.avatarURL 59 | }; 60 | if(body.emailAddress){ 61 | this.userModel.findOne({ 62 | 'emailAddress': req.body.emailAddress.toLowerCase() 63 | }, (err, user) => { 64 | if (err){ 65 | deleteAvatar(req, next); 66 | return next(err); 67 | }; 68 | if (user) { 69 | deleteAvatar(req, next); 70 | return res.json({ success : false, message : 'This email is already exist' }); 71 | } else { 72 | this.userModel.findOneAndUpdate({_id: id}, 73 | { 74 | $set:{profile : profile, 75 | emailAddress : emailAddress.toLowerCase(), 76 | dateOfBirth : dateOfBirth 77 | } 78 | },{upsert: false ,multi: true}, (err, user)=>{ 79 | if (err || !user){ 80 | deleteAvatar(req, next); 81 | return res.json({ success : false, message : 'profile is not edited....', Error: err}); 82 | }; 83 | logout(req, res, next); 84 | return res.json({ success : true, message : 'profile is edited successfully, please login with new email...'}); 85 | }); 86 | } 87 | } 88 | ); 89 | } else { 90 | this.userModel.findOneAndUpdate({_id: id}, 91 | { 92 | $set:{profile : profile, 93 | dateOfBirth : dateOfBirth 94 | } 95 | },{upsert: false ,multi: true}, (err, user)=>{ 96 | if(err || !user){ 97 | deleteAvatar(req, next); 98 | return res.json({ success : false, message : 'profile is not edited....', Error: err}); 99 | }; 100 | let sendUser = user => new Promise(res=> res(user)); 101 | sendUser(user).then((user)=>{ 102 | let userObj = {}; 103 | if(user.profile.avatarURL !== req.filename){ 104 | let avatarURL = req.filename; 105 | let {emailAddress, profile, gender, dateOfBirth, _id, status} = user; 106 | return userObj = {emailAddress, profile, gender, dateOfBirth, avatarURL, _id}; 107 | }; 108 | }).then((data)=>res.json({ success : true, message : 'profile is edited successfully', user: data})); 109 | 110 | }); 111 | } 112 | } 113 | }; 114 | 115 | 116 | -------------------------------------------------------------------------------- /server/handlers/user.js: -------------------------------------------------------------------------------- 1 | module.exports = class { 2 | 3 | constructor(userModel) { 4 | this.userModel = userModel; 5 | } 6 | 7 | get(req,res,next) { 8 | this.userModel.findOne({_id: req.id}).exec((err, user)=>{ 9 | if(err || !user){ 10 | res.json({success : false, message : "Friends are not available..."}) 11 | }else{ 12 | res.json(user.contacts.friends); 13 | } 14 | }); 15 | } 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /server/middleware/authenticated.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const jwt = require("jsonwebtoken"); 4 | const User = require("../models/user.model"); 5 | const jwtSecret = require("../config/config").JWT_Secret; 6 | 7 | module.exports = (req, res, next) => { 8 | if (req.headers.authorization.split(" ")[0] === "TOKEN") { 9 | let token = req.headers.authorization.split(" ")[1]; 10 | jwt.verify(token, jwtSecret.SECRET_KEY, (err, decoded) => { 11 | req.id = decoded._id; 12 | req.user = decoded; 13 | if (err) return next(err); 14 | User.findById(decoded._id, (err, user) => { 15 | if (err || !user) return next(err); 16 | if (user.accessToken.token === token) { 17 | return next(); 18 | } else { 19 | return res.json({ success: false, message: "Access denied" }); 20 | } 21 | }); 22 | }); 23 | } else { 24 | return res.json({ success: false, message: "Access denied" }); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /server/middleware/file-upload.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const multiparty = require('multiparty'); 4 | const fs = require('fs'); 5 | const qs = require('qs'); 6 | const uuidv1 = require('uuid/v1'); 7 | 8 | function incomingForm(req, res, next) { 9 | let data = {}; 10 | let count = 0; 11 | 12 | if (req._body) return next(); 13 | req.body = req.body || {}; 14 | 15 | // ignore GET 16 | if ('GET' === req.method || 'HEAD' === req.method) return next(); 17 | 18 | function ondata(name, val, data){ 19 | if (Array.isArray(data[name])) { 20 | data[name].push(val); 21 | } else if (data[name]) { 22 | data[name] = [data[name], val]; 23 | } else { 24 | data[name] = val; 25 | } 26 | }; 27 | 28 | // flag as parsed 29 | req._body = true; 30 | 31 | const form = new multiparty.Form(); 32 | form.uploadDir = __dirname + '/../../public/images/avatars'; 33 | form.maxFieldsSize = 1 * 1024 * 1024; // Memory size 34 | form.maxFilesSize = 1 * 1024 * 1024; 35 | 36 | form.on('error', (err)=>{ 37 | //res.header('Connection', 'close'); 38 | if(req.filename){ 39 | fs.unlink(`${form.uploadDir}/${req.filename}`, err=>{if(err) return netx(err);}); 40 | } 41 | next(err); 42 | }); 43 | 44 | form.on('field', (name, val)=>{ 45 | ondata(name, val, data); 46 | }); 47 | 48 | //Parts are emitted when parsing the form 49 | form.on('part', (part)=>{ 50 | const type = part.headers['content-type']; 51 | const size = part.byteCount - part.byteOffset; 52 | count++; 53 | if(count>1){ 54 | form.emit('error', new Error('Only one file is allowed...')); 55 | part.resume(); 56 | return; 57 | }; 58 | 59 | if (part.filename && type === 'image/jpeg' || type === 'image/jpg' || type === 'image/png' || type === 'image/gif') { 60 | const mimeType = type.substring('6'); 61 | const name = uuidv1() + '-' + Date.now() + '.' + mimeType; 62 | const path = form.uploadDir + "/" + name; 63 | req.filename = name; 64 | part.pipe(fs.createWriteStream(path)); 65 | 66 | if (size > form.maxFilesSize) { 67 | form.emit('error', new Error('File size is bigger than 1MB...')); 68 | part.resume(); 69 | return; 70 | } 71 | 72 | } else { 73 | form.emit('error', new Error('Only image files are allowed...')); 74 | part.resume(); 75 | return; 76 | } 77 | 78 | part.on('error',(err)=>form.emit(err)); 79 | 80 | }); 81 | 82 | form.on('progress', (bytesReceived, bytesExpected) => { 83 | const percent = (bytesReceived / bytesExpected * 100) | 0; 84 | process.stdout.write('Uploading: %' + percent + '\r'); 85 | console.log(); 86 | }); 87 | 88 | form.on('close', () => { 89 | try { 90 | req.body = qs.parse(data, { allowDots: true }) 91 | next(); 92 | } catch (err) { 93 | err.status = 400; 94 | next(err); 95 | } 96 | }); 97 | form.parse(req); 98 | }; 99 | 100 | module.exports = incomingForm; 101 | -------------------------------------------------------------------------------- /server/mock-data.js: -------------------------------------------------------------------------------- 1 | const faker = require('faker'); 2 | 3 | module.exports = function(User,Message,callback) { 4 | 5 | function createUsers(cb){ 6 | const data = []; 7 | for(let i = 0; i < 10; i++){ 8 | data.push({ 9 | emailAddress: faker.internet.email(), 10 | password: faker.internet.password(), 11 | dateOfBirth: faker.date.past(), 12 | profile:{ 13 | firstName: faker.name.firstName(), 14 | lastName: faker.name.lastName(), 15 | gender: faker.random.arrayElement(['Male', 'Female', 'Other']), 16 | Avatar: faker.image.avatar() 17 | }, 18 | status:{ 19 | lastSeen: faker.date.recent(), 20 | active: faker.random.boolean() 21 | }, 22 | loginStrategy:faker.random.arrayElement([ 23 | // 'facebook', 24 | // 'twitter', 25 | // 'instagram', 26 | // 'github', 27 | 'local' 28 | ]), 29 | contacts:{ 30 | friends: [ 31 | // { 32 | // fullName:faker.name.findName(), 33 | // avatarURL:faker.image.avatar(), 34 | // userId:faker.random.uuid() 35 | // }, 36 | // { 37 | // fullName:faker.name.findName(), 38 | // avatarURL:faker.image.avatar(), 39 | // userId:faker.random.uuid() 40 | // }, 41 | // { 42 | // fullName:faker.name.findName(), 43 | // avatarURL:faker.image.avatar(), 44 | // userId:faker.random.uuid() 45 | // }, 46 | // { 47 | // fullName:faker.name.findName(), 48 | // avatarURL:faker.image.avatar(), 49 | // userId:faker.random.uuid() 50 | // } 51 | ], 52 | 53 | blocked: [ 54 | // { 55 | // fullName:faker.name.findName(), 56 | // avatarURL:faker.image.avatar(), 57 | // userId:faker.random.uuid() 58 | // }, 59 | // { 60 | // fullName:faker.name.findName(), 61 | // avatarURL:faker.image.avatar(), 62 | // userId:faker.random.uuid() 63 | // }, 64 | // { 65 | // fullName:faker.name.findName(), 66 | // avatarURL:faker.image.avatar(), 67 | // userId:faker.random.uuid() 68 | // } 69 | ], 70 | 71 | requested: [ 72 | // { 73 | // fullName:faker.name.findName(), 74 | // avatarURL:faker.image.avatar(), 75 | // userId:faker.random.uuid() 76 | // }, 77 | // { 78 | // fullName:faker.name.findName(), 79 | // avatarURL:faker.image.avatar(), 80 | // userId:faker.random.uuid() 81 | // } 82 | ], 83 | 84 | pending: [ 85 | // { 86 | // fullName:faker.name.findName(), 87 | // avatarURL:faker.image.avatar(), 88 | // userId:faker.random.uuid() 89 | // }, 90 | // { 91 | // fullName:faker.name.findName(), 92 | // avatarURL:faker.image.avatar(), 93 | // userId:faker.random.uuid() 94 | // } 95 | ] 96 | } 97 | }); 98 | } 99 | User.create(data,cb); 100 | } 101 | function createMessages (userIds,cb){ 102 | const messages = userIds.map(function (id){ 103 | return { 104 | senderID: id, 105 | message: faker.lorem.text() 106 | }; 107 | }); 108 | Message.create(messages,cb) 109 | } 110 | 111 | User.find({}).remove(function(err){ 112 | if(err){ 113 | return callback(err); 114 | } 115 | Message.find({}).remove(function(err){ 116 | if(err){ 117 | return callback(err); 118 | } 119 | createUsers(function(err,users){ 120 | if(err){ 121 | return callback(err); 122 | } 123 | const ids = users.map(function (user){ 124 | return user.id; 125 | }); 126 | createMessages(ids, function(err){ 127 | if(err) return callback(err); 128 | console.log('user ids',ids); 129 | callback(); 130 | }) 131 | 132 | }) 133 | 134 | }); 135 | }); 136 | } 137 | -------------------------------------------------------------------------------- /server/models/chat.model.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | var ChatSchema = new Schema ({ 5 | participants: [{type: Schema.Types.ObjectId, ref: 'User'}] 6 | }); 7 | 8 | module.exports = mongoose.model('chat', ChatSchema); -------------------------------------------------------------------------------- /server/models/message.model.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | var MessageSchema = new Schema ({ 5 | chatID: {type: Schema.Types.ObjectId, ref: 'Chat'}, 6 | userID: {type: Schema.Types.ObjectId, ref: 'User'}, 7 | message: String 8 | }); 9 | 10 | module.exports = mongoose.model('message', MessageSchema); -------------------------------------------------------------------------------- /server/models/user.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const Schema = mongoose.Schema; 5 | 6 | const emailValidation = email => { 7 | const valid = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; 8 | return valid.test(email); 9 | }; 10 | 11 | const contact = new Schema({ 12 | fullName: { 13 | type: String, 14 | required: true 15 | }, 16 | avatarURL: { 17 | type: String, 18 | required: true 19 | }, 20 | userId: { 21 | type: Schema.Types.ObjectId, 22 | required: true, 23 | ref: 'User' 24 | } 25 | }); 26 | 27 | const userSchema = new Schema({ 28 | emailAddress: { 29 | type: String, 30 | unique: true, 31 | lowercase: true, 32 | required: 'Email is required', 33 | validate: [emailValidation, 'Email is not valid...'] 34 | }, 35 | password: { 36 | type: String, 37 | required: 'Fill the password field', 38 | }, 39 | dateOfBirth: { 40 | type: Date, 41 | required: 'Date of birth is required', 42 | }, 43 | profile: { 44 | firstName: { 45 | type: String, 46 | required: 'First name is required...', 47 | minlength: [3, 'First name must has min 3 and max 20 characters ...'], 48 | maxlength: [20, 'First name must has min 3 and max 20 characters ...'] 49 | }, 50 | lastName: { 51 | type: String, 52 | required: 'Last name is required...', 53 | minlength: [3, 'Last name must has min 3 and max 20 characters ...'], 54 | maxlength: [20, 'Last name must has min 3 and max 20 characters ...'] 55 | }, 56 | gender: { 57 | type: String, 58 | enum: { 59 | values: ['Male', 'Female', 'Other'], 60 | message: 'Select gender ...' 61 | } 62 | }, 63 | avatarURL: String 64 | }, 65 | status: { 66 | lastSeen: { 67 | type: Date 68 | }, 69 | active: { 70 | type: Boolean, 71 | default: false 72 | } 73 | }, 74 | loginStrategy: { 75 | type: String, 76 | enum: { 77 | values: ['facebook', 'twitter', 'instagram', 'github', 'signin'], 78 | message: 'Unknown oAuth provider ;(' 79 | } 80 | }, 81 | loginObject: Object, 82 | contacts: { 83 | friends: [contact], 84 | blocked: [contact], 85 | requested: [contact], 86 | pending: [contact], 87 | decline: [contact] 88 | }, 89 | accessToken:{ 90 | token: { 91 | type: String, 92 | }, 93 | created: { 94 | type: Date 95 | } 96 | } 97 | }); 98 | 99 | userSchema.virtual('fullName').get(()=>{ 100 | return this.profile.firstName + ' ' + this.profile.lastName; 101 | }); 102 | 103 | const User = mongoose.model('user', userSchema); 104 | 105 | //To check the email is unique or not... 106 | User.schema.path('emailAddress').validate((value, done)=>{ 107 | User.findOne({ emailAddress: value },(err, user)=>{ 108 | if(err) return done(err); 109 | done(!user); 110 | }); 111 | }, 'This email address is already registered'); 112 | 113 | module.exports = User; -------------------------------------------------------------------------------- /server/routers/auth-routers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const router = require('express').Router(); 4 | const Signup = require('../auth/signup'); 5 | const Signin = require('../auth/signin'); 6 | const logout = require('../auth/logout'); 7 | const User = require('../models/user.model'); 8 | 9 | const signup = new Signup(User); 10 | const signin = new Signin(User); 11 | 12 | module.exports = (passport)=>{ 13 | 14 | router.get('/logout', logout); 15 | 16 | router.post('/login', signin.signin.bind(signin)); 17 | 18 | router.post('/signup', signup.signup.bind(signup)); 19 | 20 | router.get('/facebook', 21 | passport.authenticate('facebook', { scope: ['email', 'public_profile'] })); 22 | 23 | router.get('/facebook/callback', 24 | passport.authenticate('facebook', { failureRedirect: '/auth/login' }), 25 | (req, res)=>{ 26 | res.json({ success : true, message : 'You are loged in through your twitter account' }); 27 | }); 28 | 29 | router.get('/github', 30 | passport.authenticate('github', { scope: [ 'user:email' ] })); 31 | 32 | router.get('/github/callback', 33 | passport.authenticate('github', { failureRedirect: '/auth/login' }), 34 | (req, res)=>{ 35 | res.json({ success : true, message : 'You are loged in through your twitter account' }); 36 | }); 37 | 38 | router.get('/twitter', 39 | passport.authenticate('twitter')); 40 | router.get('/twitter/callback', 41 | passport.authenticate('twitter',{failureRedirect: '/auth/login'}), 42 | (req, res)=>{ 43 | res.json({ success : true, message : 'You are loged in through your twitter account' }); 44 | }); 45 | 46 | router.get('/google', 47 | passport.authenticate('google', {scope: ['email']})); 48 | router.get('/google/callback', 49 | passport.authenticate('google', {failureRedirect: '/auth/login'}), 50 | (req, res) => { 51 | res.json({ success : true, message : 'You are loged in through your twitter account' }); 52 | }); 53 | 54 | return router; 55 | } 56 | -------------------------------------------------------------------------------- /server/routers/user-routers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const router = require('express').Router(); 4 | //middlewares 5 | const isAuthenticated = require('../middleware/authenticated'); 6 | const multiparty = require('../middleware/file-upload'); 7 | 8 | const Message = require('../models/message.model'); 9 | const User = require('../models/user.model'); 10 | const Chat = require('../models/chat.model'); 11 | 12 | //Handlers 13 | const UserHandler = require('../handlers/user.js'); 14 | const FriendHandler = require('../handlers/friend.js'); 15 | const ProfileHandler = require('../handlers/profile.js'); 16 | const ContactHandler = require('../handlers/contact.js'); 17 | const MessageHandler = require('../handlers/message.js'); 18 | 19 | const userHandler = new UserHandler(User); 20 | const friendHandler = new FriendHandler(User); 21 | const profileHandler = new ProfileHandler(User); 22 | const contactHandler = new ContactHandler(User); 23 | const messageHandler = new MessageHandler(Message, Chat); 24 | 25 | module.exports = ()=>{ 26 | 27 | router.post('/message/get_history', isAuthenticated, messageHandler.messageHistory.bind(messageHandler)); 28 | router.get('/contacts/search/:keyword', isAuthenticated, contactHandler.searchContact.bind(contactHandler)); 29 | 30 | router.post('/message/send', isAuthenticated, messageHandler.send.bind(messageHandler)); 31 | router.post('/message/get', isAuthenticated, messageHandler.get.bind(messageHandler)); 32 | 33 | router.get('/get_friends/:id', isAuthenticated, userHandler.get.bind(userHandler)); 34 | 35 | router.get('/profile/:id', isAuthenticated, profileHandler.getProfile.bind(profileHandler)); 36 | router.post('/profile_edit/:id', isAuthenticated, multiparty, profileHandler.editProfile.bind(profileHandler)); 37 | router.post('/profile/update_password/:id', isAuthenticated, profileHandler.updatePassword.bind(profileHandler)); 38 | 39 | router.post('/friend/add/:id', isAuthenticated, friendHandler.add.bind(friendHandler)); 40 | router.get('/friend/accept/:id', isAuthenticated, friendHandler.accept.bind(friendHandler)) 41 | router.get('/friend/decline/:id', isAuthenticated, friendHandler.decline.bind(friendHandler)) 42 | router.get('/friend/remove/:id', isAuthenticated, friendHandler.remove.bind(friendHandler)); 43 | 44 | return router; 45 | }; 46 | -------------------------------------------------------------------------------- /src/App.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 | @keyframes App-logo-spin { 26 | from { transform: rotate(0deg); } 27 | to { transform: rotate(360deg); } 28 | } 29 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import UserAvatar from "./components/UserAvatar"; 3 | import ContactList from "./components/contactList"; 4 | import ContactDetail from "./components/contactDetail"; 5 | import MessagesLog from "./components/messagesLog"; 6 | import NewMessage from "./components/newMessage"; 7 | import { fetchContactList } from "./actions/userActions"; 8 | import io from "socket.io-client"; 9 | import moment from "moment"; 10 | import { connect } from "react-redux"; 11 | import SearchBar from "./container/FriendsSearchBar"; //FIXME: Incompatible with Material UI 1.0 Beta. Use react-autosuggest instead. 12 | import "./App.css"; 13 | import "./style.css"; 14 | import Grid from "material-ui/Grid"; 15 | import Paper from "material-ui/Paper"; 16 | import decode from "jwt-decode"; 17 | import config from "./config/config"; 18 | import store from "./store"; 19 | 20 | 21 | function mapStateToProps(state, filter) { 22 | return { 23 | //currentUserData:state.changeSettingReducer.currentUserData, 24 | contactList: state.contactListReducers.contactList.filter(c => { 25 | return ( 26 | c.fullName 27 | .toLowerCase() 28 | .indexOf(state.contactListFilterReducer.toLowerCase()) > -1 29 | ); 30 | 31 | //return c.name.toLowerCase().indexOf(state.contactListFilterReducer.toLowerCase()) > -1 32 | }) 33 | }; 34 | } 35 | 36 | 37 | class App extends Component { 38 | constructor(props) { 39 | super(props); 40 | this.state = { 41 | direction: "row", 42 | justify: "flex-start", 43 | alignItems: "stretch", 44 | socketId: "", 45 | socketChanelId:'', 46 | messages: [], 47 | moment: moment() 48 | .startOf("day") 49 | .fromNow() 50 | }; 51 | this.handleSubmit = this.handleSubmit.bind(this); 52 | this.chatMessageHandler = this.chatMessageHandler.bind(this); 53 | 54 | this.socket = io(config.SOCKET_URL); 55 | } 56 | 57 | componentWillMount() { 58 | this.props.dispatch(fetchContactList()); 59 | this.socket.on("connect", socket => this.connect(socket)); 60 | 61 | } 62 | 63 | componentDidMount() { 64 | console.log(store.getState()) 65 | console.log(this.props) 66 | console.log(this.props.contactList) 67 | //this.socket.on("message", message => { 68 | /*this.socket.on(this.state.socketChanelId, message => { 69 | console.log("received messages...") 70 | this.setState({ messages: [...this.state.messages, message] }); 71 | });*/ 72 | 73 | //FIXME: this is a hack, try to bind socket.io properly with "this" 74 | this.chatMessageHandler(this); 75 | } 76 | 77 | chatMessageHandler(thisKeyword) { 78 | this.socket.on('chatMessages', function(data) { 79 | //display data.message 80 | console.log("received: ", data); 81 | //console.log(state.messages) 82 | thisKeyword.setState({ messages: [...thisKeyword.state.messages, data] }); 83 | 84 | }); 85 | } 86 | 87 | connect(socket) { 88 | this.setState({ 89 | socketId: this.socket.id 90 | }); 91 | } 92 | 93 | socketSignal(roomID, Sbody) { 94 | 95 | const body = Sbody; 96 | 97 | console.log(body); 98 | 99 | let id = this.state.socketId; 100 | let moment =

this.state.moment

; 101 | 102 | const message = { 103 | body, 104 | id, 105 | socketId: this.state.socketId, 106 | moment, 107 | roomID 108 | }; 109 | 110 | console.log("-----------") 111 | console.log(message) 112 | 113 | this.setState({ messages: [...this.state.messages, message] }); 114 | 115 | this.socket.emit("privateMessage", message); 116 | 117 | } 118 | 119 | getSocketChanelId(chatInfo) { 120 | 121 | this.setState({ 122 | socketChanelId: chatInfo.chatID 123 | }) 124 | 125 | let oldMessages = []; 126 | console.log("last hope: ", chatInfo.messages); 127 | chatInfo.messages.forEach((message) => { 128 | console.log("message: ", message) 129 | let oldMessage = { 130 | messageBody: message.message, 131 | userID: message.userID, 132 | roomID: chatInfo.chatID 133 | }; 134 | oldMessages.push(oldMessage); 135 | }); 136 | console.log("Old Messages: ", oldMessages) 137 | 138 | this.setState({ messages: oldMessages }); 139 | 140 | 141 | 142 | this.socket.emit('joinRoom', chatInfo.chatID); 143 | 144 | console.log("joining the roommmmm: ", chatInfo.chatID) 145 | 146 | 147 | //this.socketSignal(chatInfo.chatID, "aaaabbbbccc"); 148 | /*setTimeout(() => { 149 | this.socketSignal(chatInfo.chatID, "xxddff"); 150 | }, 100) 151 | */ 152 | /*this.socket.emit('joinRoom', { 153 | roomID, 154 | participants: [roomID.split("--")[0], roomID.split("--")[1]] 155 | }); 156 | 157 | 158 | this.socket.on('joinRoom', function(roomInfo) { 159 | console.log('joining room', roomInfo.roomID); 160 | 161 | let user = decode(localStorage.getItem("token")); 162 | 163 | if(user._id === roomInfo.friendID || user._id === roomInfo.userID) { 164 | this.socket.join(roomInfo.roomID); 165 | } 166 | }); 167 | */ 168 | 169 | 170 | } 171 | 172 | handleSubmit(event) { 173 | //this.socketSignal(this.state.socketChanelId, event.target.value) 174 | 175 | //this.socket.on('privateMessage', function(roomInfo) { 176 | //console.log('sending data to channel: ', this.state.socketChanelId); 177 | 178 | let user = decode(localStorage.getItem("token")); 179 | 180 | let messagePayload = { 181 | chatID: this.state.socketChanelId, 182 | userID: user._id, 183 | messageBody: event.target.value 184 | }; 185 | 186 | console.log("messagePayload: ", messagePayload) 187 | this.socket.emit("privateMessage", messagePayload); 188 | // }); 189 | 190 | this.setState({ messages: [...this.state.messages, messagePayload] }); 191 | 192 | event.target.value = ''; 193 | } 194 | 195 | render() { 196 | const { alignItems, direction, justify } = this.state; 197 | let avatarURL; 198 | // Getting the information from the loged user 199 | let user = decode(localStorage.getItem("token")); 200 | let currentAvatar = user.profile.avatarURL; 201 | if(localStorage.getItem("updatedUserData")) { 202 | 203 | let updatedUserData = JSON.parse(localStorage.getItem("updatedUserData")); 204 | 205 | let newAvatar = updatedUserData.user.avatarURL 206 | 207 | avatarURL = `${config.BASE_URL}images/avatars/${newAvatar}`; 208 | } else { 209 | avatarURL = currentAvatar !== "" ? `${config.BASE_URL}images/avatars/${currentAvatar}` : `${config.BASE_URL}images/avatar_placeholder.png`; 210 | } 211 | return ( 212 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | ); 254 | } 255 | } 256 | 257 | export default connect(mapStateToProps)(App); 258 | -------------------------------------------------------------------------------- /src/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/Modal.css: -------------------------------------------------------------------------------- 1 | 2 | .popup { 3 | position: fixed; 4 | top: 50%; 5 | left: 40%; 6 | margin-top: -20%; 7 | margin-left: -20%; 8 | width: 70%; 9 | height: 70%; 10 | border-radius: 10px; 11 | background: rgba(255, 255, 255, 0.8); 12 | text-align: center; 13 | font-size: 30px; 14 | color: black; 15 | } 16 | 17 | .shade { 18 | position: fixed; 19 | top: 0; 20 | right: 0; 21 | bottom: 0; 22 | left: 0; 23 | background: rgba(0, 0, 0, 0.4); 24 | } 25 | 26 | .content { 27 | padding: 50px; 28 | color: black; 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/actions/changeSetting.js: -------------------------------------------------------------------------------- 1 | 2 | export function changeSetting(url,formData) { 3 | return dispatch => { 4 | let token = localStorage.getItem("token"); 5 | fetch(url, { 6 | method: "POST", 7 | headers: { 8 | 'Authorization': `TOKEN ${token}` 9 | }, 10 | body: formData 11 | }) 12 | .then(response => response.json()) 13 | .then(response => { 14 | 15 | dispatch({ type: "CHANGE_USER_DONE", payload: response }); 16 | }) 17 | .catch(err => { 18 | dispatch({ type: "CHANGE_USER_WITHERROR", payload: err }); 19 | 20 | }); 21 | 22 | } 23 | } 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/actions/filterAction.js: -------------------------------------------------------------------------------- 1 | export const setFilter = (filter) => ({ 2 | type: 'CONTACT_FILTER', 3 | payload:filter, 4 | 5 | 6 | }) -------------------------------------------------------------------------------- /src/actions/login.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export function login(data) { 4 | return dispatch => { 5 | return axios.post('http://localhost:3001/auth/login',data) 6 | } 7 | } -------------------------------------------------------------------------------- /src/actions/setCurrentFriendAction.js: -------------------------------------------------------------------------------- 1 | export const setCurrentFriend = (user) => ({ 2 | type: 'SET_CURRENT_FRIEND', 3 | payload: user, 4 | }) -------------------------------------------------------------------------------- /src/actions/userActions.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export function fetchContactList() { 4 | return function(dispatch) { 5 | let token = localStorage.getItem("token"); 6 | axios 7 | .get("http://localhost:3001/user/get_friends/aaaa", { 8 | headers: { 9 | Authorization: `TOKEN ${token}` 10 | } 11 | }) // muss be passed the token object 12 | .then(response => { 13 | dispatch({ type: "FETCH_USER_DONE", payload: response.data }); 14 | }) 15 | .catch(err => { 16 | dispatch({ type: "FETCH_USER_WITHERROR", payload: err }); 17 | }); 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Addcontact.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import IconButton from "material-ui/IconButton"; 3 | //import Input from "material-ui/Input"; 4 | import SearchList from "../components/searchList"; 5 | import TextField from 'material-ui/TextField'; 6 | //import { CircularProgress } from 'material-ui/Progress'; 7 | //import { FormControl, FormHelperText } from 'material-ui/Form'; 8 | class AddContact extends Component { 9 | constructor() { 10 | super(); 11 | this.state = { 12 | users: '', 13 | error :'' 14 | }; 15 | this.btnSearchClicked = this.btnSearchClicked.bind(this); 16 | } 17 | 18 | btnSearchClicked() { 19 | let token = localStorage.getItem("token"); 20 | let searchValue = this.txtSearchInput.value; 21 | if (searchValue !== '') { 22 | fetch(`http://localhost:3001/user/contacts/search/${searchValue}`,{headers: {Authorization: `TOKEN ${token}` }} ) 23 | .then(res => res.json()) 24 | .then((data) => { 25 | 26 | this.setState({ 27 | users: data, 28 | error:'' 29 | 30 | }); 31 | }) 32 | .catch(err => console.log(err)); 33 | } else { 34 | this.setState({error:'please insert a name' }); 35 | } 36 | } 37 | render() { 38 | //let error = this.state.error ?
{this.state.error}
:
39 | return ( 40 |
41 | { 43 | if(e.keyCode === 13){ 44 | this.btnSearchClicked() 45 | } 46 | } 47 | } 48 | inputRef={thisInput => { 49 | this.txtSearchInput = thisInput; 50 | }} 51 | style={{width:"80%",marginLeft:35}} 52 | label="Search For a Friends" 53 | helperText={this.state.error} 54 | /> 55 | 56 | 66 | search 67 | 68 | 69 | 70 |
71 | ); 72 | } 73 | } 74 | 75 | export default AddContact; -------------------------------------------------------------------------------- /src/components/ImageCropper.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import AvatarImageCropper from 'react-avatar-image-cropper'; 3 | //import config from "../config/config.js"; 4 | 5 | class ImageCropper extends Component { 6 | 7 | 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | imgSrc: "", 12 | 13 | } 14 | 15 | } 16 | apply = (file) => { 17 | 18 | localStorage.setItem("arguments", JSON.stringify(arguments)); 19 | let objectURL = URL.createObjectURL(file); 20 | 21 | this.setState({ 22 | imgSrc: objectURL, 23 | }) 24 | console.log(this.state); 25 | this.props.saveCropedImage(file) 26 | } 27 | 28 | 29 | 30 | 31 | render() { 32 | return ( 33 |
34 | 35 | 36 |
37 | 38 | ); 39 | } 40 | } 41 | 42 | export default ImageCropper; -------------------------------------------------------------------------------- /src/components/Modal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ReactPageClick} from 'react-page-click'; 3 | import'../Modal.css'; 4 | import AddFriends from '../container/AddFriends'; 5 | const Modal = ({onClose, notifyOnTouchEnd, ...rest}) => ( 6 |
7 |
8 | 9 |
10 | 11 |
12 |
13 | 14 |
15 | ); 16 | Modal.defaultProps = { 17 | notifyOnTouchEnd: undefined 18 | }; 19 | export default Modal; -------------------------------------------------------------------------------- /src/components/ProfileSettings.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react' 3 | import { withStyles } from 'material-ui/styles'; 4 | import Button from 'material-ui/Button'; 5 | import { DatePicker } from "material-ui-pickers"; 6 | import moment from "moment"; 7 | import TextField from "material-ui/TextField"; 8 | import config from "../config/config.js"; 9 | import Typography from "material-ui/Typography/Typography"; 10 | import decode from "jwt-decode"; 11 | import uuidv1 from "uuid/v1"; 12 | import Grid from "material-ui/Grid"; 13 | import { Redirect } from 'react-router'; 14 | //import { changeSetting } from "../actions/changeSetting"; 15 | //import compose from 'recompose/compose'; 16 | //import { connect } from "react-redux"; 17 | import ImageCropper from './ImageCropper'; 18 | 19 | 20 | const styles = theme => ({ 21 | textField: { 22 | width: 300, 23 | float: 'left', 24 | marginBottom: 10, 25 | }, 26 | emailAddress: { 27 | width: 300, 28 | float: 'left', 29 | marginBottom: 30, 30 | }, 31 | 32 | pickerContainer: { 33 | width: 200, 34 | }, 35 | 36 | datePicker: { 37 | width: 300, 38 | } 39 | 40 | 41 | }); 42 | 43 | 44 | class UserPictureAndState extends Component { 45 | constructor() { 46 | super(); 47 | this.state = { 48 | value: '', 49 | submittedImgValue: '', 50 | newUser: { 51 | firstName: '', 52 | lastName: '', 53 | emailAddress: '', 54 | dateOfBirth: '', 55 | gender: '' 56 | }, 57 | disabled: true 58 | 59 | } 60 | 61 | this.handleInputChange = this.handleInputChange.bind(this); 62 | this.handleimageChange = this.handleimageChange.bind(this); 63 | } 64 | componentWillMount() { 65 | let oldCredentials = decode(localStorage.getItem('token')); 66 | let user = JSON.parse(localStorage.getItem('updatedUserData')); 67 | console.log('oldCredentials', oldCredentials); 68 | console.log('user', user); 69 | 70 | if (user) { 71 | this.setState({ 72 | userCurrentData: { 73 | firstName: user.user.profile.firstName, 74 | lastName: user.user.profile.lastName, 75 | emailAddress: user.user.emailAddress, 76 | dateOfBirth: user.user.dateOfBirth, 77 | gender: user.user.profile.gender, 78 | avatarURL: user.user.profile.avatarURL 79 | 80 | } 81 | }); 82 | } else { 83 | this.setState({ 84 | userCurrentData: { 85 | firstName: oldCredentials.profile.firstName, 86 | lastName: oldCredentials.profile.lastName, 87 | emailAddress: oldCredentials.emailAddress, 88 | dateOfBirth: oldCredentials.dateOfBirth, 89 | gender: oldCredentials.profile.gender, 90 | avatarURL: oldCredentials.profile.avatarURL 91 | } 92 | 93 | }); 94 | } 95 | } 96 | 97 | handleDataChange = date => { 98 | let checkedDate = date.format().substring(0, 10); 99 | this.setState({ 100 | newUser: { ...this.state.newUser, dateOfBirth: checkedDate } 101 | }); 102 | }; 103 | 104 | 105 | handleimageChange(e) { 106 | e.preventDefault(); 107 | let reader = new FileReader(); 108 | let file = e.target.files[0]; 109 | 110 | reader.onloadend = () => { 111 | this.setState({ 112 | newUser: { 113 | ...this.state.newUser, 114 | avatarURL: reader.result 115 | }, 116 | disabled: false 117 | 118 | }); 119 | 120 | } 121 | 122 | reader.readAsDataURL(file); 123 | 124 | } 125 | 126 | 127 | 128 | handleRadioChange = (e, value) => { 129 | this.setState({ 130 | newUser: { 131 | ...this.state.newUser, 132 | gender: value 133 | }, 134 | value: value 135 | }); 136 | } 137 | 138 | handleInputChange(event) { 139 | const target = event.target; 140 | const value = target.value; 141 | const name = target.name; 142 | 143 | this.setState({ 144 | newUser: { 145 | ...this.state.newUser, 146 | [name]: value 147 | } 148 | }); 149 | } 150 | 151 | submitForm() { 152 | let changedEmail = false; 153 | let formData = new FormData(this.formSettings.target); 154 | 155 | 156 | 157 | 158 | // Only will be updated what the user changes 159 | //this.state.submittedImgValue === '' ? formData.append('file', this.state.userCurrentData.avatarURL) : formData.append('file', this.state.submittedImgValue); 160 | this.state.newUser.dateOfBirth === '' ? formData.append('dateOfBirth', this.state.userCurrentData.dateOfBirth) : formData.append('dateOfBirth', this.state.newUser.dateOfBirth); 161 | this.state.newUser.firstName === '' ? formData.append('firstName', this.state.userCurrentData.firstName) : formData.append('firstName', this.state.newUser.firstName); 162 | this.state.newUser.lastName === '' ? formData.append('lastName', this.state.userCurrentData.lastName) : formData.append('lastName', this.state.newUser.lastName); 163 | 164 | 165 | // if the email is empty it will no send nothing 166 | if (this.state.newUser.emailAddress !== '') { 167 | formData.append('emailAddress', this.state.newUser.emailAddress); 168 | changedEmail = true; 169 | } 170 | 171 | if (this.state.imageChanged) { 172 | formData.append('file', this.state.submittedImgValue) 173 | } 174 | 175 | 176 | for (var pair of formData.entries()) { 177 | console.log(pair[0] + ', ' + pair[1]); 178 | } 179 | 180 | 181 | let url = `${config.BASE_URL}user/profile_edit/${uuidv1()}`; 182 | let token = localStorage.getItem("token"); 183 | 184 | 185 | fetch(url, { 186 | method: "POST", 187 | headers: { 188 | 'Authorization': `TOKEN ${token}` 189 | }, 190 | body: formData 191 | }) 192 | .then(res => res.json()) 193 | .then(data => { 194 | console.log(data); 195 | if (data) { 196 | localStorage.setItem('updatedUserData', JSON.stringify(data)) 197 | 198 | console.log(changedEmail); 199 | // 200 | if (changedEmail) { 201 | let url = `${config.BASE_URL}auth/logout`; 202 | fetch(url, { 203 | method: "Get", 204 | headers: { 205 | Authorization: `TOKEN ${token}`, 206 | } 207 | }) 208 | .then(res => res.json()) 209 | .then(data => { 210 | localStorage.clear(); 211 | this.setState({ redirect: true }) 212 | }) 213 | .catch(err => console.log(err)); 214 | } 215 | setTimeout(() => { 216 | window.location.reload(); 217 | }, 2000) 218 | 219 | } 220 | 221 | }) 222 | .catch(err => console.log(err)); 223 | 224 | 225 | } 226 | 227 | saveCropedImage(file) { 228 | //let objectURL = URL.createObjectURL(file); 229 | this.setState({ imageChanged: true, submittedImgValue: file }) 230 | 231 | } 232 | 233 | handleSubmit(e) { 234 | 235 | e.preventDefault(); 236 | 237 | }; 238 | 239 | render() { 240 | const { classes } = this.props; 241 | if (this.state.redirect) { 242 | return 243 | } else { 244 | return ( 245 | 246 | 247 |
this.formSettings = form} 253 | > 254 | 261 | 262 | 270 | 271 | 278 | 279 |
280 | 285 | Date of Birth 286 | 287 | moment(date).format("Do MMMM YYYY")} 291 | onChange={this.handleDataChange} 292 | className={classes.datePicker} 293 | /> 294 |
295 | 296 | 297 | 298 | 299 | 302 | 303 | 304 |
305 |
306 | ) 307 | } 308 | } 309 | } 310 | 311 | 312 | export default withStyles(styles)(UserPictureAndState) 313 | 314 | 315 | -------------------------------------------------------------------------------- /src/components/SingIn.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withStyles } from "material-ui/styles"; 3 | import TextField from "material-ui/TextField"; 4 | import Button from "material-ui/Button"; 5 | import { Link } from "react-router-dom"; 6 | import decode from "jwt-decode"; 7 | import { login } from "../actions/login"; 8 | import { connect } from "react-redux"; 9 | import config from "../config/config.js"; 10 | 11 | const styles = theme => ({ 12 | formWrapper: { 13 | display: "flex", 14 | flexDirection: "column", 15 | position: "relative", 16 | top: "2rem" 17 | }, 18 | registerNow: { 19 | textDecoration: "none", 20 | color: "#FF6A6F" 21 | }, 22 | forgotPassword: { 23 | textDecoration: "none", 24 | color: "#777777" 25 | } 26 | }); 27 | 28 | class SignIn extends Component { 29 | constructor() { 30 | super(); 31 | this.state = { 32 | checked: false, 33 | email: "", 34 | password: "", 35 | loginError:'' 36 | }; 37 | } 38 | 39 | handleChange = event => { 40 | this.setState({ 41 | checked: event.target.checked 42 | }); 43 | }; 44 | 45 | handleEmailChange = event => { 46 | this.setState({ email: event.target.value }); 47 | }; 48 | 49 | handlePasswordChange = event => { 50 | this.setState({ password: event.target.value }); 51 | }; 52 | 53 | handleSubmit(e) { 54 | e.preventDefault(); 55 | localStorage.removeItem("updatedUserData"); 56 | 57 | let url = `${config.BASE_URL}auth/login`; 58 | const formData = { 59 | checked: this.state.checked, 60 | username: this.state.email, 61 | password: this.state.password 62 | }; 63 | console.log(formData); 64 | 65 | if (formData) { 66 | const searchParams = Object.keys(formData) 67 | .map(key => { 68 | return ( 69 | encodeURIComponent(key) + "=" + encodeURIComponent(formData[key]) 70 | ); 71 | }) 72 | .join("&"); 73 | 74 | fetch(url, { 75 | method: "POST", 76 | headers: { 77 | "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" 78 | }, 79 | body: searchParams 80 | }) 81 | .then(res => res.json()) 82 | .then(data => { 83 | let user = decode(data.token); 84 | console.log(user); 85 | localStorage.setItem("token", data.token); 86 | this.props.history.push("/auth"); 87 | }) 88 | .catch(err => { 89 | console.log(err) 90 | this.setState({ 91 | loginError:err.message 92 | }) 93 | 94 | }); 95 | } else { 96 | console.log({ Error: "Fields are required" }); //Handle errors here... 97 | } 98 | /* let url = 'localhost:3001/login'; 99 | let email = this.state.email; 100 | let password = this.state.password; 101 | loginRequest(url,email,password) 102 | 103 | 104 | this.props.login(this.state).then( 105 | (res) => this.context.router.push('/auth'), 106 | )*/ 107 | } 108 | render() { 109 | const { classes } = this.props; 110 | return ( 111 |
112 |
113 |
114 |
115 | logo 116 |
117 |
123 | 129 | 136 | 144 | 145 |
146 | 147 |

Register

148 | 149 | 150 |

Forgot Password?

151 | 152 |
153 | {this.state.loginError} 154 |
155 | 156 |
157 |
158 | ); 159 | } 160 | } 161 | 162 | export default withStyles(styles)(connect(null, { login })(SignIn)); 163 | -------------------------------------------------------------------------------- /src/components/SingUp.js: -------------------------------------------------------------------------------- 1 | import React, {Component}from 'react'; 2 | import UserData from './UserData'; 3 | // import SocialMedia from './SocialMedia'; 4 | 5 | export default class SingUp extends Component { 6 | render() { 7 | return ( 8 | 9 | ) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/SocialMedia.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withStyles } from "material-ui/styles"; 3 | import Tooltip from "material-ui/Tooltip"; 4 | import classNames from "classnames"; 5 | import Button from "material-ui/Button"; 6 | 7 | const styles = theme => ({ 8 | social: { 9 | width: "20px" 10 | } 11 | }); 12 | 13 | class SocialMedia extends Component { 14 | render() { 15 | const { classes } = this.props; 16 | return ( 17 |
18 |
19 | 20 | 27 | 28 | 29 | 35 | 36 | 37 | 43 | 44 |
45 |
46 | ); 47 | } 48 | } 49 | 50 | export default withStyles(styles)(SocialMedia); 51 | -------------------------------------------------------------------------------- /src/components/UserAvatar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withStyles } from "material-ui/styles"; 3 | import IconButton from "material-ui/IconButton"; 4 | import Avatar from "./skypeAvatar"; 5 | import ProfileSettings from "./ProfileSettings"; 6 | import FormDialog from "./dialog"; 7 | import config from "../config/config.js"; 8 | import { Redirect } from 'react-router' 9 | 10 | 11 | const styles = { 12 | root: { 13 | flexGrow: 1, 14 | }, 15 | row: { 16 | display: "flex", 17 | justifyContent: "center" 18 | }, 19 | avatar: { 20 | position: "relative", 21 | padding: "1rem", 22 | }, 23 | img: { 24 | width: "100%", 25 | } 26 | }; 27 | 28 | class UserAvatar extends Component { 29 | state = { 30 | open: false, 31 | redirect : false 32 | }; 33 | 34 | handleClickOpen = () => { 35 | this.setState({ open: true }); 36 | }; 37 | 38 | handleClose = () => { 39 | this.setState({ open: false }); 40 | }; 41 | 42 | logOut = () => { 43 | let token = localStorage.getItem("token"); 44 | let url = `${config.BASE_URL}auth/logout`; 45 | console.log('Asdfd'); 46 | 47 | fetch(url, { 48 | method: "Get", 49 | headers: { 50 | Authorization: `TOKEN ${token}`, 51 | } 52 | }) 53 | .then(res => res.json()) 54 | .then(data => { 55 | localStorage.removeItem('token'); 56 | this.setState({ redirect : true }) 57 | }) 58 | .catch(err => console.log(err)); 59 | 60 | } 61 | 62 | 63 | render() { 64 | const { classes } = this.props; 65 | let avatarURL; 66 | 67 | 68 | if(this.state.redirect){ 69 | return 70 | }else{ 71 | return ( 72 |
73 |
74 | 75 | settings 76 | 77 | } 81 | 82 | /> 83 | 84 | 85 | exit_to_app 86 | 87 |
88 |
89 | 90 |
91 |
92 | ); 93 | } 94 | 95 | } 96 | } 97 | 98 | export default withStyles(styles)(UserAvatar); 99 | -------------------------------------------------------------------------------- /src/components/UserData.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withStyles } from "material-ui/styles"; 3 | import TextField from "material-ui/TextField"; 4 | import Button from "material-ui/Button"; 5 | import Radio, { RadioGroup } from "material-ui/Radio"; 6 | import { 7 | FormLabel, 8 | FormControl, 9 | FormControlLabel, 10 | FormHelperText 11 | } from "material-ui/Form"; 12 | import { DatePicker } from "material-ui-pickers"; 13 | import Typography from "material-ui/Typography/Typography"; 14 | import moment from "moment"; 15 | import config from '../config/config.js'; 16 | import { Link } from "react-router-dom"; 17 | 18 | const styles = theme => ({ 19 | container: { 20 | width: "50%", 21 | margin: "7% auto", 22 | textAlign: "center" 23 | }, 24 | formWrapper: { 25 | display: "flex", 26 | flexDirection: "column", 27 | width: "100%", 28 | margin: "0 auto", 29 | alignItems: "stretch", 30 | textAlign: "center" 31 | }, 32 | Paper: { 33 | width: "80%", 34 | margin: "8% auto" 35 | }, 36 | textField: { 37 | marginBottom: 20, 38 | width: "100%" 39 | }, 40 | 41 | p: { 42 | color: "#777777" 43 | }, 44 | 45 | RadioGroupWrapper: { 46 | display: "flex", 47 | marginBottom: 10, 48 | position: "relative", 49 | left: "-15px" 50 | }, 51 | 52 | h3: { 53 | color: "#000000" 54 | }, 55 | Typography: { 56 | fontSize: "20px", 57 | marginTop: 10 58 | }, 59 | DatePicker: { 60 | float: "left", 61 | marginBottom: 30, 62 | width: 250 63 | }, 64 | RadioGroup: { 65 | display: "inline-block", 66 | textAlign: "left", 67 | marginBottom: 20 68 | }, 69 | FormControl: { 70 | textAlign: "left" 71 | }, 72 | 73 | button: { 74 | marginBottom: 30, 75 | width: 200 76 | }, 77 | text: { 78 | color: "black" 79 | } 80 | }); 81 | 82 | class UserData extends Component { 83 | constructor() { 84 | super(); 85 | this.state = { 86 | formTitle: "Sing Up", 87 | buttonTitle: "Sing Up", 88 | settingUserData: { 89 | firstName: "john", 90 | lastName: "Doe", 91 | email: "johnDoe@gmail.com", 92 | password: "******", 93 | newPassword: "newPasssword" 94 | }, 95 | newUser: { 96 | firstName: "", 97 | lastName: "", 98 | email: "", 99 | password: "", 100 | newPassword: "", 101 | dateOfBirth: "", 102 | gender: "" 103 | }, 104 | generalerror: " ", 105 | errorMessageFirstName: " ", 106 | errorMessagelastName: " ", 107 | errorMessagePassword: " ", 108 | errorMessageEmail: "", 109 | errorMessagedateOfBirth: "", 110 | errorMessagerepeatPassword: "", 111 | errorMessageGender: "" 112 | // firstNameRequired:false, 113 | // lastNameRequired:false, 114 | // emailRequired:false, 115 | // emailValid: false, 116 | // emailTaken:false, 117 | // passwordRequired:false, 118 | // repasswordRequired:false, 119 | // dateOfBirthRequird:false, 120 | // genderRequired:false 121 | }; 122 | this.handleInputChange = this.handleInputChange.bind(this); 123 | this.isRequierd = this.isRequierd.bind(this); 124 | this.isPasswordMatch = this.isPasswordMatch.bind(this); 125 | } 126 | 127 | handleChange = (event, value) => { 128 | this.setState({ value }); 129 | }; 130 | 131 | handleDataChange = date => { 132 | let checketDate = date.format().substring(0, 10); 133 | console.log(date.format()); 134 | 135 | this.setState({ 136 | newUser: { ...this.state.newUser, dateOfBirth: checketDate } 137 | }); 138 | }; 139 | 140 | isRequierd(formData) { 141 | let bool = true; 142 | this.setState({ 143 | errorMessageFirstName: " ", 144 | errorMessagelastName: " ", 145 | errorMessagePassword: " ", 146 | errorMessageEmail: "", 147 | errorMessagedateOfBirth: "", 148 | errorMessageGender: "", 149 | errorMessagerepeatPassword: "", 150 | }); 151 | 152 | if (this.state.newUser === undefined) { 153 | console.log("form"); 154 | this.setState({ 155 | errorMessage: "please fill the Requireds filed" 156 | }); 157 | bool = false; 158 | } 159 | //|| || this.state.newUser.dateOfBirth || this.state.newUser.password || || this.state.newUser.dateOfBirth 160 | if (this.state.newUser.firstName === "") { 161 | console.log("form field"); 162 | this.setState({ 163 | errorMessageFirstName: "please fill first name" 164 | }); 165 | bool = false; 166 | } 167 | 168 | if (this.state.newUser.lastName === "") { 169 | console.log("form last name"); 170 | this.setState({ 171 | errorMessagelastName: "please fill last name" 172 | }); 173 | bool = false; 174 | } 175 | if (this.state.newUser.password === "") { 176 | console.log("form passwoer"); 177 | this.setState({ 178 | errorMessagePassword: "please insert password" 179 | }); 180 | bool = false; 181 | } 182 | if (this.state.newUser.email === "") { 183 | console.log("form email"); 184 | this.setState({ 185 | errorMessageEmail: "please insert your email address" 186 | }); 187 | bool = false; 188 | } 189 | if (this.state.newUser.newPassword === "") { 190 | console.log("form repeat password"); 191 | this.setState({ 192 | errorMessagerepeatPassword: "please repeat your password " 193 | }); 194 | bool = false; 195 | } 196 | if (this.state.newUser.dateOfBirth === "") { 197 | console.log("form dateOfBirth"); 198 | this.setState({ 199 | errorMessagedateOfBirth: "please choose your dateOfBirth " 200 | }); 201 | bool = false; 202 | } 203 | if (this.state.newUser.gender === "") { 204 | console.log("form gender"); 205 | this.setState({ 206 | errorMessageGender: "please choose your gender " 207 | }); 208 | bool = false; 209 | } 210 | return bool; 211 | } 212 | 213 | handleChange = (e, value) => { 214 | console.log(value); 215 | this.setState({ 216 | newUser: { 217 | ...this.state.newUser, 218 | gender: value 219 | } 220 | }); 221 | }; 222 | handleInputChange(event) { 223 | const target = event.target; 224 | const value = target.value; 225 | const name = target.name; 226 | 227 | this.setState({ 228 | newUser: { 229 | ...this.state.newUser, 230 | [name]: value 231 | } 232 | }); 233 | } 234 | isPasswordMatch = (formData) => { 235 | let password = formData.password; 236 | let repeatPassword = formData.newPassword; 237 | if(password === repeatPassword) { 238 | return true; 239 | } else { 240 | this.setState({ 241 | errorMessagerepeatPassword:'password is not match' 242 | }) 243 | } 244 | } 245 | handleSubmit(e) { 246 | e.preventDefault(); 247 | console.log(this.state.newUser); 248 | let formData = this.state.newUser; 249 | let url = "http://localhost:3001/auth/signup"; 250 | 251 | 252 | 253 | if (this.isRequierd(formData)&& this.isPasswordMatch(formData)) { 254 | 255 | const searchParams = Object.keys(formData) 256 | .map(key => { 257 | return ( 258 | encodeURIComponent(key) + "=" + encodeURIComponent(formData[key]) 259 | ); 260 | }) 261 | .join("&"); 262 | console.log(searchParams); 263 | fetch(url, { 264 | method: "POST", 265 | headers: { 266 | "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" 267 | }, 268 | body: searchParams 269 | }) 270 | .then(res => res.json()) 271 | .then(data => { 272 | console.log(data); 273 | if (data) { 274 | this.props.history.push("/"); 275 | } else { 276 | this.setState({ 277 | generalerror: data.message 278 | }); 279 | 280 | } 281 | }) 282 | .catch(err => console.log(err)); 283 | } else { 284 | console.log("registration error"); 285 | //Handle errors here... 286 | } 287 | } 288 | 289 | componentWillMount() { 290 | if (this.props.place === "setting") { 291 | this.setState({ 292 | formTitle: "setting", 293 | buttonTitle: "save" 294 | }); 295 | } else { 296 | this.setState({ 297 | settingUserData: { 298 | firstName: "FirstName", 299 | lastName: "LastName", 300 | email: "Email Adress", 301 | password: "password", 302 | newPassword: "repeat password" 303 | } 304 | }); 305 | } 306 | } 307 | 308 | render() { 309 | const { classes } = this.props; 310 | 311 | return ( 312 |
313 |
314 |
315 |
316 | avatar 317 |
318 |

Sign Up

319 |
325 | 333 | 334 | 342 | 343 | 351 | 360 | 369 |
370 | 376 | Date of Birth 377 | 378 | moment(date).format("Do MMMM YYYY")} 382 | onChange={this.handleDataChange} 383 | className={classes.DatePicker} 384 | helperText={this.state.errorMessagedateOfBirth} 385 | /> 386 |
387 | 388 | Gender 389 | 395 | } 398 | label="Male" 399 | /> 400 | } 403 | label="Female" 404 | /> 405 | } 408 | label="Other" 409 | /> 410 | 411 | {this.state.errorMessageGender} 412 | 413 | 416 | 417 |
418 | 419 |

Login

420 | 421 | 422 |
423 | 424 |
425 |

{this.state.generalerror}

426 |
427 | 428 |
429 |
430 |
431 | ); 432 | } 433 | } 434 | 435 | export default withStyles(styles)(UserData); 436 | -------------------------------------------------------------------------------- /src/components/UserPictureAndState.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { withStyles } from 'material-ui/styles'; 3 | import Button from 'material-ui/Button'; 4 | import Input, { InputLabel } from 'material-ui/Input'; 5 | import { FormControl } from 'material-ui/Form'; 6 | const styles = { 7 | row: { 8 | display: 'flex', 9 | flexDirection: 'column', 10 | position: "relative", 11 | top: "50px", 12 | right: "15%" 13 | }, 14 | avatar: { 15 | position: 'relative', 16 | bottom: 0, 17 | padding: '10px', 18 | order:-1, 19 | }, 20 | bigAvatar: { 21 | width: 100, 22 | height: 100, 23 | }, 24 | button: { 25 | marginTop: 15, 26 | marginBottom:15, 27 | backgroundColor:"ffffff", 28 | }, 29 | input: { 30 | display: 'none', 31 | }, 32 | formControl: { 33 | width:250, 34 | }, 35 | img:{ 36 | height:100, 37 | width:150, 38 | } 39 | }; 40 | 41 | 42 | class UserPictureAndState extends Component { 43 | 44 | handleChange = event => { 45 | console.log(event.target.value) 46 | }; 47 | 48 | render() { 49 | const { classes } = this.props; 50 | return ( 51 |
52 | {'profile'}/ 53 | 58 | 59 | Status Message: 60 | 66 | 67 |
68 | ) 69 | } 70 | } 71 | export default withStyles(styles)(UserPictureAndState); -------------------------------------------------------------------------------- /src/components/WellcomePage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link } from "react-router-dom"; 3 | import './wellcome.css' 4 | 5 | export default class WellcomePage extends Component { 6 | render() { 7 | return ( 8 |
9 |
10 |

wellcome your registered

11 | Sign in 12 |
13 |
14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/addFriendConfirmation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from 'material-ui/Button'; 3 | import TextField from 'material-ui/TextField'; 4 | import Dialog, { 5 | DialogActions, 6 | DialogContent, 7 | DialogContentText, 8 | DialogTitle, 9 | } from 'material-ui/Dialog'; 10 | 11 | class FriendConfirmation extends React.Component { 12 | state = { 13 | open: false, 14 | }; 15 | 16 | handleClose = () => { 17 | this.setState({ open: false }); 18 | }; 19 | 20 | render() { 21 | return ( 22 |
23 | 29 | Subscribe 30 | 31 | 32 | You added a new Friend 33 | 34 | 42 | 43 | 44 | 47 | 48 | 49 | 50 |
51 | ); 52 | } 53 | } 54 | 55 | export default FriendConfirmation; -------------------------------------------------------------------------------- /src/components/contactDetail.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Button from 'material-ui/Button'; 3 | import Avatar from './skypeAvatar'; 4 | import FormDialog from './dialog' 5 | import config from "../config/config"; 6 | import {connect} from "react-redux"; 7 | import VedioCall from "./vedioCall"; 8 | 9 | 10 | class ContactDetail extends Component { 11 | state = { 12 | open: false, 13 | }; 14 | 15 | handleClickOpen = () => { 16 | this.setState({ open: true }); 17 | }; 18 | 19 | handleClose = () => { 20 | this.setState({ open: false }); 21 | }; 22 | render() { 23 | let friendDetail = null; 24 | console.log(this.props.setCurrentFriend.avatarURL); 25 | let avatarURL = this.props.setCurrentFriend.avatarURL !== "" ? `${config.BASE_URL}/images/avatars/${this.props.setCurrentFriend.avatarURL}` : `${config.BASE_URL}/images/avatar_placeholder.png`; 26 | 27 | if(this.props.setCurrentFriend.avatarURL !== undefined) { 28 | friendDetail =
29 |
30 | 31 |
32 | 33 | 36 | 43 | 46 | } 51 | fullScreen={true} 52 | /> 53 | 54 |
55 |
56 |
57 | } 58 | return ( 59 |
60 |
61 |
62 | logo 63 |
64 | {friendDetail} 65 | 66 |
67 | 68 |
69 | ); 70 | } 71 | } 72 | 73 | 74 | const mapStateToProps = (state) => { 75 | return { 76 | setCurrentFriend: state.setCurrentFriendReducer, 77 | }; 78 | }; 79 | 80 | export default connect(mapStateToProps)(ContactDetail); 81 | -------------------------------------------------------------------------------- /src/components/contactList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withStyles } from "material-ui/styles"; 3 | import List, { ListItem, ListItemText } from "material-ui/List"; 4 | import SkypeAvatar from "./skypeAvatar"; 5 | //import SearchList from "../components/searchList"; 6 | import decode from 'jwt-decode'; 7 | import config from '../config/config'; 8 | import { setCurrentFriend } from "../actions/setCurrentFriendAction"; 9 | import compose from 'recompose/compose'; 10 | import { connect } from "react-redux"; 11 | 12 | const styles = theme => ({ 13 | root: { 14 | width: "100%" 15 | } 16 | }); 17 | 18 | class ContactList extends Component { 19 | 20 | constructor() { 21 | super(); 22 | this.state = { 23 | socketChanelId: '', 24 | selectedIndex: null, 25 | } 26 | } 27 | 28 | socketChanel(friend, e, i){ 29 | 30 | 31 | this.setState({ selectedIndex: i }); 32 | 33 | let user = decode(localStorage.getItem('token')) ; 34 | //let userId = user._id; 35 | //let socketChanelId = userId+"--"+friend.userId; 36 | 37 | //AJAX call 38 | let url = `${config.BASE_URL}user/message/get`; 39 | 40 | const postData = { 41 | userID: user._id, 42 | friendID: friend.userId 43 | }; 44 | 45 | if (postData) { 46 | const parsedFields = Object.keys(postData) 47 | .map(key => { 48 | return ( 49 | encodeURIComponent(key) + "=" + encodeURIComponent(postData[key]) 50 | ); 51 | }) 52 | .join("&"); 53 | 54 | fetch(url, { 55 | method: "POST", 56 | headers: { 57 | 'Authorization': `TOKEN ${localStorage.getItem('token')}`, 58 | "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" 59 | }, 60 | body: parsedFields 61 | }) 62 | .then(res => res.json()) 63 | .then((data) => { 64 | console.log("ChatID Response: ", data); 65 | let chatInfo; 66 | 67 | if(data.messages !== undefined) { //old messages exists, should load them 68 | chatInfo = { 69 | chatID: data.chat._id, 70 | messages: data.messages 71 | }; 72 | } else { //no old messages to load 73 | chatInfo = { 74 | chatID: data._id, 75 | messages: [] 76 | }; 77 | } 78 | 79 | console.log(chatInfo); 80 | 81 | this.setState({ 82 | socketChanelId: chatInfo.chatID 83 | }) 84 | 85 | 86 | this.props.getId(chatInfo) 87 | }) 88 | .catch(err => console.log(err)); 89 | } else { 90 | console.log({ Error: "Fields are required" }); //Handle errors here... 91 | } 92 | 93 | 94 | 95 | this.props.setCurrentFriend(friend); 96 | 97 | // console.log(friend); 98 | 99 | 100 | 101 | //console.log(socketChanelId) 102 | 103 | 104 | 105 | } 106 | 107 | render() { 108 | const { classes } = this.props; 109 | 110 | const listItems = this.props.friendsList.map((item, index) => { 111 | 112 | 113 | let avatarURL = item.avatarURL !== '' ? `${config.BASE_URL}images/avatars/${item.avatarURL}` : `${config.BASE_URL}images/avatar_placeholder.png`; 114 | 115 | let highlightedFriend = (index === this.state.selectedIndex) ? "#01062e" : ""; 116 | 117 | return ( 118 | this.socketChanel(item, event, index)} > 122 | 126 | 127 | 128 | ); 129 | }); 130 | 131 | return ( 132 |
133 | {listItems} 134 |
135 | ); 136 | } 137 | } 138 | 139 | const mapDispatchToProps = (dispatch) => { 140 | return { 141 | setCurrentFriend: (user) => { 142 | dispatch(setCurrentFriend(user)); 143 | } 144 | }; 145 | }; 146 | 147 | 148 | export default compose( 149 | withStyles(styles), 150 | connect(null, mapDispatchToProps) 151 | )(ContactList); -------------------------------------------------------------------------------- /src/components/dialog.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from 'material-ui/Button'; 3 | import { withStyles } from 'material-ui/styles'; 4 | import Dialog, { 5 | DialogActions, 6 | DialogContent, 7 | } from 'material-ui/Dialog'; 8 | 9 | const styles = { 10 | closeDialog:{ 11 | color:'#00010b', 12 | position:'absolute', 13 | right:0, 14 | top:0, 15 | }, 16 | comp:{ 17 | position:'relative', 18 | display:'flex', 19 | width:'50%' 20 | 21 | }, 22 | background:{ 23 | backgroundColor:'rgba(0, 0, 0,0.5)', 24 | 25 | } 26 | } 27 | 28 | class FormDialog extends React.Component { 29 | 30 | 31 | render() { 32 | const { classes } = this.props 33 | 34 | return ( 35 |
36 | 43 | 44 | 45 | {this.props.compo} 46 | 47 | 48 | 52 | 53 | 54 |
55 | ); 56 | } 57 | } 58 | export default withStyles(styles) (FormDialog) -------------------------------------------------------------------------------- /src/components/emoji.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import EmojiPicker from 'emoji-picker-react'; 3 | import {emojify} from 'react-emojione'; 4 | 5 | 6 | class Emoji extends Component { 7 | constructor(props){ 8 | super(props); 9 | 10 | this.logEmoji = this.logEmoji.bind(this); 11 | this.insertEmoji = this.insertEmoji.bind(this); 12 | this.addSpecialChar = this.addSpecialChar.bind(this); 13 | } 14 | 15 | logEmoji (data,emoji) { 16 | this.insertEmoji(":"+emoji.name+":") 17 | } 18 | 19 | insertEmoji(emoji) { 20 | var input = this.props.txtMessage; 21 | console.log(input); 22 | if (input === undefined) { return; } 23 | var scrollPos = input.scrollTop; 24 | var pos = 0; 25 | var range; 26 | var browser = ((input.selectionStart || input.selectionStart === "0") ? 27 | "ff" : (document.selection ? "ie" : false ) ); 28 | if (browser === "ie") { 29 | input.focus(); 30 | range = document.selection.createRange(); 31 | range.moveStart ("character", -input.value.length); 32 | pos = range.text.length; 33 | } 34 | else if (browser === "ff") { pos = input.selectionStart }; 35 | 36 | var front = (input.value).substring(0, pos); 37 | var back = (input.value).substring(pos, input.value.length); 38 | input.value = front+emojify(emoji, {output:"unicode"})+back; 39 | pos = Number(pos) + 2 40 | console.log("len: "+emoji.length); 41 | console.log(emoji); 42 | if (browser === "ie") { 43 | input.focus(); 44 | range = document.selection.createRange(); 45 | range.moveStart ("character", -input.value.length); 46 | range.moveStart ("character", pos); 47 | range.moveEnd ("character", 0); 48 | range.select(); 49 | } 50 | else if (browser === "ff") { 51 | input.selectionStart = pos; 52 | input.selectionEnd = pos; 53 | input.focus(); 54 | } 55 | input.scrollTop = scrollPos; 56 | } 57 | 58 | addSpecialChar(evt) { 59 | if (evt.target.selectionStart || evt.target.selectionStart === '0') { 60 | let startPos = evt.target.selectionStart; 61 | let endPos = evt.target.selectionEnd; 62 | evt.target.value = evt.target.value.substring(0, startPos) 63 | //+ emojify(this.state.selectedEmoji, {output: 'unicode'}) 64 | + emojify(":innocent:", {output: 'unicode'}) 65 | 66 | + evt.target.value.substring(endPos, evt.target.value.length); 67 | 68 | } else { 69 | //evt.target.value += emojify(this.state.selectedEmoji, {output: 'unicode'}); 70 | evt.target.value += emojify(":innocent:", {output: 'unicode'}); 71 | }; 72 | this.setState({ 73 | selectedEmoji: '' 74 | }); 75 | } 76 | 77 | 78 | render() { 79 | const {display} = this.props; 80 | return ( 81 |
82 | 83 |
84 | ) 85 | 86 | } 87 | } 88 | 89 | export default Emoji; 90 | -------------------------------------------------------------------------------- /src/components/mainPage.css: -------------------------------------------------------------------------------- 1 | .background { 2 | height: 800px; 3 | widows: 100%; 4 | background-image: url('mainPage.jpg'); 5 | background-position: center; 6 | background-size: cover; 7 | background-repeat: no-repeat; 8 | } 9 | 10 | .container { 11 | width: 100%; 12 | height: 900px; 13 | background-color: rgba(161, 148, 148, 0.383) 14 | } 15 | 16 | .wellcomeMessageContainer { 17 | position: relative; 18 | top:30%; 19 | margin: 0 auto; 20 | 21 | text-align: center; 22 | } 23 | 24 | .wellcomeMessage { 25 | color: aliceblue; 26 | font-family: 'Raleway', sans-serif; 27 | font-size: 57px; 28 | letter-spacing: 2px; 29 | margin: 0; 30 | 31 | } 32 | 33 | .about { 34 | font-family: 'Open Sans Condensed', sans-serif; 35 | color: rgb(236, 236, 227); 36 | letter-spacing: 2px; 37 | font-size: 22px; 38 | } 39 | .loginButoon ,.signUpButton { 40 | background-color: rgba(8, 6, 6, 0); 41 | width: 90px; 42 | padding: 5px; 43 | border-radius: 15px; 44 | margin: 25px; 45 | color:white; 46 | font-family: 'Josefin Sans', sans-serif; 47 | font-size: 20px; 48 | line-height: 1.2em; 49 | cursor: pointer; 50 | 51 | border: 1px solid rgba(8, 6, 6, 0); 52 | 53 | } 54 | .signUpButton:hover { 55 | background-color: rgba(43, 40, 40, 0.883); 56 | font-size: 25px; 57 | width: 110px; 58 | 59 | } 60 | .loginButoon:hover { 61 | background-color: rgba(43, 40, 40, 0.883); 62 | font-size: 25px; 63 | width: 110px; 64 | 65 | } -------------------------------------------------------------------------------- /src/components/mainPage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/src/components/mainPage.jpg -------------------------------------------------------------------------------- /src/components/mainPage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { withStyles } from 'material-ui/styles'; 3 | import AppBar from 'material-ui/AppBar'; 4 | import Toolbar from 'material-ui/Toolbar'; 5 | import BottomNavigation, { BottomNavigationAction } from 'material-ui/BottomNavigation'; 6 | import RestoreIcon from 'material-ui-icons/Restore'; 7 | import FavoriteIcon from 'material-ui-icons/Favorite'; 8 | import LocationOnIcon from 'material-ui-icons/LocationOn'; 9 | import './mainPage.css' 10 | const styles = { 11 | root: { 12 | width: '99%', 13 | 14 | top:0, 15 | }, 16 | }; 17 | 18 | class mainPage extends Component { 19 | render() { 20 | const { classes } = this.props; 21 | return ( 22 |
23 |
24 | 25 | 26 | 27 | 28 | } /> 29 | } /> 30 | 31 | 32 | 33 |
34 |

Wellcome to DCI Messenger

35 |

Don't Stop when you'r tired. Stop when you'r done

36 | 39 | 42 |
43 |
44 |
45 | ) 46 | } 47 | } 48 | export default withStyles(styles)(mainPage ) -------------------------------------------------------------------------------- /src/components/mainPage1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/src/components/mainPage1.jpg -------------------------------------------------------------------------------- /src/components/messageBaloun.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import decode from "jwt-decode"; 3 | 4 | 5 | 6 | class ContactDetail extends Component { 7 | render() { 8 | const {message} = this.props 9 | let align; 10 | //let time = this.props.time; 11 | 12 | let user = decode(localStorage.getItem("token")); 13 | 14 | console.log("message from ballon:", message) 15 | if(message.userID === user._id) { 16 | align = "bubble me" 17 | } else { 18 | align = "bubble you" 19 | } 20 | 21 | return ( 22 |
23 |
24 | {message.messageBody} 25 | 26 | 27 | {/* 28 | 29 | {time} 30 | */} 31 |
32 | 33 | 34 |
35 | ); 36 | } 37 | } 38 | 39 | 40 | export default ContactDetail; 41 | -------------------------------------------------------------------------------- /src/components/messagesLog.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import MessageBaloun from './messageBaloun'; 3 | import SkypeAvatar from './skypeAvatar'; 4 | import moment from 'moment'; 5 | import decode from "jwt-decode"; 6 | import config from "../config/config.js"; 7 | import {connect} from "react-redux"; 8 | 9 | class MessagesLog extends Component { 10 | constructor(){ 11 | super(); 12 | 13 | this.state = { 14 | moment:moment().calendar() 15 | } 16 | } 17 | componentDidMount() { 18 | this.scrollToBottom(); 19 | } 20 | 21 | componentDidUpdate() { 22 | this.scrollToBottom(); 23 | 24 | } 25 | 26 | scrollToBottom() { 27 | this.el.scrollIntoView({ behaviour: 'smooth' }); 28 | } 29 | 30 | render() { 31 | const {messages} = this.props; 32 | 33 | console.log("aabbddd", messages); 34 | 35 | const Message = messages.map((message, index, socketId) => { 36 | let order, avatarURL; 37 | 38 | let user = decode(localStorage.getItem("token")); 39 | 40 | console.log("user", user) 41 | console.log("message", message) 42 | console.log('profile avatar hereee: ', user.profile.avatarURL) 43 | if(message.userID === user._id) { 44 | order = '' 45 | avatarURL = user.profile.avatarURL !== "" ? `${config.BASE_URL}images/avatars/${user.profile.avatarURL}` : `${config.BASE_URL}images/avatar_placeholder.png`; 46 | } else { 47 | order = 2; 48 | avatarURL = `${config.BASE_URL}images/avatars/${this.props.setCurrentFriend.avatarURL}`; 49 | } 50 | 51 | return( 52 |
53 | 54 |
55 | 56 |
57 | 58 |
59 | 60 |
61 |
62 | ); 63 | }); 64 | 65 | return ( 66 |
67 | {Message} 68 |
{ this.el = el; }} /> 69 |
70 | ); 71 | } 72 | } 73 | 74 | 75 | const mapStateToProps = (state) => { 76 | return { 77 | setCurrentFriend: state.setCurrentFriendReducer, 78 | }; 79 | }; 80 | export default connect(mapStateToProps)(MessagesLog); 81 | 82 | -------------------------------------------------------------------------------- /src/components/newMessage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withStyles } from 'material-ui/styles'; 3 | import IconButton from 'material-ui/IconButton'; 4 | import Input, { InputAdornment } from 'material-ui/Input'; 5 | import InsertEmoticon from 'material-ui-icons/InsertEmoticon'; 6 | import { FormControl } from 'material-ui/Form'; 7 | import Emoji from './emoji' 8 | 9 | const styles = theme => ({ 10 | 11 | container: { 12 | display: 'flex', 13 | flexWrap: 'wrap', 14 | }, 15 | inputLabelFocused: { 16 | color: '#3BA8C6', 17 | }, 18 | inputInkbar: { 19 | '&:before': { 20 | backgroundColor: '#79848E', 21 | }, 22 | '&:after': { 23 | backgroundColor: '#3BA8C6', 24 | }, 25 | } 26 | }) 27 | 28 | class NewMessage extends Component { 29 | constructor(props){ 30 | super(props); 31 | this.state = { 32 | emojiClicked: false, 33 | selectedEmoji: '', 34 | value: '' 35 | } 36 | this.emojiHandle = this.emojiHandle.bind(this); 37 | this.handleChange = this.handleChange.bind(this); 38 | this.handleEnterKey = this.handleEnterKey.bind(this); 39 | 40 | } 41 | 42 | emojiHandle() { 43 | 44 | this.setState({ 45 | emojiClicked: !this.state.emojiClicked 46 | }); 47 | } 48 | 49 | 50 | handleChange(evt) { 51 | if (this.state.selectedEmoji) { 52 | this.addSpecialChar(evt); 53 | } 54 | } 55 | 56 | handleEnterKey(e) { 57 | const body = e.target.value 58 | 59 | if (e.keyCode === 13 && body) { 60 | this.setState({ 61 | emojiClicked: false 62 | }); 63 | 64 | this.props.handleSubmit(e); 65 | } 66 | } 67 | 68 | render() { 69 | const { classes } = this.props; 70 | 71 | return ( 72 |
73 | 74 | 75 | 76 | 77 | handleSubmit(e)} 84 | onKeyUp={(e) => this.handleEnterKey(e)} 85 | classes={{ 86 | inkbar: classes.inputInkbar, 87 | }} 88 | inputRef={(thisInput) => {this.txtMessage = thisInput}} 89 | endAdornment={ 90 | 91 | 92 | 93 | 94 | {this.state.emojiClicked ?
: null} 95 |
96 | } 97 | /> 98 | 99 | 100 |
101 |
102 | ); 103 | } 104 | } 105 | 106 | export default withStyles(styles)(NewMessage); 107 | -------------------------------------------------------------------------------- /src/components/searchList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withStyles } from 'material-ui/styles'; 3 | import List, { ListItem, ListItemText } from 'material-ui/List'; 4 | import Avatar from './skypeAvatar'; 5 | //import IconButton from "material-ui/IconButton"; 6 | import uuidv1 from "uuid/v1"; 7 | //import FriendConfirmation from "./addFriendConfirmation"; 8 | import config from "../config/config"; 9 | import AddIcon from 'material-ui-icons/Add'; 10 | //import Icon from 'material-ui/Icon'; 11 | import Button from 'material-ui/Button'; 12 | const styles = theme => ({ 13 | root: { 14 | width: '100%', 15 | backgroundColor:'#fff' 16 | }, 17 | button: { 18 | minWidth:10, 19 | height:"46px", 20 | width:"46px", 21 | borderRadius:'50%', 22 | fontSize:24, 23 | backgroundColor:'#0d56a5', 24 | color:'#fff' 25 | }, 26 | }); 27 | 28 | class SearchList extends Component { 29 | constructor() { 30 | super(); 31 | this.requestFriends = this.requestFriends.bind(this); 32 | } 33 | 34 | requestFriends(user) { 35 | let token = localStorage.getItem("token"); 36 | let url = `http://localhost:3001/user/friend/add/${uuidv1()}`; 37 | let formData = { 38 | fullName: user.profile.firstName + ' ' + user.profile.lastName, 39 | avatarURL: user.profile.avatarURL, 40 | userId: user._id 41 | } 42 | if (user) { 43 | const searchParams = Object.keys(formData) 44 | .map(key => { 45 | return ( 46 | encodeURIComponent(key) + "=" + encodeURIComponent(formData[key]) 47 | ); 48 | }) 49 | .join("&"); 50 | 51 | fetch(url, { 52 | method: "POST", 53 | headers: { 54 | Authorization: `TOKEN ${token}`, 55 | "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" 56 | }, 57 | body: searchParams 58 | }) 59 | .then(res => res.json()) 60 | .then(data => { 61 | // console.log(data ); 62 | window.location.reload(); 63 | 64 | }) 65 | .catch(err => console.log(err)); 66 | } else { 67 | console.log({ Error: "Fields are required" }); //Handle errors here... 68 | } 69 | } 70 | handleClickOpen = () => { 71 | console.log(this.state) 72 | this.setState({ open: true }); 73 | }; 74 | render() { 75 | const { classes } = this.props; 76 | 77 | let listItems = ""; 78 | if(this.props.users !== ''){ 79 | listItems = this.props.users.usersList.map(item => { 80 | // let avatarURL = `${config.BASE_URL}images/avatars/${newAvatar}`; 81 | //console.log(item,`${config.BASE_URL}images/avatars/${item.profile.avatarURL}`) 82 | return ( 83 | { 84 | this.requestFriends(item); 85 | // this.handleClickOpen(); 86 | 87 | } 88 | 89 | }> 90 | 94 | 95 | 105 | 106 | ); 107 | }); 108 | } 109 | return ( 110 |
111 | {listItems} 112 |
113 | ); 114 | } 115 | } 116 | 117 | 118 | export default withStyles(styles)(SearchList); -------------------------------------------------------------------------------- /src/components/skypeAvatar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Avatar from 'material-ui/Avatar'; 3 | import { ListItemText } from 'material-ui/List'; 4 | 5 | 6 | class SkypeAvatar extends React.Component { 7 | 8 | render(){ 9 | const props = { 10 | avatarUrl : this.props.avatar, 11 | size : this.props.size, 12 | key : this.props.key, 13 | name : this.props.name, 14 | } 15 | const style = { 16 | width:props.size, 17 | height:props.size, 18 | backgroundImage: "url(" + props.avatarUrl + ")", 19 | } 20 | 21 | return ( 22 |
23 | 29 | 30 |
31 | ) 32 | } 33 | } 34 | 35 | export default SkypeAvatar; 36 | -------------------------------------------------------------------------------- /src/components/test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AppBar from 'material-ui/AppBar'; 3 | 4 | /** 5 | * A simple example of `AppBar` with an icon on the right. 6 | * By default, the left icon is a navigation-menu. 7 | */ 8 | 9 | const AppBarExampleIcon = () => ( 10 | 14 | ); 15 | 16 | export default AppBarExampleIcon; 17 | -------------------------------------------------------------------------------- /src/components/userDataSetting.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/src/components/userDataSetting.js -------------------------------------------------------------------------------- /src/components/vedioCall.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | 4 | 5 | 6 | 7 | 8 | class VedioCall extends Component { 9 | 10 | state = { 11 | videoSrc: null, 12 | }; 13 | 14 | componentDidMount= () => { 15 | navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia; 16 | if (navigator.getUserMedia) { 17 | navigator.getUserMedia({audio: false, video:{ width: { min: 1024, ideal: 1280, max: 1920 }, 18 | height: { min: 576, ideal: 720, max: 1080 }},}, this.handleVideo, this.videoError); 19 | } 20 | }; 21 | 22 | 23 | handleVideo= (stream)=> { 24 | // Update the state, triggering the component to re-render with the correct stream 25 | this.refs.video.src = window.URL.createObjectURL(stream); 26 | }; 27 | 28 | videoError= ()=> { 29 | 30 | }; 31 | 32 | render() { 33 | return
34 |
; 36 | } 37 | } 38 | 39 | export default VedioCall; -------------------------------------------------------------------------------- /src/components/wellcome.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .main { 4 | margin: 10px 0 10px 0; 5 | border: 1px solid rgb(194, 194, 171); 6 | height: 700px; 7 | background-image: url('wellcomePage.jpg'); 8 | background-position: center; 9 | background-size: cover; 10 | background-repeat: no-repeat; 11 | } 12 | 13 | .wellcome { 14 | background-color:rgba(129, 136, 129, 0.719); 15 | margin:90px auto; 16 | text-align: center; 17 | border-radius: 10px; 18 | width: 43%; 19 | height: 500px; 20 | } -------------------------------------------------------------------------------- /src/components/wellcomePage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/src/components/wellcomePage.jpg -------------------------------------------------------------------------------- /src/config/config.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | BASE_URL: "http://localhost:3001/", 5 | SOCKET_URL:"http://localhost:3001/" 6 | } 7 | -------------------------------------------------------------------------------- /src/container/AddFriends.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import SearchBar from 'material-ui-search-bar'; 3 | 4 | 5 | 6 | 7 | class AddFriends extends Component { 8 | render() { 9 | return ( 10 |
11 | console.log('onChange')} 13 | onRequestSearch={() => console.log('onRequestSearch')} 14 | style={{ 15 | margin: '0 auto', 16 | maxWidth: 700, 17 | 18 | }} 19 | 20 | /> 21 |
22 | ); 23 | } 24 | } 25 | 26 | export default AddFriends; -------------------------------------------------------------------------------- /src/container/FriendsSearchBar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | //import { withStyles } from 'material-ui/styles'; 3 | import IconButton from 'material-ui/IconButton'; 4 | import AppBar from 'material-ui/AppBar'; 5 | import Toolbar from 'material-ui/Toolbar'; 6 | import { FormControl } from 'material-ui/Form'; 7 | import Popover from 'material-ui/Popover'; 8 | import Input, { InputLabel } from 'material-ui/Input'; 9 | import { findDOMNode } from 'react-dom'; 10 | import { setFilter }from '../actions/filterAction'; 11 | import { connect } from 'react-redux'; 12 | import FormDialog from '../components/dialog' 13 | import AddContact from '../components/Addcontact' 14 | 15 | 16 | 17 | const mapDispatchToProps = dispatch => ({ 18 | onFilter: filter => dispatch(setFilter(filter)) 19 | }) 20 | 21 | class SearchBar extends Component { 22 | 23 | state = { 24 | open: false, 25 | contactAddOpen: false, 26 | anchorEl: null, 27 | anchorOriginVertical: 'bottom', 28 | anchorOriginHorizontal: 'center', 29 | transformOriginVertical: 'top', 30 | transformOriginHorizontal: 'center', 31 | positionTop: 200, // Just so the popover can be spotted more easily 32 | positionLeft: 400, // Same as above 33 | anchorReference: 'anchorEl', 34 | }; 35 | 36 | handleClickOpen = () => { 37 | this.setState({ contactAddOpen: true }); 38 | }; 39 | handleClickClose = () => { 40 | this.setState({ 41 | contactAddOpen: false, 42 | }); 43 | }; 44 | 45 | 46 | handleClickButton = () => { 47 | this.setState({ 48 | open: true, 49 | anchorEl: findDOMNode(this.button), 50 | }); 51 | }; 52 | 53 | handleClose = () => { 54 | this.setState({ 55 | open: false, 56 | }); 57 | }; 58 | searchFriends(e) { 59 | this.props.onFilter(e.target.value) 60 | this.setState({searchKeyword: e.target.value}); 61 | } 62 | button = null; 63 | 64 | render() { 65 | //const { classes } = this.props; 66 | const { 67 | open, 68 | anchorEl, 69 | anchorOriginVertical, 70 | anchorOriginHorizontal, 71 | transformOriginVertical, 72 | transformOriginHorizontal, 73 | positionTop, 74 | positionLeft, 75 | anchorReference, 76 | } = this.state; 77 | return ( 78 |
79 | 81 | 82 | { 84 | this.button = node; 85 | }} 86 | onClick={this.handleClickButton} 87 | id="cypress-friend-search-button" 88 | style={{position:'absolute', top:9, left:2}} 89 | aria-label="Menu"> 90 | search 91 | 92 | 93 | 108 | 109 | 110 | search 111 | { 116 | this.searchFriends(e) 117 | }} 118 | /> 119 | 120 | 121 | 126 | 127 | add_circle 128 | 129 | } 134 | autoScrollBodyContent={false} 135 | fullScreen= {false} 136 | /> 137 | 138 | 139 |
140 | ); 141 | } 142 | } 143 | 144 | export default connect(null, mapDispatchToProps)(SearchBar) 145 | -------------------------------------------------------------------------------- /src/images/avatar-penguin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/src/images/avatar-penguin.png -------------------------------------------------------------------------------- /src/images/bear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/src/images/bear.png -------------------------------------------------------------------------------- /src/images/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/src/images/cat.png -------------------------------------------------------------------------------- /src/images/rabbit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aemal/skype-clone/506e56d66adce49bf920ed1c8a7d561f0fbdcca0/src/images/rabbit.png -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | import registerServiceWorker from "./registerServiceWorker"; 5 | import { Provider } from "react-redux"; 6 | //import {createStore} from 'redux'; 7 | //import reducer from './reducers'; 8 | import ProfileSettings from "./components/ProfileSettings"; 9 | import store from "./store"; 10 | import { ConnectedRouter } from "react-router-redux"; 11 | import SingIn from "./components/SingIn"; 12 | import { Route } from "react-router"; 13 | import createHistory from "history/createBrowserHistory"; 14 | import "../node_modules/font-awesome/css/font-awesome.min.css"; 15 | import SingUp from "./components/SingUp"; 16 | import WellcomePage from "./components/WellcomePage"; 17 | import Favicon from "react-favicon"; 18 | import config from "./config/config.js"; 19 | 20 | const history = createHistory(); 21 | 22 | ReactDOM.render( 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 |
, 35 | document.getElementById("root") 36 | ); 37 | registerServiceWorker(); 38 | -------------------------------------------------------------------------------- /src/reducers/changeSettingReducer.js: -------------------------------------------------------------------------------- 1 | export default function changeSettingReducer(state={ 2 | currentUserData:{}, 3 | error:null 4 | 5 | },action){ 6 | switch(action.type){ 7 | case 'CHANGE_USER_DONE':{ 8 | return {...state,currentUserData:action.payload} 9 | } 10 | case 'CHANGE_USER_WITHERROR':{ 11 | return {...state,error:action.payload} 12 | } 13 | default: 14 | return state 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/reducers/contactListFilterReducer.js: -------------------------------------------------------------------------------- 1 | const contactListFilterReducer = (state,action ) => { 2 | switch (action.type) { 3 | case 'CONTACT_FILTER': 4 | return action.payload; 5 | default: 6 | return state || ''; 7 | 8 | } 9 | 10 | } 11 | //state.contactList.filter(el=> el.name.toLowerCase().match(searchValue.toLowerCase())) 12 | export default contactListFilterReducer; -------------------------------------------------------------------------------- /src/reducers/contactListReducers.js: -------------------------------------------------------------------------------- 1 | export default function contactListReducer(state={ 2 | contactList:[], 3 | fetching:false, 4 | fetched:false, 5 | error:null 6 | 7 | },action){ 8 | switch(action.type){ 9 | case 'FETCH_CONTACT_LIST':{ 10 | return {...state,fetching:true} 11 | } 12 | case 'FETCH_USER_DONE':{ 13 | return {...state,fetching:false,fetched:true,contactList:action.payload} 14 | } 15 | case 'FETCH_USER_WITHERROR':{ 16 | return {...state,fetching:false,error:action.payload} 17 | } 18 | default: 19 | return state 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux'; 2 | import contactListReducers from './contactListReducers'; 3 | import contactListFilterReducer from './contactListFilterReducer'; 4 | import setCurrentFriendReducer from './setCurrentFriendReducer'; 5 | import { routerReducer } from 'react-router-redux' 6 | import changeSettingReducer from './changeSettingReducer' 7 | export default combineReducers({ 8 | contactListReducers, 9 | setCurrentFriendReducer, 10 | contactListFilterReducer, 11 | changeSettingReducer, 12 | router: routerReducer 13 | }) -------------------------------------------------------------------------------- /src/reducers/setCurrentFriendReducer.js: -------------------------------------------------------------------------------- 1 | const setCurrentFriendReducer = (state={ 2 | setCurrentFriend: '', 3 | } ,action ) => { 4 | switch (action.type) { 5 | case 'SET_CURRENT_FRIEND': 6 | return action.payload; 7 | default: 8 | return state || ''; 9 | } 10 | } 11 | export default setCurrentFriendReducer; -------------------------------------------------------------------------------- /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 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | } else { 39 | // Is not local host. Just register service worker 40 | registerValidSW(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 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import {applyMiddleware, createStore} from 'redux'; 2 | //import createHistory from 'history/createBrowserHistory'; 3 | import thunk from 'redux-thunk'; 4 | import reducer from './reducers'; 5 | //import { ConnectedRouter, routerReducer, routerMiddleware, push } from 'react-router-redux'; 6 | 7 | //const history = createHistory(); 8 | //const Routemiddleware = routerMiddleware(history) 9 | 10 | const middlewares= applyMiddleware( thunk); 11 | 12 | 13 | export default createStore(reducer,middlewares); -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | box-sizing: border-box; 5 | } 6 | /* 7 | *+* { 8 | margin-top: 1rem; 9 | }*/ 10 | 11 | /* Media Query for removing padding on small devices */ 12 | 13 | /*@media (min-width: 200px) and (max-width: 599px) { 14 | .MuiGrid-typeItem-2 { 15 | padding: 0; 16 | } 17 | }*/ 18 | 19 | /* GLOBAL STYLES */ 20 | 21 | body { 22 | height: 100%; 23 | margin: 0px; 24 | } 25 | 26 | .app { 27 | height: 100%; 28 | } 29 | 30 | /* SIGN IN PAGE */ 31 | /****************/ 32 | 33 | .logo { 34 | height: 100px; 35 | display: block; 36 | margin: auto; 37 | } 38 | 39 | .main-container { 40 | display: table; 41 | position: absolute; 42 | height: 100%; 43 | width: 100%; 44 | background-color: #2b81ca; 45 | } 46 | 47 | @-webkit-keyframes Gradient { 48 | 0% { 49 | background-position: 0% 50%; 50 | } 51 | 50% { 52 | background-position: 100% 50%; 53 | } 54 | 100% { 55 | background-position: 0% 50%; 56 | } 57 | } 58 | 59 | @-moz-keyframes Gradient { 60 | 0% { 61 | background-position: 0% 50%; 62 | } 63 | 50% { 64 | background-position: 100% 50%; 65 | } 66 | 100% { 67 | background-position: 0% 50%; 68 | } 69 | } 70 | 71 | @keyframes Gradient { 72 | 0% { 73 | background-position: 0% 50%; 74 | } 75 | 50% { 76 | background-position: 100% 50%; 77 | } 78 | 100% { 79 | background-position: 0% 50%; 80 | } 81 | } 82 | 83 | .sign-in-header { 84 | letter-spacing: 2px; 85 | text-align: center; 86 | text-shadow: 0px 1px 15px grey; 87 | } 88 | 89 | .middle-container { 90 | display: table-cell; 91 | vertical-align: middle; 92 | padding: 30px; 93 | } 94 | 95 | .sign-in-details { 96 | width: 350px; 97 | background: #fff; 98 | border-radius: 10px; 99 | overflow: hidden; 100 | padding: 77px 55px 33px 55px; 101 | box-shadow: 0 5px 10px 0px rgba(0, 0, 0, 0.1); 102 | -moz-box-shadow: 0 5px 10px 0px rgba(0, 0, 0, 0.1); 103 | -webkit-box-shadow: 0 5px 10px 0px rgba(0, 0, 0, 0.1); 104 | -o-box-shadow: 0 5px 10px 0px rgba(0, 0, 0, 0.1); 105 | -ms-box-shadow: 0 5px 10px 0px rgba(0, 0, 0, 0.1); 106 | margin-left: auto; 107 | margin-right: auto; 108 | } 109 | 110 | .sign-in-details .MuiFormControl-root-12 { 111 | margin-top: 0.7rem; 112 | } 113 | 114 | .sign-in-details input, 115 | .sign-in-details label { 116 | color: #2b81ca; 117 | margin-top: 1rem; 118 | } 119 | 120 | .sign-in-h3 { 121 | letter-spacing: 1px; 122 | color: #726f6f; 123 | } 124 | 125 | .sign-in-details .MuiButton-raised-48 { 126 | background-color: #3ba8c6; 127 | color: white; 128 | border-radius: 10px; 129 | } 130 | 131 | .login-button { 132 | margin-top: 60px; 133 | width: 100%; 134 | color: #f50057; 135 | } 136 | 137 | .additional-options { 138 | justify-content: space-between; 139 | margin-top: 20px; 140 | } 141 | .text-center { 142 | text-align: center; 143 | } 144 | 145 | .additional-options, 146 | .additional-options .link { 147 | display: flex; 148 | flex-wrap: wrap; 149 | color: #726f6f; 150 | text-decoration: none; 151 | } 152 | 153 | .color-checkbox { 154 | margin-right: -2.5rem; 155 | } 156 | 157 | .color-checkbox.MuiCheckbox-checked-58 { 158 | color: #726f6f; 159 | } 160 | .color-checkbox.MuiCheckbox-checked-66 { 161 | color: #726f6f; 162 | } 163 | 164 | .register-forgot-password { 165 | padding: 1rem; 166 | } 167 | 168 | .social { 169 | width: 20px; 170 | } 171 | 172 | .social-media-login-main button { 173 | width: 100%; 174 | margin: 0.5rem; 175 | } 176 | 177 | .social-media-login { 178 | margin-top: 20%; 179 | } 180 | 181 | .social-media-login i { 182 | margin-right: 2rem; 183 | } 184 | 185 | /* SIGN UP PAGE */ 186 | /*************/ 187 | 188 | .picker { 189 | margin-top: 2rem; 190 | } 191 | 192 | span.MuiTypography-body1-52 { 193 | color: #3ba8c6; 194 | font-size: 1rem; 195 | } 196 | 197 | /* Tried to change the purple colour of the default, but doesn't seem to work */ 198 | 199 | legend.MuiFormLabel-focused-24, 200 | legend.MuiFormLabel-focused-25 { 201 | color: #79848e; 202 | } 203 | 204 | /* Tried to change the purple colour of the default, but doesn't seem to work */ 205 | span.MuiRadio-checked-112, 206 | span.MuiRadio-checked-113 { 207 | color: #79848e; 208 | } 209 | 210 | button.login-button { 211 | background-color: #ed7033; 212 | color: white; 213 | } 214 | 215 | button.login-button:hover { 216 | background-color: #79848e; 217 | color: white; 218 | } 219 | 220 | /* AUTH PAGE */ 221 | /*************/ 222 | 223 | /* SIDEBAR COMPONENTS */ 224 | 225 | .sideBarMainComponent { 226 | height: 100%; 227 | width: 25%; 228 | float: left; 229 | } 230 | 231 | .sideBarAvatarComponent { 232 | height: "auto"; 233 | width: 100%; 234 | background-color: #01062e; 235 | /*position: relative; creates the shadow above the contact list */ 236 | } 237 | 238 | .sideBarAvatarComponent .avatar { 239 | margin: 1rem auto 0 auto; 240 | box-shadow: 0 0 13px 3px rgb(255, 255, 255); 241 | } 242 | 243 | .avatar { 244 | margin: 0 auto; 245 | background-size: cover; 246 | background-position: center; 247 | -webkit-border-radius: 50%; 248 | -moz-border-radius: 50%; 249 | border-radius: 50%; 250 | box-shadow: 0 0 13px grey; 251 | } 252 | 253 | .icons { 254 | display: inline-flex; 255 | width: 100%; 256 | justify-content: space-around; 257 | padding: 1rem; 258 | } 259 | 260 | .icons i { 261 | font-size: 1.8rem; 262 | color: #726f6f; 263 | transition: all 0.1s ease-in; 264 | } 265 | 266 | .icons i:hover { 267 | color: #3ba8c6; 268 | transform: scale(1.3); 269 | } 270 | 271 | .sideBarContactListComponent { 272 | height: calc(100vh - 207px); 273 | width: 100%; 274 | overflow: auto; 275 | display: block; 276 | background: linear-gradient(#01062e, #2f3772); 277 | } 278 | 279 | .list-item h3 { 280 | color: #79848e; 281 | font-size: 0.85rem; 282 | } 283 | 284 | .list-item:hover h3 { 285 | font-weight: 700; 286 | color: #3ba8c6; 287 | } 288 | 289 | .list-item .avatar { 290 | font-weight: 700; 291 | border: 2px solid #79848e; 292 | transition: all 0.1s ease-in; 293 | } 294 | 295 | .list-item:hover .avatar { 296 | font-weight: 700; 297 | border: 2px solid #3ba8c6; 298 | transform: scale(1.3); 299 | } 300 | 301 | .sideBarSearchComponent { 302 | height: auto; 303 | width: 100%; 304 | } 305 | 306 | .friend-search-bar { 307 | background-color:#01062e; 308 | color: white; 309 | } 310 | 311 | .friend-search-bar i { 312 | color: #fcf9ff; 313 | } 314 | 315 | .open-search-bar { 316 | background-color: #fcf9ff; 317 | box-shadow: 0 0 15px #fcf9ff; 318 | color: #fcf9ff; 319 | border-radius: 3rem; 320 | } 321 | 322 | .open-search-bar > div, 323 | .open-search-bar > label { 324 | color: #726f6f; 325 | font-size: 1rem; 326 | padding: 0 1rem 0.3rem 1rem; 327 | } 328 | 329 | #searchContact { 330 | width: 80%; 331 | border-radius: 0; 332 | } 333 | 334 | /* MESSAGES FIELD COMPONENTS */ 335 | 336 | .chat { 337 | width: 100%; 338 | } 339 | 340 | .messagesMainComponent { 341 | height: 100vh; 342 | width: 75%; 343 | float: left; 344 | padding: 1rem; 345 | } 346 | 347 | .contact-list { 348 | display: flex; 349 | justify-content: space-between; 350 | padding: 0.5rem; 351 | margin: 0; 352 | box-shadow: 1px 0 5px grey; 353 | } 354 | 355 | .contact-list i { 356 | font-size: 1.5rem; 357 | color: #726f6f; 358 | transition: all 0.1s ease-in; 359 | margin-top: 0.4rem; 360 | background-color: #ebeaed; 361 | border-radius: 50%; 362 | padding: 0.7rem; 363 | } 364 | 365 | .contact-list i:hover { 366 | color: #3ba8c6; 367 | transform: scale(1.2); 368 | } 369 | 370 | .contact-list .avatar { 371 | margin-right: 0.8rem; 372 | margin-top: 0.6rem; 373 | } 374 | 375 | /* Messages area */ 376 | 377 | .messagesLogComponent { 378 | overflow: auto; 379 | height: calc(100vh - 183px); 380 | width: 100%; 381 | float: left; 382 | display: block; 383 | } 384 | 385 | 386 | .messagesContactDetailComponent { 387 | height: auto; 388 | width: 100%; 389 | float: left; 390 | display: block; 391 | position: relative; 392 | } 393 | 394 | .bubble { 395 | background-color: #fcf9ff; 396 | border-radius: 5px; 397 | box-shadow: 0 0 10px grey; 398 | display: inline-block; 399 | padding: 10px 15px; 400 | position: relative; 401 | vertical-align: top; 402 | color: #726f6f; 403 | } 404 | 405 | .timer { 406 | display: block; 407 | color: #3ba8c6; 408 | font-size: 0.8rem; 409 | margin-top: 0.5rem; 410 | } 411 | 412 | .bubble::before { 413 | background-color: #fcf9ff; 414 | content: "\00a0"; 415 | display: block; 416 | height: 13px; 417 | position: absolute; 418 | margin-left: 0.3rem; 419 | transform: rotate(29deg) skew(-35deg); 420 | -moz-transform: rotate(29deg) skew(-35deg); 421 | -ms-transform: rotate(29deg) skew(-35deg); 422 | -o-transform: rotate(29deg) skew(-35deg); 423 | -webkit-transform: rotate(29deg) skew(-35deg); 424 | width: 13px; 425 | } 426 | 427 | .me { 428 | float: left; 429 | margin: 5px 45px 5px 20px; 430 | } 431 | 432 | .me::before { 433 | box-shadow: -2px 2px 2px 0 rgba(178, 178, 178, 0.4); 434 | left: -9px; 435 | } 436 | 437 | .you { 438 | float: right; 439 | margin: 5px 20px 5px 45px; 440 | } 441 | 442 | .you::before { 443 | 444 | box-shadow: 2px -2px 2px 0 rgba(178, 178, 178, 0.4); 445 | right: -9px; 446 | } 447 | 448 | .message { 449 | display: flex; 450 | } 451 | 452 | /* Typing the text area */ 453 | 454 | .messagesNewMessageComponent { 455 | width: 100%; 456 | float: left; 457 | bottom: 2%; 458 | } 459 | 460 | .texting-area { 461 | padding: 0.5rem; 462 | bottom: 2%; 463 | } 464 | 465 | .emoticon:hover { 466 | color: #a5a5a5; 467 | } 468 | 469 | .form-control { 470 | width: 100%; 471 | } 472 | .highlightedFriend { 473 | background-color: #ffaadd; 474 | } -------------------------------------------------------------------------------- /src/utiles/Api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | 4 | export function loginRequest(url,email,pass){ 5 | axios.post(url, { emailAddress: email, password: pass }) 6 | .then(function(response){ 7 | console.log(response +'submit succesfull') 8 | }); 9 | 10 | } -------------------------------------------------------------------------------- /temp/data.js: -------------------------------------------------------------------------------- 1 | db.User.insert({ 2 | emailAddress: "john@gmail.com", 3 | password: "john123", 4 | dateOfBirth: "1987.02.12", 5 | profile:{ 6 | firstName: "john", 7 | lastName: "balout", 8 | gender: "Male", 9 | avatarURL: "statics/pics/image.jpg" 10 | }, 11 | status:{ 12 | lastSeen: "1987.02.12", 13 | active: true 14 | }, 15 | loginStrategy:"local", 16 | loginObject: {}, 17 | contacts:{ 18 | friends: [], 19 | blocked: [], 20 | requested: [], 21 | pending: [] 22 | } 23 | }); 24 | --------------------------------------------------------------------------------