├── .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 |
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 |
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 |
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 |
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 |

116 |
117 |
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 |
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 |

317 |
318 |
Sign Up
319 |
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 |
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 |
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 |
32 |
33 |
36 |
43 |
46 |
}
51 | fullScreen={true}
52 | />
53 |
54 |
55 |
56 |
57 | }
58 | return (
59 |
60 |
61 |
62 |

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 |
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 |
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 |
35 |
;
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 |
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 |
--------------------------------------------------------------------------------