├── .gitignore
├── README.md
├── docs
├── asset-manifest.json
├── favicon.ico
├── index.html
├── manifest.json
├── service-worker.js
└── static
│ ├── css
│ ├── main.1d762d0c.css
│ └── main.1d762d0c.css.map
│ └── js
│ ├── main.8f5ab0ff.js
│ └── main.8f5ab0ff.js.map
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
└── src
├── App.js
├── App.test.js
├── Classes.js
├── Constants.js
├── components
├── Layout.js
├── LoginForm.js
├── UsersInChatRoom.js
├── chat
│ ├── ChatContainer.js
│ ├── ChatHeading.js
│ └── SideBar.js
└── messaging
│ ├── MessageInput.js
│ └── Messages.js
├── index.js
├── registerServiceWorker.js
├── server
├── Procfile
├── SocketManager.js
├── package.json
└── server.js
└── styles
├── .sass-cache
└── 6af3623bcbcb565e309f0528766b9138bd675e55
│ └── index.scssc
├── index.css
├── index.css.map
└── index.scss
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | #test components
13 | /src/components/TestIo.js
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | *.scssc
27 |
28 | src/components/test-io.css
29 |
30 | src/registerServiceWorker.js
31 |
32 | .env
33 |
34 | package-lock.json
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React-Chat-App
2 | React Chat App Using Socket.io made back in 2016 pre hook and function component. The new one will be located at https://github.com/leonwatson2/react-chat-app-v2
3 |
--------------------------------------------------------------------------------
/docs/asset-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "main.css": "static/css/main.1d762d0c.css",
3 | "main.css.map": "static/css/main.1d762d0c.css.map",
4 | "main.js": "static/js/main.8f5ab0ff.js",
5 | "main.js.map": "static/js/main.8f5ab0ff.js.map"
6 | }
--------------------------------------------------------------------------------
/docs/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leonwatson2/React-Chat-App/d96e718a544ae790e8591dd53dc5029d740b38b2/docs/favicon.ico
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
React Chat App using Socket.io
--------------------------------------------------------------------------------
/docs/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/docs/service-worker.js:
--------------------------------------------------------------------------------
1 | "use strict";function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}var precacheConfig=[["/React-Chat-App/index.html","785d59473f13e825a66de9c657b3862f"],["/React-Chat-App/static/css/main.1d762d0c.css","0c82c17316a8c275c386632e9141a219"],["/React-Chat-App/static/js/main.8f5ab0ff.js","bd9975bc75a9f3d1a9b8198d290a4c7b"]],cacheName="sw-precache-v3-sw-precache-webpack-plugin-"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var n=new URL(e);return"/"===n.pathname.slice(-1)&&(n.pathname+=t),n.toString()},cleanResponse=function(e){return e.redirected?("body"in e?Promise.resolve(e.body):e.blob()).then(function(t){return new Response(t,{headers:e.headers,status:e.status,statusText:e.statusText})}):Promise.resolve(e)},createCacheKey=function(e,t,n,r){var a=new URL(e);return r&&a.pathname.match(r)||(a.search+=(a.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(n)),a.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var n=new URL(t).pathname;return e.some(function(e){return n.match(e)})},stripIgnoredUrlParameters=function(e,t){var n=new URL(e);return n.hash="",n.search=n.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(e){return t.every(function(t){return!t.test(e[0])})}).map(function(e){return e.join("=")}).join("&"),n.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],n=e[1],r=new URL(t,self.location),a=createCacheKey(r,hashParamName,n,/\.\w{8}\./);return[r.toString(),a]}));self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(e){return setOfCachedUrls(e).then(function(t){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(n){if(!t.has(n)){var r=new Request(n,{credentials:"same-origin"});return fetch(r).then(function(t){if(!t.ok)throw new Error("Request for "+n+" returned a response with status "+t.status);return cleanResponse(t).then(function(t){return e.put(n,t)})})}}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var t=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(e){return e.keys().then(function(n){return Promise.all(n.map(function(n){if(!t.has(n.url))return e.delete(n)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(e){if("GET"===e.request.method){var t,n=stripIgnoredUrlParameters(e.request.url,ignoreUrlParametersMatching);(t=urlsToCacheKeys.has(n))||(n=addDirectoryIndex(n,"index.html"),t=urlsToCacheKeys.has(n));!t&&"navigate"===e.request.mode&&isPathWhitelisted(["^(?!\\/__).*"],e.request.url)&&(n=new URL("/React-Chat-App/index.html",self.location).toString(),t=urlsToCacheKeys.has(n)),t&&e.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(n)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(t){return console.warn('Couldn\'t serve response for "%s" from cache: %O',e.request.url,t),fetch(e.request)}))}});
--------------------------------------------------------------------------------
/docs/static/css/main.1d762d0c.css:
--------------------------------------------------------------------------------
1 | :root{--primary-color:#3e3e5e;--primary-color-dark:#2e2e4f;--primary-color-light:#5d5d8a;--primary-color-active:#363656;--sent-message-bg-color:#89a1fc;--secondary-color:#484d79;--main-text-color:#cac8ee;--messages-bg:#dcddf5;--received-message-bg-color:#fff;--sent-message-color:#fff;--received-message-color:#b3b2ca;--success-color:#60d66a}#root,body,html{margin:0;padding:0;font-family:sans-serif;height:100%}input,textarea{font-family:Arial}.login{width:100%;height:100%;-ms-flex-align:center;align-items:center}.login,.login-form{display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center}.login-form{-ms-flex-direction:column;flex-direction:column}.login-form input{--height:20px;max-width:100%;border-top:none;border-left:none;border-right:none;height:var(--height);line-height:var(--height);font-size:var(--height);border-bottom:solid 2px var(--received-message-color);-webkit-transition:all .23s ease-in;-o-transition:all .23s ease-in;transition:all .23s ease-in}.login-form input:focus{--height:30px;border-bottom:solid 2px var(--primary-color-light);outline:none}.indicator{width:var(--indicator-size);height:var(--indicator-size);border-radius:var(--indicator-size);background:var(--indicator-color);display:inline-block;margin-right:8px}.container{--main-padding-vertical:18px;--main-padding-horizontal:16px;--main-padding:var(--main-padding-vertical) var(--main-padding-horizontal);--head-size:65px;--footer-height:65px;color:var(--main-text-color);display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;-ms-flex-align:start;align-items:flex-start;height:100%;width:100%}#side-bar{width:33.75%;height:100%;-ms-flex-pack:distribute;justify-content:space-around;-ms-flex-direction:column;flex-direction:column}#side-bar,#side-bar .heading{-webkit-box-sizing:border-box;box-sizing:border-box;display:-ms-flexbox;display:flex}#side-bar .heading{padding:var(--main-padding);background:var(--primary-color-dark)}#side-bar .heading,#side-bar .search{min-height:var(--head-size);-ms-flex-direction:row;flex-direction:row;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}#side-bar .search{background:var(--primary-color);-webkit-box-sizing:border-box;box-sizing:border-box;display:-ms-flexbox;display:flex;border-width:1px 0;border-style:solid;border-color:#000;padding-left:15px;padding-right:20px}#side-bar .search .search-icon{margin-right:15px;cursor:pointer}#side-bar .search input{width:100%;background:var(--primary-color);-ms-flex-positive:1;flex-grow:1;-webkit-box-sizing:border-box;box-sizing:border-box;border:none;color:var(--main-text-color)}#side-bar .search input:focus{outline:none}#side-bar .search input::-webkit-input-placeholder{color:var(--main-text-color);opacity:.6}#side-bar .search input:-ms-input-placeholder{color:var(--main-text-color);opacity:.6}#side-bar .search input::placeholder{color:var(--main-text-color);opacity:.6}#side-bar .search .plus{--plus-width:2px;--plus-length:16px;display:inline-block;position:relative;cursor:pointer;width:13px;height:13px}#side-bar .search .plus:after,#side-bar .search .plus:before{content:"";position:absolute;background:var(--received-message-color)}#side-bar .search .plus:after{width:var(--plus-length);height:var(--plus-width);top:5px}#side-bar .search .plus:before{width:var(--plus-width);height:var(--plus-length);top:-2px;left:7px}#side-bar .users{overflow-y:scroll;background:var(--primary-color);-ms-flex-positive:1;flex-grow:1}#side-bar .users::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,.3);background-color:var(--main-text-color)}#side-bar .users::-webkit-scrollbar{width:5px;background-color:green}#side-bar .users::-webkit-scrollbar-thumb{background-color:var(--secondary-color)}#side-bar .users .user{--indicator-size:16px;--indicator-color:var(--success-color);-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex;height:66px;-ms-flex-pack:start;justify-content:flex-start;padding:var(--main-padding)}#side-bar .users .user.active,#side-bar .users .user:hover{background:var(--primary-color-dark)}#side-bar .users .user:hover:not(.active){cursor:pointer}#side-bar .users .user .user-info{margin-left:15px;-ms-flex-positive:1;flex-grow:1}#side-bar .users .user .user-info .last-message{font-size:12px;opacity:.56}#side-bar .users .user .new-message{height:100%;-ms-flex-pack:center;justify-content:center}#side-bar .current-user,#side-bar .users .user .new-message{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}#side-bar .current-user{background:var(--secondary-color);-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-direction:row;flex-direction:row;min-height:var(--footer-height);-ms-flex-pack:justify;justify-content:space-between;padding-left:var(--main-padding-horizontal);padding-right:var(--main-padding-horizontal)}#side-bar .current-user .logout{cursor:pointer;-ms-flex-pack:center;justify-content:center;font-size:2em}#side-bar .current-user .logout,.chat-header{-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex}.chat-header{background:var(--primary-color-light);-webkit-box-shadow:0 6px 5px -2px hsla(0,0%,88%,.7);box-shadow:0 6px 5px -2px hsla(0,0%,88%,.7);-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-pack:justify;justify-content:space-between;min-height:var(--head-size);padding:18px 16px}.chat-header .user-info{-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex}.chat-header .user-info .user-name{margin-right:10px}.chat-header .user-info .status{--indicator-size:8px;-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex}.chat-header .user-info .status .online{--indicator-color:#32b0bb}.chat-header .user-info .status .offline{--indicator-color:#c63d2c}.chat-header .options{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-direction:row;flex-direction:row;-ms-flex-pack:distribute;justify-content:space-around;height:100%;width:15%}.chat-header .options svg{cursor:pointer}.chat-room-container{height:100%;width:76.25%}.chat-room{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;height:100%;width:100%}.chat-room.choose{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;font-size:2em}.thread-container{-ms-flex-positive:1;flex-grow:1;overflow-y:scroll;position:relative}.thread-container::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,.3);background-color:var(--main-text-color)}.thread-container::-webkit-scrollbar{width:5px;background-color:green}.thread-container::-webkit-scrollbar-thumb{background-color:var(--secondary-color)}.thread-container .thread{position:relative;width:100%;min-height:800px;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:end;justify-content:flex-end;color:var(--received-message-color);background:var(--messages-bg)}.message-container{display:-ms-flexbox;display:flex;-ms-flex-pack:start;justify-content:flex-start;min-height:50px;margin:10px 15px;--message-triangle-offset:-7px;-webkit-animation:.65s ease-out 0s show;animation:.65s ease-out 0s show}.message-container .time{-ms-flex-order:1;order:1}.message-container .data{-ms-flex-order:2;order:2;height:100%;margin-left:25px}.message-container .name{font-size:.65em;margin-top:5px;text-align:right}.message-container .message{background:var(--received-message-bg-color);border-radius:5px;border-top-left-radius:0;-webkit-box-sizing:border-box;box-sizing:border-box;color:var(--received-message-color);height:100%;padding:10px 15px;position:relative}@-webkit-keyframes show{0%{opacity:0}to{opacity:1}}@keyframes show{0%{opacity:0}to{opacity:1}}.message-container .message:before{--triangle-color:var(--received-message-bg-color);border-bottom-color:transparent;border-left-color:transparent;border-right-color:var(--triangle-color);border-style:solid;border-top-color:var(--triangle-color);border-width:4px;content:"";height:0;left:var(--message-triangle-offset);position:absolute;top:0;width:0}.message-container.right{text-align:right;-ms-flex-pack:end;justify-content:flex-end}.message-container.right .time{-ms-flex-order:2;order:2;margin-left:25px}.message-container.right .data{margin-left:0;-ms-flex-order:1;order:1}.message-container.right .name{display:none}.message-container.right .message{background:var(--sent-message-bg-color);color:var(--sent-message-color);border-top-right-radius:0;border-top-left-radius:5px}.message-container.right .message:before{--triangle-color:var(--sent-message-bg-color);border-top-color:var(--triangle-color);border-left-color:var(--triangle-color);border-right-color:transparent;left:auto;right:var(--message-triangle-offset)}.typing-user{text-align:right;margin:10px 15px}.message-input{background:#fff;color:var(--secondary-color);-webkit-box-sizing:border-box;box-sizing:border-box;height:var(--footer-height)}.message-input .message-form{height:100%;width:100%;display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between}.message-input .message-form .form-control{padding-top:24px;padding-bottom:24px;resize:none;padding-left:15px;-webkit-box-sizing:border-box;box-sizing:border-box;width:80%;height:100%;border:none}.message-input .message-form .form-control::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,.3);background-color:var(--main-text-color)}.message-input .message-form .form-control::-webkit-scrollbar{width:5px;background-color:green}.message-input .message-form .form-control::-webkit-scrollbar-thumb{background-color:var(--secondary-color)}.message-input .message-form .form-control:focus{outline:none}.message-input .message-form .send{width:20%;-webkit-box-sizing:border-box;box-sizing:border-box;font-size:1.25em;text-align:center;border:none;height:100%;color:var(--sent-message-color);background:var(--primary-color);-webkit-transition:all .35s ease-out;-o-transition:all .35s ease-out;transition:all .35s ease-out}.message-input .message-form .send:disabled{opacity:.2;background:var(--primary-color-light)}
2 | /*# sourceMappingURL=main.1d762d0c.css.map*/
--------------------------------------------------------------------------------
/docs/static/css/main.1d762d0c.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["styles/index.css"],"names":[],"mappings":"AAAA,MACE,wBACA,6BACA,8BACA,+BACA,gCACA,0BACA,0BACA,sBACA,iCACA,0BACA,iCACA,uBAAyB,CAE3B,gBACE,SACA,UACA,uBACA,WAAa,CAEf,eACE,iBAAmB,CAErB,OACE,WACA,YAKA,sBACI,kBAAoB,CAE1B,mBAPE,oBACA,aACA,qBACI,sBAAwB,CAUC,YAD7B,0BACI,qBAAuB,CAC3B,kBACE,cACA,eACA,gBACA,iBACA,kBACA,qBACA,0BACA,wBACA,sDACA,oCACA,+BACA,2BAA6B,CAC7B,wBACE,cACA,mDACA,YAAc,CAEpB,WACE,4BACA,6BACA,oCACA,kCACA,qBACA,gBAAkB,CAEpB,WACE,6BACA,+BACA,2EACA,iBACA,qBACA,6BACA,oBACA,aACA,uBACI,mBACJ,qBACI,uBACJ,YACA,UAAY,CAEd,UAGE,aACA,YAGA,yBACI,6BACJ,0BACI,qBAAuB,CAC3B,6BAVA,8BACQ,sBAGR,oBACA,YAAc,CAkB4B,mBATxC,4BASA,oCAAsC,CACxC,qCAXE,4BAIA,uBACI,mBACJ,sBACI,mBACJ,sBACI,6BAA+B,CAmBJ,kBAhB/B,gCACA,8BACQ,sBACR,oBACA,aAOA,mBACA,mBACA,kBACA,kBACA,kBAAoB,CAEpB,+BACE,kBACA,cAAgB,CAClB,wBACE,WACA,gCACA,oBACI,YACJ,8BACQ,sBACR,YACA,4BAA8B,CAC9B,8BACE,YAAc,CAChB,mDACE,6BACA,UAAY,CACd,8CACE,6BACA,UAAY,CACd,qCACE,6BACA,UAAY,CAChB,wBACE,iBACA,mBACA,qBACA,kBACA,eACA,WACA,WAAa,CACb,6DACE,WACA,kBACA,wCAA0C,CAC5C,8BACE,yBACA,yBACA,OAAS,CACX,+BACE,wBACA,0BACA,SACA,QAAU,CAChB,iBACE,kBACA,gCACA,oBACI,WAAa,CACjB,0CACE,gDACA,uCAAyC,CAC3C,oCACE,UACA,sBAAwB,CAC1B,0CACE,uCAAyC,CAC3C,uBACE,sBACA,uCACA,sBACI,mBACJ,oBACA,aACA,YACA,oBACI,2BACJ,2BAA6B,CAC7B,2DACE,oCAAsC,CACxC,0CACE,cAAgB,CAClB,kCACE,iBACA,oBACI,WAAa,CACjB,gDACE,eACA,WAAa,CACjB,oCACE,YAKA,qBACI,sBAAwB,CAClC,4DANM,oBACA,aACA,sBACI,kBAAoB,CAiBoB,wBAXhD,kCACA,8BACQ,sBAGR,uBACI,mBACJ,gCACA,sBACI,8BACJ,4CACA,4CAA8C,CAC9C,gCAGE,eAGA,qBACI,uBACJ,aAAe,CAErB,6CATM,sBACI,mBAEJ,oBACA,YAAc,CAkBG,aAZrB,sCACA,oDACQ,4CACR,8BACQ,sBAGR,sBACI,8BAGJ,4BACA,iBAAmB,CACnB,wBACE,sBACI,mBACJ,oBACA,YAAc,CACd,mCACE,iBAAmB,CACrB,gCACE,qBACA,sBACI,mBACJ,oBACA,YAAc,CACd,wCACE,yBAA2B,CAC7B,yCACE,yBAA2B,CACjC,sBACE,oBACA,aACA,sBACI,mBACJ,uBACI,mBACJ,yBACI,6BACJ,YACA,SAAW,CACX,0BACE,cAAgB,CAEtB,qBACE,YACA,YAAc,CAEhB,WACE,oBACA,aACA,0BACI,sBACJ,sBACI,8BACJ,YACA,UAAY,CACZ,kBACE,sBACI,mBACJ,qBACI,uBACJ,aAAe,CAEnB,kBACE,oBACI,YACJ,kBACA,iBAAmB,CACnB,2CACE,gDACA,uCAAyC,CAC3C,qCACE,UACA,sBAAwB,CAC1B,2CACE,uCAAyC,CAC3C,0BACE,kBACA,WACA,iBACA,oBACA,aACA,0BACI,sBACJ,kBACI,yBACJ,oCACA,6BAA+B,CAEnC,mBACE,oBACA,aACA,oBACI,2BACJ,gBACA,iBACA,+BACA,wCACQ,+BAAiC,CACzC,yBACE,iBACI,OAAS,CACf,yBACE,iBACI,QACJ,YACA,gBAAkB,CACpB,yBACE,gBACA,eACA,gBAAkB,CACpB,4BACE,4CACA,kBACA,yBACA,8BACQ,sBACR,oCACA,YACA,kBACA,iBAAmB,CACvB,wBACE,GACE,SAAW,CACb,GACE,SAAW,CAAE,CACjB,gBACE,GACE,SAAW,CACb,GACE,SAAW,CAAE,CACf,mCACE,kDACA,gCACA,8BACA,yCACA,mBACA,uCACA,iBACA,WACA,SACA,oCACA,kBACA,MACA,OAAS,CACX,yBACE,iBACA,kBACI,wBAA0B,CAC9B,+BACE,iBACI,QACJ,gBAAkB,CACpB,+BACE,cACA,iBACI,OAAS,CACf,+BACE,YAAc,CAChB,kCACE,wCACA,gCACA,0BACA,0BAA4B,CAC9B,yCACE,8CACA,uCACA,wCACA,+BACA,UACA,oCAAsC,CAE5C,aACE,iBACA,gBAAkB,CAEpB,eACE,gBACA,6BACA,8BACQ,sBACR,2BAA6B,CAC7B,6BACE,YACA,WACA,oBACA,aACA,sBACI,6BAA+B,CACnC,2CACE,iBACA,oBACA,YACA,kBACA,8BACQ,sBACR,UACA,YACA,WAAa,CACb,oEACE,gDACA,uCAAyC,CAC3C,8DACE,UACA,sBAAwB,CAC1B,oEACE,uCAAyC,CAC3C,iDACE,YAAc,CAClB,mCACE,UACA,8BACQ,sBACR,iBACA,kBACA,YACA,YACA,gCACA,gCACA,qCACA,gCACA,4BAA8B,CAC9B,4CACE,WACA,qCAAuC","file":"static/css/main.1d762d0c.css","sourcesContent":[":root {\n --primary-color: #3e3e5e;\n --primary-color-dark: #2e2e4f;\n --primary-color-light: #5d5d8a;\n --primary-color-active: #363656;\n --sent-message-bg-color: #89a1fc;\n --secondary-color: #484d79;\n --main-text-color: #cac8ee;\n --messages-bg: #dcddf5;\n --received-message-bg-color: #fff;\n --sent-message-color: #fff;\n --received-message-color: #b3b2ca;\n --success-color: #60d66a; }\n\nhtml, body, #root {\n margin: 0;\n padding: 0;\n font-family: sans-serif;\n height: 100%; }\n\ninput, textarea {\n font-family: Arial; }\n\n.login {\n width: 100%;\n height: 100%;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-pack: center;\n justify-content: center;\n -ms-flex-align: center;\n align-items: center; }\n\n.login-form {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-pack: center;\n justify-content: center;\n -ms-flex-direction: column;\n flex-direction: column; }\n .login-form input {\n --height: 20px;\n max-width: 100%;\n border-top: none;\n border-left: none;\n border-right: none;\n height: var(--height);\n line-height: var(--height);\n font-size: var(--height);\n border-bottom: solid 2px var(--received-message-color);\n -webkit-transition: all .23s ease-in;\n -o-transition: all .23s ease-in;\n transition: all .23s ease-in; }\n .login-form input:focus {\n --height: 30px;\n border-bottom: solid 2px var(--primary-color-light);\n outline: none; }\n\n.indicator {\n width: var(--indicator-size);\n height: var(--indicator-size);\n border-radius: var(--indicator-size);\n background: var(--indicator-color);\n display: inline-block;\n margin-right: 8px; }\n\n.container {\n --main-padding-vertical: 18px;\n --main-padding-horizontal: 16px;\n --main-padding: var(--main-padding-vertical) var(--main-padding-horizontal);\n --head-size: 65px;\n --footer-height: 65px;\n color: var(--main-text-color);\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: row;\n flex-direction: row;\n -ms-flex-align: start;\n align-items: flex-start;\n height: 100%;\n width: 100%; }\n\n#side-bar {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n width: 33.75%;\n height: 100%;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-pack: distribute;\n justify-content: space-around;\n -ms-flex-direction: column;\n flex-direction: column; }\n #side-bar .heading {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n min-height: var(--head-size);\n padding: var(--main-padding);\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: row;\n flex-direction: row;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: justify;\n justify-content: space-between;\n background: var(--primary-color-dark); }\n #side-bar .search {\n background: var(--primary-color);\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: row;\n flex-direction: row;\n -ms-flex-pack: justify;\n justify-content: space-between;\n -ms-flex-align: center;\n align-items: center;\n border-width: 1px 0;\n border-style: solid;\n border-color: black;\n padding-left: 15px;\n padding-right: 20px;\n min-height: var(--head-size); }\n #side-bar .search .search-icon {\n margin-right: 15px;\n cursor: pointer; }\n #side-bar .search input {\n width: 100%;\n background: var(--primary-color);\n -ms-flex-positive: 1;\n flex-grow: 1;\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n border: none;\n color: var(--main-text-color); }\n #side-bar .search input:focus {\n outline: none; }\n #side-bar .search input::-webkit-input-placeholder {\n color: var(--main-text-color);\n opacity: .6; }\n #side-bar .search input:-ms-input-placeholder {\n color: var(--main-text-color);\n opacity: .6; }\n #side-bar .search input::placeholder {\n color: var(--main-text-color);\n opacity: .6; }\n #side-bar .search .plus {\n --plus-width: 2px;\n --plus-length: 16px;\n display: inline-block;\n position: relative;\n cursor: pointer;\n width: 13px;\n height: 13px; }\n #side-bar .search .plus::after, #side-bar .search .plus::before {\n content: '';\n position: absolute;\n background: var(--received-message-color); }\n #side-bar .search .plus::after {\n width: var(--plus-length);\n height: var(--plus-width);\n top: 5px; }\n #side-bar .search .plus::before {\n width: var(--plus-width);\n height: var(--plus-length);\n top: -2px;\n left: 7px; }\n #side-bar .users {\n overflow-y: scroll;\n background: var(--primary-color);\n -ms-flex-positive: 1;\n flex-grow: 1; }\n #side-bar .users::-webkit-scrollbar-track {\n -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);\n background-color: var(--main-text-color); }\n #side-bar .users::-webkit-scrollbar {\n width: 5px;\n background-color: green; }\n #side-bar .users::-webkit-scrollbar-thumb {\n background-color: var(--secondary-color); }\n #side-bar .users .user {\n --indicator-size: 16px;\n --indicator-color: var(--success-color);\n -ms-flex-align: center;\n align-items: center;\n display: -ms-flexbox;\n display: flex;\n height: 66px;\n -ms-flex-pack: start;\n justify-content: flex-start;\n padding: var(--main-padding); }\n #side-bar .users .user.active, #side-bar .users .user:hover {\n background: var(--primary-color-dark); }\n #side-bar .users .user:hover:not(.active) {\n cursor: pointer; }\n #side-bar .users .user .user-info {\n margin-left: 15px;\n -ms-flex-positive: 1;\n flex-grow: 1; }\n #side-bar .users .user .user-info .last-message {\n font-size: 12px;\n opacity: .56; }\n #side-bar .users .user .new-message {\n height: 100%;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center; }\n #side-bar .current-user {\n -ms-flex-align: center;\n align-items: center;\n background: var(--secondary-color);\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: row;\n flex-direction: row;\n min-height: var(--footer-height);\n -ms-flex-pack: justify;\n justify-content: space-between;\n padding-left: var(--main-padding-horizontal);\n padding-right: var(--main-padding-horizontal); }\n #side-bar .current-user .logout {\n -ms-flex-align: center;\n align-items: center;\n cursor: pointer;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-pack: center;\n justify-content: center;\n font-size: 2em; }\n\n.chat-header {\n background: var(--primary-color-light);\n -webkit-box-shadow: 0px 6px 5px -2px rgba(225, 225, 225, 0.7);\n box-shadow: 0px 6px 5px -2px rgba(225, 225, 225, 0.7);\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-pack: justify;\n justify-content: space-between;\n -ms-flex-align: center;\n align-items: center;\n min-height: var(--head-size);\n padding: 18px 16px; }\n .chat-header .user-info {\n -ms-flex-align: center;\n align-items: center;\n display: -ms-flexbox;\n display: flex; }\n .chat-header .user-info .user-name {\n margin-right: 10px; }\n .chat-header .user-info .status {\n --indicator-size: 8px;\n -ms-flex-align: center;\n align-items: center;\n display: -ms-flexbox;\n display: flex; }\n .chat-header .user-info .status .online {\n --indicator-color: #32b0bb; }\n .chat-header .user-info .status .offline {\n --indicator-color: #C63D2C; }\n .chat-header .options {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-direction: row;\n flex-direction: row;\n -ms-flex-pack: distribute;\n justify-content: space-around;\n height: 100%;\n width: 15%; }\n .chat-header .options svg {\n cursor: pointer; }\n\n.chat-room-container {\n height: 100%;\n width: 76.25%; }\n\n.chat-room {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n -ms-flex-pack: justify;\n justify-content: space-between;\n height: 100%;\n width: 100%; }\n .chat-room.choose {\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n font-size: 2em; }\n\n.thread-container {\n -ms-flex-positive: 1;\n flex-grow: 1;\n overflow-y: scroll;\n position: relative; }\n .thread-container::-webkit-scrollbar-track {\n -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);\n background-color: var(--main-text-color); }\n .thread-container::-webkit-scrollbar {\n width: 5px;\n background-color: green; }\n .thread-container::-webkit-scrollbar-thumb {\n background-color: var(--secondary-color); }\n .thread-container .thread {\n position: relative;\n width: 100%;\n min-height: 800px;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n -ms-flex-pack: end;\n justify-content: flex-end;\n color: var(--received-message-color);\n background: var(--messages-bg); }\n\n.message-container {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-pack: start;\n justify-content: flex-start;\n min-height: 50px;\n margin: 10px 15px;\n --message-triangle-offset: -7px;\n -webkit-animation: .65s ease-out 0s show;\n animation: .65s ease-out 0s show; }\n .message-container .time {\n -ms-flex-order: 1;\n order: 1; }\n .message-container .data {\n -ms-flex-order: 2;\n order: 2;\n height: 100%;\n margin-left: 25px; }\n .message-container .name {\n font-size: .65em;\n margin-top: 5px;\n text-align: right; }\n .message-container .message {\n background: var(--received-message-bg-color);\n border-radius: 5px;\n border-top-left-radius: 0;\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n color: var(--received-message-color);\n height: 100%;\n padding: 10px 15px;\n position: relative; }\n@-webkit-keyframes show {\n 0% {\n opacity: 0; }\n 100% {\n opacity: 1; } }\n@keyframes show {\n 0% {\n opacity: 0; }\n 100% {\n opacity: 1; } }\n .message-container .message::before {\n --triangle-color: var(--received-message-bg-color);\n border-bottom-color: transparent;\n border-left-color: transparent;\n border-right-color: var(--triangle-color);\n border-style: solid;\n border-top-color: var(--triangle-color);\n border-width: 4px;\n content: '';\n height: 0;\n left: var(--message-triangle-offset);\n position: absolute;\n top: 0;\n width: 0; }\n .message-container.right {\n text-align: right;\n -ms-flex-pack: end;\n justify-content: flex-end; }\n .message-container.right .time {\n -ms-flex-order: 2;\n order: 2;\n margin-left: 25px; }\n .message-container.right .data {\n margin-left: 0;\n -ms-flex-order: 1;\n order: 1; }\n .message-container.right .name {\n display: none; }\n .message-container.right .message {\n background: var(--sent-message-bg-color);\n color: var(--sent-message-color);\n border-top-right-radius: 0;\n border-top-left-radius: 5px; }\n .message-container.right .message::before {\n --triangle-color: var(--sent-message-bg-color);\n border-top-color: var(--triangle-color);\n border-left-color: var(--triangle-color);\n border-right-color: transparent;\n left: auto;\n right: var(--message-triangle-offset); }\n\n.typing-user {\n text-align: right;\n margin: 10px 15px; }\n\n.message-input {\n background: white;\n color: var(--secondary-color);\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n height: var(--footer-height); }\n .message-input .message-form {\n height: 100%;\n width: 100%;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-pack: justify;\n justify-content: space-between; }\n .message-input .message-form .form-control {\n padding-top: 24px;\n padding-bottom: 24px;\n resize: none;\n padding-left: 15px;\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n width: 80%;\n height: 100%;\n border: none; }\n .message-input .message-form .form-control::-webkit-scrollbar-track {\n -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);\n background-color: var(--main-text-color); }\n .message-input .message-form .form-control::-webkit-scrollbar {\n width: 5px;\n background-color: green; }\n .message-input .message-form .form-control::-webkit-scrollbar-thumb {\n background-color: var(--secondary-color); }\n .message-input .message-form .form-control:focus {\n outline: none; }\n .message-input .message-form .send {\n width: 20%;\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n font-size: 1.25em;\n text-align: center;\n border: none;\n height: 100%;\n color: var(--sent-message-color);\n background: var(--primary-color);\n -webkit-transition: all .35s ease-out;\n -o-transition: all .35s ease-out;\n transition: all .35s ease-out; }\n .message-input .message-form .send:disabled {\n opacity: .2;\n background: var(--primary-color-light); }\n\n\n\n// WEBPACK FOOTER //\n// ./src/styles/index.css"],"sourceRoot":""}
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-chat-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "prop-types": "^15.5.10",
7 | "react": "^15.6.1",
8 | "react-dom": "^15.6.1",
9 | "react-icons": "^2.2.5",
10 | "react-scripts": "1.0.11",
11 | "socket.io": "^2.0.3",
12 | "uuid": "^3.1.0"
13 | },
14 | "scripts": {
15 | "start":"concurrently 'npm run r-start' 'npm run nodemon' 'npm run styles'",
16 | "r-start": "react-scripts start",
17 | "nodemon": "nodemon src/server/server.js",
18 | "styles": "sass --watch --style compressed src/styles/index.scss:src/styles/index.css",
19 | "build": "react-scripts build",
20 | "test": "react-scripts test --env=jsdom",
21 | "eject": "react-scripts eject"
22 | },
23 | "devDependencies": {
24 | "concurrently": "^3.5.0",
25 | "nodemon": "^1.11.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leonwatson2/React-Chat-App/d96e718a544ae790e8591dd53dc5029d740b38b2/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | React Chat App using Socket.io
11 |
12 |
13 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Layout from './components/Layout'
3 | import './styles/index.css'
4 |
5 | class App extends Component {
6 |
7 | render() {
8 | return (
9 |
10 | );
11 | }
12 | }
13 |
14 | export default App;
15 |
--------------------------------------------------------------------------------
/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/Classes.js:
--------------------------------------------------------------------------------
1 | const uuidv4 = require('uuid/v4');
2 |
3 | /*
4 | * @prop id {string}
5 | * @prop name {string}
6 | * @param {object}
7 | * name {string}
8 | */
9 | const createUser = ({name})=>(
10 | {
11 | id: uuidv4(),
12 | name
13 | }
14 | )
15 |
16 | /*
17 | * Creates a messages object.
18 | * @prop id {string}
19 | * @prop time {Date} the time in 24hr format i.e. 14:22
20 | * @prop message {string} actual string message
21 | * @prop sender {string} sender of the message
22 | * @param {object}
23 | * message {string}
24 | * sender {string}
25 | */
26 | const createMessage = ({message, sender})=>{
27 | return {
28 | id: uuidv4(),
29 | time: getTime(new Date(Date.now())),
30 | message: message,
31 | sender: sender
32 | }
33 | }
34 |
35 | /*
36 | * Creates a Chat object
37 | * @prop id {string}
38 | * @prop name {string}
39 | * @prop messages {Array.Message}
40 | * @prop users {Array.string}
41 | * @prop addMessage {function} adds message to chat.
42 | * @prop addTypingUser {function} adds a username to typing users of chat.
43 | * @prop removeTypingUser {function} removes a username to typing users of chat.
44 | * @param {object}
45 | * messages {Array.Message}
46 | * name {string}
47 | * users {Array.string}
48 | *
49 | */
50 | const createChat = ({messages = [], name="Community", users=[]} = {})=>(
51 | {
52 | id: uuidv4(),
53 | name,
54 | messages,
55 | users,
56 | typingUsers: [],
57 |
58 | addMessage: (messages, message)=>{
59 | return [...messages, message]
60 | },
61 | addTypingUser: (typingUsers, username)=>{
62 | return [...typingUsers, username]
63 | },
64 | removeTypingUser: (typingUsers, username) => {
65 | return typingUsers.filter((u)=>u === username)
66 |
67 | }
68 | }
69 | )
70 |
71 | const getTime = (date)=>{
72 | return `${date.getHours()}:${("0"+date.getMinutes()).slice(-2)}`
73 | }
74 |
75 | module.exports = {
76 | createChat,
77 | createMessage,
78 | createUser
79 | }
--------------------------------------------------------------------------------
/src/Constants.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | COMMUNITY_CHAT:"COMMUNITY_CHAT",
3 | USER_CONNECTED:"USER_CONNECTED",
4 | MESSAGE_RECIEVED:"MESSAGE_RECIEVED",
5 | MESSAGE_SENT:"MESSAGE_SENT",
6 | USER_DISCONNECTED:"USER_DISCONNECTED",
7 | TYPING:"TYPING",
8 | VERIFY_USER:"VERIFY_USER",
9 | LOGOUT:"LOGOUT"
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/Layout.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import LoginForm from './LoginForm'
3 | import ChatContainer from './chat/ChatContainer'
4 | import { USER_CONNECTED, LOGOUT } from '../Constants'
5 |
6 | var serverURI = process.env.REACT_APP_SERVER
7 | var io = require('socket.io-client')
8 |
9 | export default class Layout extends Component {
10 | constructor(props) {
11 | super(props);
12 |
13 | this.state = {
14 | socket:null,
15 | user:null
16 | };
17 | this.setUser = this.setUser.bind(this)
18 | this.logout = this.logout.bind(this)
19 | this.reconnectUserInfo = this.reconnectUserInfo.bind(this)
20 | }
21 |
22 | componentWillMount() {
23 |
24 | var socket = io(serverURI)
25 | this.setState({ socket })
26 | this.initSocket(socket)
27 | }
28 |
29 | /*
30 | * Initializes socket event callbacks
31 | */
32 | initSocket(socket){
33 | socket.on('connect', (value)=>{
34 | console.log("Connected");
35 | })
36 | socket.on('disconnect', this.reconnectUserInfo)
37 | }
38 |
39 | /*
40 | * Connectes user info back to the server.
41 | * If the user name is already logged in.
42 | */
43 | reconnectUserInfo(){
44 | const { socket, user } = this.state
45 |
46 | if(this.state.user != null){
47 |
48 | socket.emit(USER_CONNECTED, user)
49 | }
50 |
51 | }
52 |
53 | /*
54 | * Sets the current user logged in
55 | * @param user an object {id:number, name:string}
56 | */
57 | setUser(user){
58 | const { socket } = this.state
59 | this.setState({user});
60 | socket.emit(USER_CONNECTED, user)
61 | }
62 |
63 | /*
64 | * Sets the user to null.
65 | */
66 | logout(){
67 | const { socket } = this.state
68 | socket.emit(LOGOUT)
69 | this.setState({user:null})
70 | }
71 | render() {
72 | const { user, socket } = this.state
73 |
74 | return (
75 |
76 |
77 | {
78 | !user ?
79 |
80 | :
81 |
82 | }
83 |
84 |
85 | );
86 | }
87 | }
88 |
89 |
--------------------------------------------------------------------------------
/src/components/LoginForm.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types'
3 | import { VERIFY_USER } from '../Constants'
4 |
5 | export default class LoginForm extends Component {
6 |
7 | constructor(props) {
8 | super(props);
9 | this.state = { nickname: "Love", error:"" };
10 |
11 | this.handleChange = this.handleChange.bind(this);
12 | this.handleSubmit = this.handleSubmit.bind(this);
13 | this.setUser = this.setUser.bind(this);
14 | }
15 |
16 | componentDidMount(){
17 | this.focus()
18 | }
19 |
20 |
21 | setUser(response){
22 | if(!response.isUser){
23 | this.props.setUser(response.user)
24 | }
25 | else{
26 | this.setError("User name taken.")
27 | }
28 |
29 | }
30 |
31 | setError(error){
32 | this.setState({error});
33 | }
34 |
35 | //updates form inputs
36 | handleChange(event){
37 | this.setState({ nickname:event.target.value })
38 | }
39 |
40 | //Sends emit to socket for verification
41 | handleSubmit(event){
42 | event.preventDefault()
43 | const { socket } = this.props
44 | const { nickname } = this.state
45 | socket.emit(VERIFY_USER, nickname, this.setUser)
46 |
47 | }
48 |
49 | //focus on input
50 | focus(){
51 | this.textInput.focus()
52 | }
53 |
54 | render() {
55 | const { nickname, error } = this.state
56 | return (
57 | // .login>form.login-form>((label[for=nickname]>h1{Got a nickname?})+input[value][onChange][placeholder=Leon])
58 |
79 | );
80 | }
81 |
82 | randomPlaceholder(){
83 | const randNames = ["VeryCleverNickNameThatsProbablyAlreadyTaken", "Rocket69", "MadDog33", "L4ser9374", "UmmmMyName134", "YouDontNayOmi","SimpleName", "SexyCat99", "LightYear111"]
84 | return randNames[Math.floor(Math.random()*3000) % randNames.length]
85 | }
86 | }
87 |
88 | LoginForm.propTypes = {
89 | socket: PropTypes.object,
90 | verified:PropTypes.func.isRequired
91 | }
92 |
--------------------------------------------------------------------------------
/src/components/UsersInChatRoom.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import C from '../Constants'
3 |
4 | export default class UsersIndChatRoom extends Component {
5 |
6 | constructor(props) {
7 | super(props);
8 |
9 | this.state = { onlineUsers:[], typingUsers:[] };
10 | this.updateOnlineUsers = this.updateOnlineUsers.bind(this)
11 | this.initSocket()
12 | }
13 |
14 | initSocket(){
15 | const { socket } = this.props
16 | socket.on(C.USER_CONNECTED, this.updateOnlineUsers)
17 |
18 | }
19 |
20 | updateOnlineUsers(onlineUsers){
21 | this.setState({onlineUsers});
22 | }
23 |
24 | render() {
25 | const { onlineUsers, typingUsers } = this.state
26 | return (
27 |
28 |
31 |
32 |
33 |
Users Online
34 |
35 | {
36 | Object.keys(onlineUsers).map((key)=>{
37 | return
{onlineUsers[key].username}
38 | })
39 | }
40 | {
41 | typingUsers.map((user, i)=>{
42 | return
{user}{' is typing. . .'}
43 | })
44 | }
45 |
46 |
47 |
48 | );
49 | }
50 | }
51 |
52 | // const temp = (
53 |
54 |
55 | // )
--------------------------------------------------------------------------------
/src/components/chat/ChatContainer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types'
3 |
4 | import SideBar from './SideBar'
5 |
6 | import { User } from '../../Classes'
7 | import Messages from '../messaging/Messages'
8 | import MessageInput from '../messaging/MessageInput'
9 | import ChatHeading from './ChatHeading'
10 |
11 | import { COMMUNITY_CHAT, MESSAGE_RECIEVED, MESSAGE_SENT, TYPING } from '../../Constants'
12 |
13 | export default class ChatContainer extends Component {
14 |
15 | constructor(props) {
16 | super(props);
17 |
18 | this.state = {
19 | activeChat:null,
20 | communityChat:null,
21 | chats:[],
22 | };
23 | this.resetChat = this.resetChat.bind(this)
24 | this.removeSocketEvents = this.removeSocketEvents.bind(this)
25 | this.socketEvents = [] //used to deinitialize socket events later
26 | }
27 |
28 | componentDidMount() {
29 | const { socket } = this.props
30 | socket.emit(COMMUNITY_CHAT, this.resetChat)
31 | this.initSocket()
32 | }
33 |
34 | componentWillUnmount() {
35 | this.deinitialize()
36 | }
37 |
38 | /*
39 | * Initializes the socket.
40 | */
41 | initSocket(){
42 | const { socket } = this.props
43 | socket.on('connect', ()=>{
44 | socket.emit(COMMUNITY_CHAT, this.resetChat)
45 | })
46 | }
47 |
48 | deinitialize(){
49 | const { socket } = this.props
50 | this.removeSocketEvents(socket, this.socketEvents)
51 | }
52 |
53 | /*
54 | * Removes chat event listeners on socket.
55 | */
56 | removeSocketEvents(socket, events){
57 |
58 | if(events.length > 0){
59 | socket.off(events[0])
60 | this.removeSocketEvents(socket, events.slice(1))
61 | }
62 | }
63 |
64 | /*
65 | * Reset the chat back to only the chat passed in.
66 | * @param chat {Chat}
67 | */
68 | resetChat(chat){
69 | return this.addChat(chat, true)
70 | }
71 |
72 | /*
73 | * Adds chat to the chat container, if reset is true removes all chats
74 | * and sets that chat to the main chat.
75 | * Sets the message and typing socket events for the chat.
76 | *
77 | * @param chat {Chat} the chat to be added.
78 | * @param reset {boolean} if true will set the chat as the only chat.
79 | */
80 | addChat(chat, reset){
81 | const { socket } = this.props
82 | const { chats } = this.state
83 | const newChats = reset ? [chat] : [...chats, chat]
84 |
85 | this.setState({chats:newChats, activeChat:chat})
86 |
87 | const messageEvent = `${MESSAGE_RECIEVED}-${chat.id}`
88 | const typingEvent = `${TYPING}-${chat.id}`
89 |
90 | socket.on(messageEvent, this.addMessageToChat(chat.id))
91 | socket.on(typingEvent, this.updateTypingInChat(chat.id))
92 |
93 | this.socketEvents.push(messageEvent, typingEvent) // used to remove event listerners
94 | }
95 |
96 | /*
97 | * Adds message to chat
98 | * @param chatId {number}
99 | */
100 | addMessageToChat(chatId){
101 | return message =>{
102 | const { chats } = this.state
103 | let newChats = chats.map((chat) => {
104 | if(chat.id === chatId)
105 | chat.messages.push(message)
106 | return chat;
107 | })
108 | this.setState({chats:newChats})
109 | }
110 | }
111 |
112 | /*
113 | * Updates the typing of chat with id passed in.
114 | * @param chatId {number}
115 | */
116 | updateTypingInChat(chatId){
117 | return ({isTyping, user}) =>{
118 | if(user !== this.props.user.name){
119 |
120 | const { chats } = this.state
121 | let newChats = chats.map((chat) => {
122 | if(chat.id === chatId){
123 | if(isTyping && !chat.typingUsers.includes(user))
124 | chat.typingUsers.push(user)
125 | else if(!isTyping && chat.typingUsers.includes(user))
126 | chat.typingUsers = chat.typingUsers.filter(u => u !== user)
127 | }
128 | return chat;
129 | })
130 | this.setState({chats:newChats})
131 | }
132 | }
133 | }
134 |
135 | /*
136 | * Adds a message to the specified chat
137 | * @param chatId {number} The id of the chat to be added to.
138 | * @param message {string} The message to be added to the chat.
139 | */
140 | sendMessage(chatId, message){
141 |
142 | const { socket } = this.props
143 |
144 | socket.emit(MESSAGE_SENT, {chatId, message})
145 |
146 | }
147 |
148 | /*
149 | * Sends typing status to server.
150 | * chatId {number} the id of the chat being typed in.
151 | * typing {boolean} If the user is typing still or not.
152 | */
153 | sendTyping(chatId, isTyping){
154 |
155 | const { socket } = this.props
156 | socket.emit(TYPING, {chatId, isTyping})
157 |
158 | }
159 |
160 | /*
161 | * Set the active the chat of the ChatRoom.
162 | * @param {Chat} The chat object to that is active.
163 | */
164 | setActiveChat(chat){
165 | this.setState({activeChat:chat})
166 | }
167 |
168 | render() {
169 | const { user, logout } = this.props
170 | const { activeChat, chats } = this.state
171 | return (
172 |
173 |
this.setActiveChat(chat) }/>
179 |
180 |
181 | {
182 | activeChat !== null ? (
183 |
184 |
187 |
191 |
192 | {
195 | this.sendMessage(activeChat.id, message)
196 | }
197 | }
198 | sendTyping={
199 | (isTyping)=>{
200 | this.sendTyping(activeChat.id, isTyping)
201 | }
202 | }
203 | />
204 |
205 | )
206 | :
207 |
208 |
Choose a chat
209 |
210 | }
211 |
212 |
213 | );
214 | }
215 | }
216 |
217 | ChatContainer.propTypes = {
218 | socket:PropTypes.object,
219 | user:PropTypes.shape(User).isRequired
220 | }
--------------------------------------------------------------------------------
/src/components/chat/ChatHeading.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import FAVideo from 'react-icons/lib/fa/video-camera'
3 | import FAUserPlus from 'react-icons/lib/fa/user-plus'
4 | import MdEllipsisMenu from 'react-icons/lib/md/keyboard-control'
5 |
6 | export default class ChatHeading extends Component {
7 | render() {
8 | const { name, online, numberOfUsers } = this.props
9 | const onlineText = online ? 'online':'offline'
10 | return (
11 |
12 |
13 |
{name}
14 |
15 |
16 |
{numberOfUsers ? numberOfUsers : null} online
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 | }
27 | }
--------------------------------------------------------------------------------
/src/components/chat/SideBar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import FAChevronDown from 'react-icons/lib/md/keyboard-arrow-down'
3 | import FAMenu from 'react-icons/lib/fa/list-ul'
4 | import FASearch from 'react-icons/lib/fa/search'
5 | import MdEject from 'react-icons/lib/md/eject'
6 |
7 | export default class SideBar extends Component {
8 | render() {
9 | const { chats, activeChat, user, setActiveChat, logout } = this.props
10 | return (
11 |
12 |
13 |
Zanjo Chat
14 |
15 |
16 |
17 |
18 |
23 |
{ (e.target === this.refs.user) && setActiveChat(null) }}>
27 |
28 | {
29 | chats.map((chat)=>{
30 | if(chat.name){
31 | const lastMessage = chat.messages[chat.messages.length - 1];
32 | const user = chat.users.find(({name})=>{
33 | return name !== this.props.name
34 | }) || { name:"Community" }
35 | const classNames = (activeChat && activeChat.id === chat.id) ? 'active' : ''
36 | return(
37 |
{ setActiveChat(chat) } }
41 | >
42 |
{user.name[0].toUpperCase()}
43 |
44 |
{user.name}
45 | {lastMessage &&
{lastMessage.message}
}
46 |
47 |
48 |
49 | )
50 | }
51 |
52 | return null
53 | })
54 | }
55 |
56 |
57 |
58 |
{user.name}
59 |
{logout()}} title="Logout" className="logout">
60 |
61 |
62 |
63 |
64 | );
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/components/messaging/MessageInput.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | export default class MessageInput extends Component {
3 |
4 | constructor(props) {
5 | super(props);
6 |
7 | this.state = { message:"", isTyping:false};
8 | this.handleSubmit = this.handleSubmit.bind(this)
9 | this.sendMessage = this.sendMessage.bind(this)
10 | }
11 |
12 | /*
13 | * Handles submitting of form.
14 | * @param e {Event} onsubmit event
15 | */
16 | handleSubmit(e){
17 | e.preventDefault()
18 | this.sendMessage()
19 | this.setState({message:""})
20 | }
21 |
22 | /*
23 | * Send message to add to chat.
24 | */
25 | sendMessage(){
26 |
27 | this.props.sendMessage(this.state.message)
28 | this.blur()
29 | }
30 |
31 | componentWillUnmount() {
32 | this.stopCheckingTyping();
33 |
34 | }
35 |
36 | /*
37 | * Starts/Stops the interval for checking
38 | */
39 | sendTyping(){
40 | this.lastUpdateTime = Date.now()
41 | if(!this.state.isTyping){
42 | this.setState({isTyping:true})
43 | this.props.sendTyping(true);
44 | this.startCheckingTyping()
45 | }
46 | }
47 |
48 | /*
49 | * Start an interval that check if the user is typing
50 | */
51 | startCheckingTyping(){
52 | this.typingInterval = setInterval(()=>{
53 |
54 | if((Date.now() - this.lastUpdateTime) > 300){
55 | this.setState({isTyping:false})
56 | this.stopCheckingTyping()
57 | }
58 | }, 300)
59 | }
60 |
61 | /*
62 | * Stops the interval from checking if the user is typing
63 | */
64 | stopCheckingTyping(){
65 | if(this.typingInterval){
66 | clearInterval(this.typingInterval)
67 | this.props.sendTyping(false)
68 | }
69 | }
70 | blur = ()=>{
71 | this.refs.messageinput.blur()
72 | }
73 | render() {
74 | const { message } = this.state
75 | return (
76 |
77 |
102 |
103 |
104 | );
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/components/messaging/Messages.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | export default class Messages extends Component {
4 |
5 | constructor(props) {
6 | super(props);
7 | this.scrollDown = this.scrollDown.bind(this)
8 | }
9 |
10 |
11 | /*
12 | * Scrolls down the view of the messages.
13 | */
14 | scrollDown(){
15 | const { container } = this.refs
16 | container.scrollTop = container.scrollHeight
17 | }
18 |
19 | componentDidUpdate(newProps){
20 | this.scrollDown();
21 |
22 | }
23 |
24 | componentDidMount(){
25 | this.scrollDown();
26 | }
27 |
28 | render() {
29 | const { messages, user, typingUsers } = this.props;
30 | return (
31 |
33 |
34 | {
35 | messages.map((mes, i)=>{
36 |
37 | return(
38 |
39 |
{mes.time}
40 |
41 |
{mes.message}
42 |
{mes.sender}
43 |
44 |
)
45 | })
46 |
47 | }
48 | {
49 | typingUsers.map((name)=>{
50 | return(
51 |
52 | {`${name} is typing . . .`}
53 |
54 | )
55 | })
56 | }
57 |
58 |
59 |
60 | );
61 | }
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/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 |
6 | ReactDOM.render(, document.getElementById('root'));
7 | registerServiceWorker();
8 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (!isLocalhost) {
36 | // Is not local host. Just register service worker
37 | registerValidSW(swUrl);
38 | } else {
39 | // This is running on localhost. Lets check if a service worker still exists or not.
40 | checkValidServiceWorker(swUrl);
41 | }
42 | });
43 | }
44 | }
45 |
46 | function registerValidSW(swUrl) {
47 | navigator.serviceWorker
48 | .register(swUrl)
49 | .then(registration => {
50 | registration.onupdatefound = () => {
51 | const installingWorker = registration.installing;
52 | installingWorker.onstatechange = () => {
53 | if (installingWorker.state === 'installed') {
54 | if (navigator.serviceWorker.controller) {
55 | // At this point, the old content will have been purged and
56 | // the fresh content will have been added to the cache.
57 | // It's the perfect time to display a "New content is
58 | // available; please refresh." message in your web app.
59 | console.log('New content is available; please refresh.');
60 | } else {
61 | // At this point, everything has been precached.
62 | // It's the perfect time to display a
63 | // "Content is cached for offline use." message.
64 | console.log('Content is cached for offline use.');
65 | }
66 | }
67 | };
68 | };
69 | })
70 | .catch(error => {
71 | console.error('Error during service worker registration:', error);
72 | });
73 | }
74 |
75 | function checkValidServiceWorker(swUrl) {
76 | // Check if the service worker can be found. If it can't reload the page.
77 | fetch(swUrl)
78 | .then(response => {
79 | // Ensure service worker exists, and that we really are getting a JS file.
80 | if (
81 | response.status === 404 ||
82 | response.headers.get('content-type').indexOf('javascript') === -1
83 | ) {
84 | // No service worker found. Probably a different app. Reload the page.
85 | navigator.serviceWorker.ready.then(registration => {
86 | registration.unregister().then(() => {
87 | window.location.reload();
88 | });
89 | });
90 | } else {
91 | // Service worker found. Proceed as normal.
92 | registerValidSW(swUrl);
93 | }
94 | })
95 | .catch(() => {
96 | console.log(
97 | 'No internet connection found. App is running in offline mode.'
98 | );
99 | });
100 | }
101 |
102 | export function unregister() {
103 | if ('serviceWorker' in navigator) {
104 | navigator.serviceWorker.ready.then(registration => {
105 | registration.unregister();
106 | });
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/server/Procfile:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leonwatson2/React-Chat-App/d96e718a544ae790e8591dd53dc5029d740b38b2/src/server/Procfile
--------------------------------------------------------------------------------
/src/server/SocketManager.js:
--------------------------------------------------------------------------------
1 | const io = require('./server.js').io
2 | const {
3 | COMMUNITY_CHAT, MESSAGE_RECIEVED, MESSAGE_SENT,
4 | USER_CONNECTED, USER_DISCONNECTED, TYPING,
5 | STOP_TYPING, VERIFY_USER, LOGOUT
6 | } = require('../Constants')
7 | const { createUser, createChat, createMessage } = require('../Classes')
8 |
9 | let communityChat = createChat()
10 |
11 | let connectedUsers = {};
12 |
13 | let chats = [communityChat];
14 |
15 |
16 | module.exports = function(socket){
17 |
18 |
19 | let sendMessageToChatFromUser;
20 | let sendTypingFromUser;
21 |
22 | //Verify Username 1
23 | socket.on(VERIFY_USER, function(newUser, callback){
24 | if(!isUser(connectedUsers, newUser)){
25 |
26 | callback({isUser:false, user:createUser({name:newUser})})
27 |
28 | }else{
29 |
30 | callback({isUser:true})
31 |
32 | }
33 | })
34 |
35 | //userconnects 2
36 | socket.on(USER_CONNECTED, function(user){
37 |
38 | connectedUsers = addUser(connectedUsers, user)
39 | socket.user = user.name;
40 | sendMessageToChatFromUser = sendMessageToChat(user.name)
41 | sendTypingFromUser = sendTypingToChat(user.name)
42 |
43 | console.log(connectedUsers);
44 | io.emit(USER_CONNECTED, connectedUsers)
45 |
46 | })
47 |
48 |
49 | //user disconnects 3
50 | socket.on('disconnect', function (){
51 | if(!!socket.user){
52 | connectedUsers = removeUser(connectedUsers, socket.user)
53 |
54 | io.emit(USER_DISCONNECTED, connectedUsers)
55 | }
56 |
57 | })
58 |
59 | //user logout 4
60 | socket.on(LOGOUT, function(){
61 | connectedUsers = removeUser(connectedUsers, socket.user)
62 | })
63 |
64 | //send community chat 5
65 | socket.on(COMMUNITY_CHAT, function(callback){
66 | callback(communityChat)
67 | })
68 |
69 | //user sends message 6
70 | socket.on(MESSAGE_SENT, function({chatId, message}){
71 | sendMessageToChatFromUser(chatId, message)
72 | })
73 |
74 | //add user to typing users on chatId 7
75 | socket.on(TYPING, function({chatId, isTyping}){
76 |
77 | sendTypingFromUser(chatId, isTyping)
78 | })
79 |
80 | }
81 |
82 | /*
83 | * Returns a function that will take a chat id and message
84 | * and then emit a broadcast to the chat id.
85 | * @param sender {string} username of sender
86 | * @return function(chatId, message)
87 | */
88 | function sendMessageToChat(sender){
89 |
90 | return (chatId, message) => {
91 | io.emit(`${MESSAGE_RECIEVED}-${chatId}`, createMessage({message, sender}))
92 | }
93 | }
94 |
95 | /*
96 | * Returns a function that will take a chat id and boolean isTyping variable
97 | * and then emit a broadcast to the chat id that the sender is typing
98 | * @param sender {string} username of sender
99 | * @return function(chatId, isTyping)
100 | */
101 | function sendTypingToChat(user){
102 |
103 | return (chatId, isTyping)=>
104 | {
105 | io.emit(`${TYPING}-${chatId}`, {user, isTyping})
106 | }
107 | }
108 |
109 |
110 | /*
111 | * Adds user to list passed in.
112 | * @param userList {Object} Object with key value pairs of users
113 | * @param user {User} the user to added to the list.
114 | * @return userList {Object} Object with key value pairs of Users
115 | */
116 | function addUser(userList, user){
117 | let newList = Object.assign({}, userList)
118 | newList[user.name] = user
119 | return newList
120 | }
121 |
122 | /*
123 | * Removes user from the list passed in.
124 | * @param userList {Object} Object with key value pairs of Users
125 | * @param username {string} name of user to be removed
126 | * @return userList {Object} Object with key value pairs of Users
127 | */
128 | function removeUser(userList, username){
129 | let newList = Object.assign({}, userList)
130 | delete newList[username]
131 | return newList
132 | }
133 |
134 | /*
135 | * Checks if the user is in list passed in.
136 | * @param userList {Object} Object with key value pairs of Users
137 | * @param username {String}
138 | * @return userList {Object} Object with key value pairs of Users
139 | */
140 | function isUser(userList, username){
141 | return username in userList
142 | }
143 |
144 | function createError(message){
145 | return {
146 | error:{
147 | message
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "socket-io-chat-server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node server.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "socket.io": "^2.0.3",
14 | "uuid": "^3.1.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require(
2 | 'express'
3 | )
4 | const app = express()
5 | const socketio = require("socket.io");
6 |
7 | const PORT = process.env.PORT || 3231
8 |
9 | const server = require('http').createServer(app)
10 | const io = module.exports.io = socketio(server, {
11 | cors: {
12 | origin: "http://localhost:3000"
13 | }
14 | });
15 | const SocketManager = require('./SocketManager')
16 |
17 |
18 |
19 | app.get('/', function (req, res) {
20 | res.send('ok');
21 | })
22 | io.on('connection', SocketManager);
23 |
24 | app.listen(PORT, function () {
25 | console.log('listening on *:' + PORT);
26 | });
27 |
28 |
--------------------------------------------------------------------------------
/src/styles/.sass-cache/6af3623bcbcb565e309f0528766b9138bd675e55/index.scssc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leonwatson2/React-Chat-App/d96e718a544ae790e8591dd53dc5029d740b38b2/src/styles/.sass-cache/6af3623bcbcb565e309f0528766b9138bd675e55/index.scssc
--------------------------------------------------------------------------------
/src/styles/index.css:
--------------------------------------------------------------------------------
1 | html,body,#root{margin:0;padding:0;font-family:sans-serif;height:100%}input,textarea{font-family:Arial}.login{width:100%;height:100%;display:flex;justify-content:center;align-items:center}.login-form{display:flex;justify-content:center;flex-direction:column}.login-form input{max-width:100%;border-top:none;border-left:none;border-right:none;height:20px;line-height:20px;font-size:20px;border-bottom:solid 2px #b3b2ca;transition:all .23s ease-in}.login-form input:focus{border-bottom:solid 2px #5d5d8a;outline:none}.login-form .error{text-align:center;margin:5px 0;padding:5px 10px;color:#c92c43}.container{color:#cac8ee;display:flex;flex-direction:row;align-items:flex-start;height:100%;width:100%}#side-bar{box-sizing:border-box;width:33.75%;height:100%;display:flex;justify-content:space-around;flex-direction:column}#side-bar .heading{box-sizing:border-box;height:12vh;max-height:65px;padding:18px 16px;display:flex;flex-direction:row;align-items:center;justify-content:space-between;background:#2e2e4f}#side-bar .search{background:#3e3e5e;box-sizing:border-box;display:flex;flex-direction:row;justify-content:space-between;align-items:center;border-width:1px 0;border-style:solid;border-color:black;padding-left:15px;padding-right:20px;height:10vh;max-height:65px}#side-bar .search .search-icon{margin-right:15px;cursor:pointer}#side-bar .search input{width:100%;background:#3e3e5e;flex-grow:1;box-sizing:border-box;border:none;color:#cac8ee}#side-bar .search input:focus{outline:none}#side-bar .search input::placeholder{color:#cac8ee;opacity:.6}#side-bar .search .plus{display:inline-block;position:relative;cursor:pointer;width:13px;height:13px}#side-bar .search .plus::after,#side-bar .search .plus::before{content:'';position:absolute;background:#b3b2ca}#side-bar .search .plus::after{width:16px;height:2px;top:5px}#side-bar .search .plus::before{width:2px;height:16px;top:-2px;left:7px}#side-bar .users{overflow-y:scroll;background:#3e3e5e;flex-grow:1}#side-bar .users::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.3);background-color:#cac8ee}#side-bar .users::-webkit-scrollbar{width:5px;background-color:green}#side-bar .users::-webkit-scrollbar-thumb{background-color:#484d79}#side-bar .users .user{align-items:center;display:flex;height:66px;justify-content:flex-start;padding:18px 16px}#side-bar .users .user.active,#side-bar .users .user:hover{background:#2e2e4f}#side-bar .users .user:hover:not(.active){cursor:pointer}#side-bar .users .user .user-info{margin-left:15px;flex-grow:1}#side-bar .users .user .user-info .last-message{font-size:12px;opacity:.56}#side-bar .users .user .new-message{height:100%;display:flex;align-items:center;justify-content:center}#side-bar .current-user{align-items:center;background:#484d79;box-sizing:border-box;display:flex;flex-direction:row;height:10vh;max-height:65px;justify-content:space-between;padding-left:16px;padding-right:16px}#side-bar .current-user .logout{align-items:center;cursor:pointer;display:flex;justify-content:center;font-size:2em}.chat-header{background:#5d5d8a;box-shadow:0px 6px 5px -2px rgba(225,225,225,0.7);box-sizing:border-box;display:flex;justify-content:space-between;align-items:center;height:12vh;max-height:65px;padding:18px 16px}.chat-header .user-info{align-items:center;display:flex}.chat-header .user-info .user-name{margin-right:10px}.chat-header .user-info .status{align-items:center;display:flex}.chat-header .options{display:flex;align-items:center;flex-direction:row;justify-content:space-around;height:100%;width:15%}.chat-header .options svg{cursor:pointer}.chat-room-container{height:100%;width:76.25%}@media screen and (max-width: 510px){#side-bar{position:absolute;left:-100%}.chat-room-container{width:100%}}.chat-room{display:flex;flex-direction:column;justify-content:space-between;height:100%;width:100%}.chat-room.choose{align-items:center;justify-content:center;font-size:2em}.thread-container{flex-grow:1;overflow-y:scroll;position:relative}.thread-container::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.3);background-color:#cac8ee}.thread-container::-webkit-scrollbar{width:5px;background-color:green}.thread-container::-webkit-scrollbar-thumb{background-color:#484d79}.thread-container .thread{position:relative;width:100%;min-height:800px;display:flex;flex-direction:column;justify-content:flex-end;color:#b3b2ca;background:#dcddf5}.message-container{display:flex;justify-content:flex-start;min-height:50px;margin:10px 15px;animation:.65s ease-out 0s show}.message-container .time{order:1}.message-container .data{order:2;height:100%;margin-left:25px}.message-container .name{font-size:.65em;margin-top:5px;text-align:right}.message-container .message{background:#fff;border-radius:5px;border-top-left-radius:0;box-sizing:border-box;color:#b3b2ca;height:100%;padding:10px 15px;position:relative}@keyframes show{0%{opacity:0}100%{opacity:1}}.message-container .message::before{border-bottom-color:transparent;border-left-color:transparent;border-right-color:#fff;border-style:solid;border-top-color:#fff;border-width:4px;content:'';height:0;left:-7px;position:absolute;top:0;width:0}.message-container.right{text-align:right;justify-content:flex-end}.message-container.right .time{order:2;margin-left:25px}.message-container.right .data{margin-left:0;order:1}.message-container.right .name{display:none}.message-container.right .message{background:#89a1fc;color:#fff;border-top-right-radius:0;border-top-left-radius:5px}.message-container.right .message::before{border-top-color:#89a1fc;border-left-color:#89a1fc;border-right-color:transparent;left:auto;right:-7px}.typing-user{text-align:right;margin:10px 15px}.message-input{background:white;color:#484d79;box-sizing:border-box;height:10vh;max-height:65px}.message-input .message-form{height:100%;width:100%;display:flex;justify-content:space-between}.message-input .message-form .form-control{padding-top:24px;padding-bottom:24px;resize:none;padding-left:15px;box-sizing:border-box;width:80%;height:100%;border:none}.message-input .message-form .form-control::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.3);background-color:#cac8ee}.message-input .message-form .form-control::-webkit-scrollbar{width:5px;background-color:green}.message-input .message-form .form-control::-webkit-scrollbar-thumb{background-color:#484d79}.message-input .message-form .form-control:focus{outline:none}.message-input .message-form .send{width:20%;box-sizing:border-box;font-size:1.25em;text-align:center;border:none;height:100%;color:#fff;background:#3e3e5e;transition:all .35s ease-out}.message-input .message-form .send:disabled{opacity:.2;background:#5d5d8a}
2 | /*# sourceMappingURL=index.css.map */
3 |
--------------------------------------------------------------------------------
/src/styles/index.css.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "mappings": "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",
4 | "sources": ["index.scss"],
5 | "names": [],
6 | "file": "index.css"
7 | }
--------------------------------------------------------------------------------
/src/styles/index.scss:
--------------------------------------------------------------------------------
1 |
2 | $primary-color:#3e3e5e;
3 | $primary-color-dark:#2e2e4f;
4 | $primary-color-light:#5d5d8a;
5 | $primary-color-active:#363656;
6 | $sent-message-bg-color:#89a1fc;
7 | $secondary-color:#484d79;
8 | $main-text-color:#cac8ee;
9 | $messages-bg:#dcddf5;
10 | $received-message-bg-color:#fff;
11 | $sent-message-color:#fff;
12 | $received-message-color:#b3b2ca;
13 | $success-color:#60d66a;
14 | $main-padding-vertical:18px;
15 | $main-padding-horizontal:16px;
16 | $main-padding: $main-padding-vertical $main-padding-horizontal;
17 | $error-text: #c92c43;
18 | $head-height:12vh;
19 | $head-max-height:65px;
20 | $footer-max-height:65px;
21 | $footer-height:10vh;
22 |
23 |
24 | @mixin customscrollbar(){
25 | &::-webkit-scrollbar-track{
26 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
27 | background-color: $main-text-color;
28 | }
29 | &::-webkit-scrollbar{
30 | width: 5px;
31 | background-color: green;
32 | }
33 | &::-webkit-scrollbar-thumb{
34 | background-color: $secondary-color;
35 | }
36 | }
37 |
38 | html, body, #root {
39 | margin: 0;
40 | padding: 0;
41 | font-family: sans-serif;
42 | height:100%;
43 | }
44 |
45 | input, textarea{
46 | font-family: Arial;
47 | }
48 |
49 | .login{
50 | width: 100%;
51 | height: 100%;
52 | display: flex;
53 | justify-content: center;
54 | align-items: center;
55 | }
56 |
57 | .login-form{
58 | display: flex;
59 | justify-content: center;
60 | flex-direction: column;
61 | input{
62 | $height:20px;
63 | max-width:100%;
64 | border-top:none;
65 | border-left:none;
66 | border-right: none;
67 | height: $height;
68 | line-height: $height;
69 | font-size: $height;
70 | border-bottom:solid 2px $received-message-color;
71 | transition:all .23s ease-in;
72 | &:focus{
73 | $height:30px;
74 | border-bottom:solid 2px $primary-color-light;
75 | outline: none;
76 | }
77 | }
78 | .error{
79 | text-align: center;
80 | margin: 5px 0;
81 | padding: 5px 10px;
82 | color:$error-text;
83 | }
84 | }
85 | @mixin dot-indicator($size, $color){
86 |
87 | .indicator{
88 | width: $size;
89 | height: $size;
90 | border-radius: $size;
91 | background: $color;
92 | display: inline-block;
93 | margin-right: 8px;
94 | }
95 |
96 | }
97 | .container{
98 |
99 | color: $main-text-color;
100 | display:flex;
101 | flex-direction: row;
102 | align-items: flex-start;
103 | height:100%;
104 | width: 100%;
105 | }
106 |
107 | #side-bar{
108 | box-sizing: border-box;
109 | width:33.75%;
110 | height: 100%;
111 | display:flex;
112 | justify-content:space-around;
113 | flex-direction:column;
114 |
115 | .heading{
116 | box-sizing: border-box;
117 | height:$head-height;
118 | max-height:$head-max-height;
119 | padding: $main-padding;
120 | display:flex;
121 | flex-direction: row;
122 | align-items: center;
123 | justify-content:space-between;
124 | background: $primary-color-dark;
125 | }
126 |
127 | .search{
128 | background: $primary-color;
129 | box-sizing: border-box;
130 | display: flex;
131 | flex-direction: row;
132 | justify-content: space-between;
133 | align-items: center;
134 | border-width: 1px 0;
135 | border-style: solid;
136 | border-color: black;
137 | padding-left: 15px;
138 | padding-right: 20px;
139 | height:10vh;
140 | max-height: $head-max-height;
141 |
142 | .search-icon{
143 | margin-right: 15px;
144 | cursor: pointer;
145 | }
146 |
147 | input{
148 | width: 100%;
149 | background: $primary-color;
150 | flex-grow: 1;
151 | box-sizing:border-box;
152 | border:none;
153 | color: $main-text-color;
154 | &:focus{
155 | outline:none;
156 | }
157 | &::placeholder{
158 | color: $main-text-color;
159 | opacity: .6;
160 | }
161 | }
162 |
163 | .plus{
164 | $plus-width:2px;
165 | $plus-length:16px;
166 | display: inline-block;
167 | position: relative;
168 | cursor:pointer;
169 | width:13px;
170 | height:13px;
171 |
172 | &::after, &::before{
173 | content:'';
174 | position: absolute;
175 | background: $received-message-color;
176 |
177 | }
178 | &::after{
179 | width: $plus-length;
180 | height: $plus-width;
181 | top:5px;
182 | }
183 | &::before{
184 | width: $plus-width;
185 | height: $plus-length;
186 | top:-2px;
187 | left:7px;
188 | }
189 | }
190 | }
191 |
192 | .users{
193 | @include customscrollbar();
194 |
195 | overflow-y: scroll;
196 | background: $primary-color;
197 | flex-grow:1;
198 |
199 | .user{
200 | $indicator-size:16px;
201 | $indicator-color: $success-color ;
202 |
203 | align-items: center;
204 | display: flex;
205 | height:66px;
206 | justify-content: flex-start;
207 | padding: $main-padding;
208 |
209 | &.active, &:hover{
210 | background: $primary-color-dark;
211 | }
212 |
213 | &:hover:not(.active){
214 | cursor: pointer;
215 | }
216 |
217 | .user-photo{
218 |
219 | }
220 |
221 | .user-info{
222 | margin-left: 15px;
223 | flex-grow:1;
224 |
225 | .last-message{
226 | font-size: 12px;
227 | opacity: .56;
228 | }
229 | }
230 |
231 | .new-message{
232 | height:100%;
233 | display: flex;
234 | align-items: center;
235 | justify-content: center;
236 | }
237 | }
238 |
239 |
240 | }
241 |
242 | .current-user{
243 | align-items: center;
244 | background: $secondary-color;
245 | box-sizing: border-box;
246 | display: flex;
247 | flex-direction: row;
248 | height: $footer-height;
249 | max-height: $footer-max-height;
250 | justify-content: space-between;
251 | padding-left: $main-padding-horizontal;
252 | padding-right: $main-padding-horizontal;
253 |
254 | .logout{
255 | align-items: center;
256 | cursor: pointer;
257 | display: flex;
258 | justify-content: center;
259 |
260 | font-size: 2em;
261 | }
262 | }
263 | }
264 |
265 | .chat-header{
266 | background: $primary-color-light;
267 |
268 | box-shadow: 0px 6px 5px -2px rgba(225,225,225, .7);
269 | box-sizing: border-box;
270 |
271 | display: flex;
272 | justify-content: space-between;
273 | align-items: center;
274 |
275 | height:$head-height;
276 |
277 | max-height:$head-max-height;
278 |
279 | padding: 18px 16px;
280 |
281 | .user-info{
282 | align-items:center;
283 | display: flex;
284 |
285 | .user-name{
286 | margin-right: 10px;
287 | }
288 |
289 | .status{
290 | $indicator-size:8px;
291 | align-items:center;
292 | display: flex;
293 | .online{
294 | $indicator-color:#32b0bb;
295 | }
296 | .offline{
297 | $indicator-color:#C63D2C
298 | }
299 |
300 | }
301 | }
302 |
303 | .options{
304 | display: flex;
305 | align-items: center;
306 | flex-direction: row;
307 | justify-content: space-around;
308 | svg{
309 | cursor: pointer;
310 | }
311 | height:100%;
312 | width:15%;
313 | }
314 |
315 | }
316 |
317 | .chat-room-container{
318 | height: 100%;
319 | width: 76.25%;
320 | }
321 | @media screen and (max-width: 510px){
322 | #side-bar{
323 | position:absolute;
324 | left:-100%;
325 | }
326 | .chat-room-container{
327 | width: 100%;
328 | }
329 | }
330 | .chat-room{
331 | display: flex;
332 | flex-direction: column;
333 | justify-content: space-between;
334 | height: 100%;
335 | width: 100%;
336 |
337 | &.choose{
338 | align-items: center;
339 | justify-content: center;
340 | font-size: 2em;
341 | }
342 | }
343 |
344 | .thread-container{
345 |
346 | flex-grow:1;
347 | @include customscrollbar();
348 | overflow-y: scroll;
349 | position: relative;
350 |
351 | .thread{
352 | position: relative;
353 | width: 100%;
354 | min-height: 800px;
355 | display:flex;
356 | flex-direction: column;
357 | justify-content: flex-end;
358 | color: $received-message-color;
359 | background: $messages-bg;
360 |
361 |
362 | }
363 | }
364 |
365 | .message-container{
366 | display: flex;
367 | justify-content: flex-start;
368 | min-height: 50px;
369 | margin:10px 15px;
370 | $message-triangle-offset:-7px;
371 |
372 | animation: .65s ease-out 0s show;
373 |
374 | .time{
375 | order:1;
376 |
377 | }
378 | .data{
379 | order:2;
380 | height:100%;
381 | margin-left:25px;
382 |
383 | }
384 | .name{
385 | font-size: .65em;
386 | margin-top: 5px;
387 | text-align: right;
388 | }
389 | .message{
390 | background: $received-message-bg-color;
391 | border-radius: 5px;
392 | border-top-left-radius: 0;
393 | box-sizing: border-box;
394 | color: $received-message-color;
395 | height:100%;
396 | padding: 10px 15px;
397 | position: relative;
398 | }
399 |
400 | @keyframes show{
401 | 0%{
402 | opacity: 0
403 | }
404 | 100%{
405 | opacity: 1;
406 | }
407 |
408 | }
409 |
410 | .message::before{
411 | $triangle-color: $received-message-bg-color;
412 |
413 | border-bottom-color: transparent;
414 | border-left-color: transparent;
415 | border-right-color: $triangle-color;
416 | border-style:solid;
417 | border-top-color: $triangle-color;
418 | border-width: 4px;
419 |
420 | content:'';
421 |
422 | height: 0;
423 | left: $message-triangle-offset;
424 | position: absolute;
425 | top:0;
426 | width: 0;
427 |
428 | }
429 |
430 | &.right{
431 |
432 | text-align: right;
433 | justify-content: flex-end;
434 |
435 | .time{
436 | order:2;
437 | margin-left: 25px;
438 | }
439 | .data{
440 | margin-left: 0;
441 | order:1;
442 | }
443 | .name{
444 | display: none;
445 |
446 | }
447 | .message{
448 | background: $sent-message-bg-color;
449 | color: $sent-message-color;
450 | border-top-right-radius: 0;
451 | border-top-left-radius: 5px;
452 |
453 | }
454 |
455 | .message::before{
456 | $triangle-color: $sent-message-bg-color;
457 |
458 | border-top-color: $triangle-color;
459 | border-left-color: $triangle-color;
460 | border-right-color: transparent;
461 | left:auto;
462 | right: $message-triangle-offset;
463 | }
464 | }
465 | }
466 |
467 | .typing-user{
468 | text-align: right;
469 | margin: 10px 15px;
470 | }
471 |
472 | .message-input{
473 | background:white;
474 | color: $secondary-color;
475 | box-sizing: border-box;
476 | height:$footer-height;
477 | max-height: $footer-max-height;
478 |
479 | .message-form{
480 | height:100%;
481 | width: 100%;
482 | display: flex;
483 | justify-content: space-between;
484 |
485 | .form-control{
486 | @include customscrollbar();
487 | &:focus{
488 | outline:none;
489 | }
490 | padding-top: 24px;
491 | padding-bottom: 24px;
492 | resize:none;
493 | padding-left: 15px;
494 | box-sizing: border-box;
495 | width: 80%;
496 | height: 100%;
497 | border:none;
498 | }
499 |
500 | .send{
501 | width: 20%;
502 | box-sizing: border-box;
503 | font-size: 1.25em;
504 | text-align: center;
505 | border:none;
506 | height:100%;
507 | color: $sent-message-color;
508 | background: $primary-color;
509 | transition:all .35s ease-out;
510 |
511 | &:disabled{
512 | opacity: .2;
513 | background: $primary-color-light;
514 | }
515 | }
516 | }
517 | }
--------------------------------------------------------------------------------