├── .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 |
59 |
60 | 61 | 67 | 68 | { this.textInput = input }} 70 | id="nickname" 71 | type="text" 72 | value={nickname} 73 | onChange={this.handleChange} 74 | placeholder={this.randomPlaceholder()} 75 | /> 76 |
{error ? error : ""}
77 |
78 |
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 | 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 |
80 | 81 | { e.keyCode !== 13 && this.sendTyping() }} 91 | onChange = { 92 | ({target:{value:v}})=>{ 93 | this.setState({message:v}) 94 | } 95 | }/> 96 | 101 |
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 | } --------------------------------------------------------------------------------