├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .yo-rc.json ├── LICENSE ├── README.md ├── cfg ├── base.js ├── defaults.js ├── dev.js ├── dist.js └── test.js ├── karma.conf.js ├── package.json ├── server.js ├── src ├── components │ ├── App.js │ ├── ChatApp.js │ ├── ChatInput.js │ ├── Message.js │ └── Messages.js ├── config │ └── index.js ├── index.html ├── index.js └── styles │ ├── App.css │ ├── ChatApp.css │ └── Login.css └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-0", 5 | "react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": [ 4 | "react" 5 | ], 6 | "parserOptions": { 7 | "ecmaVersion": 6, 8 | "sourceType": "module", 9 | "ecmaFeatures": { 10 | "jsx": true 11 | } 12 | }, 13 | "env": { 14 | "browser": true, 15 | "amd": true, 16 | "es6": true, 17 | "node": true, 18 | "mocha": true 19 | }, 20 | "rules": { 21 | "comma-dangle": 1, 22 | "quotes": [ 1, "single" ], 23 | "no-undef": 1, 24 | "global-strict": 0, 25 | "no-extra-semi": 1, 26 | "no-underscore-dangle": 0, 27 | "no-console": 1, 28 | "no-unused-vars": 1, 29 | "no-trailing-spaces": [1, { "skipBlankLines": true }], 30 | "no-unreachable": 1, 31 | "no-alert": 0, 32 | "react/jsx-uses-react": 1, 33 | "react/jsx-uses-vars": 1 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # Bower 30 | bower_components/ 31 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-react-webpack": {} 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Kent and Lime 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-instant-chat-tutorial 2 | A skeleton version of the react-instant-chat app. It contains no React code and allows the reader to follow along with the tutorial located on the CoderFactory blog 3 | 4 | To start the app, run: 5 | 6 | ``` 7 | npm install 8 | 9 | npm start 10 | ``` 11 | 12 | Once the webpack server has started you can access it in a browser at http://localhost:8000 13 | 14 | Ensure that you also have the [simple-chat-api](https://github.com/kentandlime/simple-chat-api) running as well 15 | 16 | ## Tutorial 17 | See the tutorial here [https://medium.com/@coderacademy/you-can-build-an-fb-messenger-style-chat-app-with-reactjs-heres-how-intermediate-211b523838ad](https://medium.com/@coderacademy/you-can-build-an-fb-messenger-style-chat-app-with-reactjs-heres-how-intermediate-211b523838ad) 18 | 19 | 20 | ![Finished react chat app](https://s17.postimg.org/40klqu39r/20160918_123011_capture.gif) 21 | -------------------------------------------------------------------------------- /cfg/base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let path = require('path'); 3 | let defaultSettings = require('./defaults'); 4 | 5 | // Additional npm or bower modules to include in builds 6 | // Add all foreign plugins you may need into this array 7 | // @example: 8 | // let npmBase = path.join(__dirname, '../node_modules'); 9 | // let additionalPaths = [ path.join(npmBase, 'react-bootstrap') ]; 10 | let additionalPaths = []; 11 | 12 | module.exports = { 13 | additionalPaths: additionalPaths, 14 | port: defaultSettings.port, 15 | debug: true, 16 | devtool: 'eval', 17 | output: { 18 | path: path.join(__dirname, '/../dist/assets'), 19 | filename: 'app.js', 20 | publicPath: defaultSettings.publicPath 21 | }, 22 | devServer: { 23 | contentBase: './src/', 24 | historyApiFallback: true, 25 | hot: true, 26 | port: defaultSettings.port, 27 | publicPath: defaultSettings.publicPath, 28 | noInfo: false 29 | }, 30 | resolve: { 31 | extensions: ['', '.js', '.jsx'], 32 | alias: { 33 | actions: `${defaultSettings.srcPath}/actions/`, 34 | components: `${defaultSettings.srcPath}/components/`, 35 | sources: `${defaultSettings.srcPath}/sources/`, 36 | stores: `${defaultSettings.srcPath}/stores/`, 37 | styles: `${defaultSettings.srcPath}/styles/`, 38 | config: `${defaultSettings.srcPath}/config/` + process.env.REACT_WEBPACK_ENV 39 | } 40 | }, 41 | module: {} 42 | }; 43 | -------------------------------------------------------------------------------- /cfg/defaults.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Function that returns default values. 3 | * Used because Object.assign does a shallow instead of a deep copy. 4 | * Using [].push will add to the base array, so a require will alter 5 | * the base array output. 6 | */ 7 | 'use strict'; 8 | 9 | const path = require('path'); 10 | const srcPath = path.join(__dirname, '/../src'); 11 | const dfltPort = 8000; 12 | 13 | /** 14 | * Get the default modules object for webpack 15 | * @return {Object} 16 | */ 17 | function getDefaultModules() { 18 | return { 19 | preLoaders: [ 20 | { 21 | test: /\.(js|jsx)$/, 22 | include: srcPath, 23 | loader: 'eslint-loader' 24 | } 25 | ], 26 | loaders: [ 27 | { 28 | test: /\.css$/, 29 | loader: 'style-loader!css-loader' 30 | }, 31 | { 32 | test: /\.sass/, 33 | loader: 'style-loader!css-loader!sass-loader?outputStyle=expanded&indentedSyntax' 34 | }, 35 | { 36 | test: /\.scss/, 37 | loader: 'style-loader!css-loader!sass-loader?outputStyle=expanded' 38 | }, 39 | { 40 | test: /\.less/, 41 | loader: 'style-loader!css-loader!less-loader' 42 | }, 43 | { 44 | test: /\.styl/, 45 | loader: 'style-loader!css-loader!stylus-loader' 46 | }, 47 | { 48 | test: /\.(png|jpg|gif|woff|woff2)$/, 49 | loader: 'url-loader?limit=8192' 50 | }, 51 | { 52 | test: /\.(mp4|ogg|svg)$/, 53 | loader: 'file-loader' 54 | } 55 | ] 56 | }; 57 | } 58 | 59 | module.exports = { 60 | srcPath: srcPath, 61 | publicPath: '/assets/', 62 | port: dfltPort, 63 | getDefaultModules: getDefaultModules 64 | }; 65 | -------------------------------------------------------------------------------- /cfg/dev.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let path = require('path'); 4 | let webpack = require('webpack'); 5 | let baseConfig = require('./base'); 6 | let defaultSettings = require('./defaults'); 7 | 8 | // Add needed plugins here 9 | let BowerWebpackPlugin = require('bower-webpack-plugin'); 10 | 11 | let config = Object.assign({}, baseConfig, { 12 | entry: [ 13 | 'webpack-dev-server/client?http://127.0.0.1:' + defaultSettings.port, 14 | 'webpack/hot/only-dev-server', 15 | './src/index' 16 | ], 17 | cache: true, 18 | devtool: 'eval-source-map', 19 | plugins: [ 20 | new webpack.HotModuleReplacementPlugin(), 21 | new webpack.NoErrorsPlugin(), 22 | new BowerWebpackPlugin({ 23 | searchResolveModulesDirectories: false 24 | }) 25 | ], 26 | module: defaultSettings.getDefaultModules() 27 | }); 28 | 29 | // Add needed loaders to the defaults here 30 | config.module.loaders.push({ 31 | test: /\.(js|jsx)$/, 32 | loader: 'react-hot!babel-loader', 33 | include: [].concat( 34 | config.additionalPaths, 35 | [ path.join(__dirname, '/../src') ] 36 | ) 37 | }); 38 | 39 | module.exports = config; 40 | -------------------------------------------------------------------------------- /cfg/dist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let path = require('path'); 4 | let webpack = require('webpack'); 5 | 6 | let baseConfig = require('./base'); 7 | let defaultSettings = require('./defaults'); 8 | 9 | // Add needed plugins here 10 | let BowerWebpackPlugin = require('bower-webpack-plugin'); 11 | 12 | let config = Object.assign({}, baseConfig, { 13 | entry: path.join(__dirname, '../src/index'), 14 | cache: false, 15 | devtool: 'sourcemap', 16 | plugins: [ 17 | new webpack.optimize.DedupePlugin(), 18 | new webpack.DefinePlugin({ 19 | 'process.env.NODE_ENV': '"production"' 20 | }), 21 | new BowerWebpackPlugin({ 22 | searchResolveModulesDirectories: false 23 | }), 24 | new webpack.optimize.UglifyJsPlugin(), 25 | new webpack.optimize.OccurenceOrderPlugin(), 26 | new webpack.optimize.AggressiveMergingPlugin(), 27 | new webpack.NoErrorsPlugin() 28 | ], 29 | module: defaultSettings.getDefaultModules() 30 | }); 31 | 32 | // Add needed loaders to the defaults here 33 | config.module.loaders.push({ 34 | test: /\.(js|jsx)$/, 35 | loader: 'babel', 36 | include: [].concat( 37 | config.additionalPaths, 38 | [ path.join(__dirname, '/../src') ] 39 | ) 40 | }); 41 | 42 | module.exports = config; 43 | -------------------------------------------------------------------------------- /cfg/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let path = require('path'); 4 | let srcPath = path.join(__dirname, '/../src/'); 5 | 6 | let baseConfig = require('./base'); 7 | 8 | // Add needed plugins here 9 | let BowerWebpackPlugin = require('bower-webpack-plugin'); 10 | 11 | module.exports = { 12 | devtool: 'eval', 13 | module: { 14 | preLoaders: [ 15 | { 16 | test: /\.(js|jsx)$/, 17 | loader: 'isparta-instrumenter-loader', 18 | include: [ 19 | path.join(__dirname, '/../src') 20 | ] 21 | } 22 | ], 23 | loaders: [ 24 | { 25 | test: /\.(png|jpg|gif|woff|woff2|css|sass|scss|less|styl)$/, 26 | loader: 'null-loader' 27 | }, 28 | { 29 | test: /\.(js|jsx)$/, 30 | loader: 'babel-loader', 31 | include: [].concat( 32 | baseConfig.additionalPaths, 33 | [ 34 | path.join(__dirname, '/../src'), 35 | path.join(__dirname, '/../test') 36 | ] 37 | ) 38 | } 39 | ] 40 | }, 41 | resolve: { 42 | extensions: [ '', '.js', '.jsx' ], 43 | alias: { 44 | actions: srcPath + 'actions/', 45 | helpers: path.join(__dirname, '/../test/helpers'), 46 | components: srcPath + 'components/', 47 | sources: srcPath + 'sources/', 48 | stores: srcPath + 'stores/', 49 | styles: srcPath + 'styles/', 50 | config: srcPath + 'config/' + process.env.REACT_WEBPACK_ENV 51 | } 52 | }, 53 | plugins: [ 54 | new BowerWebpackPlugin({ 55 | searchResolveModulesDirectories: false 56 | }) 57 | ] 58 | }; 59 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var webpackCfg = require('./webpack.config'); 2 | 3 | // Set node environment to testing 4 | process.env.NODE_ENV = 'test'; 5 | 6 | module.exports = function(config) { 7 | config.set({ 8 | basePath: '', 9 | browsers: [ 'PhantomJS' ], 10 | files: [ 11 | 'test/loadtests.js' 12 | ], 13 | port: 8000, 14 | captureTimeout: 60000, 15 | singleRun: true, 16 | preprocessors: { 17 | 'test/loadtests.js': [ 'webpack', 'sourcemap' ] 18 | }, 19 | webpack: webpackCfg, 20 | webpackServer: { 21 | noInfo: true 22 | } 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "description": "A simple instant message client built for a CoderFactory blog article", 4 | "main": "", 5 | "scripts": { 6 | "start": "node server.js --env=dev", 7 | "serve": "node server.js --env=dev", 8 | "serve:dist": "node server.js --env=dist", 9 | "dist": "npm run copy & webpack --env=dist", 10 | "lint": "eslint ./src", 11 | "copy": "copyfiles -f ./src/index.html ./src/favicon.ico ./dist", 12 | "clean": "rimraf dist/*" 13 | }, 14 | "repository": "", 15 | "keywords": [], 16 | "license": "MIT", 17 | "author": "Paul Pagnan ", 18 | "devDependencies": { 19 | "babel-core": "^6.0.0", 20 | "babel-eslint": "^6.0.0", 21 | "babel-loader": "^6.0.0", 22 | "babel-polyfill": "^6.3.14", 23 | "babel-preset-es2015": "^6.0.15", 24 | "babel-preset-react": "^6.0.15", 25 | "babel-preset-stage-0": "^6.5.0", 26 | "bower-webpack-plugin": "^0.1.9", 27 | "copyfiles": "^1.0.0", 28 | "css-loader": "^0.23.0", 29 | "eslint": "^3.0.0", 30 | "eslint-loader": "^1.0.0", 31 | "eslint-plugin-react": "^6.0.0", 32 | "file-loader": "^0.9.0", 33 | "glob": "^7.0.0", 34 | "isparta-instrumenter-loader": "^1.0.0", 35 | "karma": "^1.0.0", 36 | "karma-phantomjs-launcher": "^1.0.0", 37 | "karma-sourcemap-loader": "^0.3.5", 38 | "karma-webpack": "^1.7.0", 39 | "minimist": "^1.2.0", 40 | "null-loader": "^0.1.1", 41 | "open": "0.0.5", 42 | "phantomjs-prebuilt": "^2.0.0", 43 | "react-addons-test-utils": "^15.0.0", 44 | "react-hot-loader": "^1.2.9", 45 | "rimraf": "^2.4.3", 46 | "style-loader": "^0.13.0", 47 | "url-loader": "^0.5.6", 48 | "webpack": "^1.12.0", 49 | "webpack-dev-server": "^1.12.0" 50 | }, 51 | "dependencies": { 52 | "core-js": "^2.0.0", 53 | "normalize.css": "^4.0.0", 54 | "react": "^15.0.0", 55 | "react-dom": "^15.0.0", 56 | "socket.io-client": "^1.4.8" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /*eslint no-console:0 */ 2 | 'use strict'; 3 | require('core-js/fn/object/assign'); 4 | const webpack = require('webpack'); 5 | const WebpackDevServer = require('webpack-dev-server'); 6 | const config = require('./webpack.config'); 7 | const open = require('open'); 8 | 9 | new WebpackDevServer(webpack(config), config.devServer) 10 | .listen(config.port, 'localhost', (err) => { 11 | if (err) { 12 | console.log(err); 13 | } 14 | console.log('Listening at localhost:' + config.port); 15 | console.log('Opening your system browser...'); 16 | open('http://localhost:' + config.port + '/webpack-dev-server/'); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | require('../styles/App.css'); 2 | require('../styles/Login.css'); 3 | 4 | import React from 'react'; 5 | import ChatApp from './ChatApp'; 6 | 7 | // This is the first screen seen by the user. We should display some way for 8 | // them to enter their name and enter the chat room 9 | class App extends React.Component { 10 | render() { 11 | // Display a simple login screen with a username field and a submit button 12 | } 13 | 14 | } 15 | App.defaultProps = { 16 | }; 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /src/components/ChatApp.js: -------------------------------------------------------------------------------- 1 | require('../styles/ChatApp.css'); 2 | 3 | import React from 'react'; 4 | import io from 'socket.io-client'; 5 | import config from '../config'; 6 | 7 | import Messages from './Messages'; 8 | import ChatInput from './ChatInput'; 9 | 10 | // This is where the main logic of the app will be. Here is where we will 11 | // communicate with the chat server (send and receive messages). We will 12 | // then pass the data received from the server to other components to be 13 | // displayed 14 | class ChatApp extends React.Component { 15 | render() { 16 | // Here we want to render the main chat application components 17 | } 18 | 19 | } 20 | ChatApp.defaultProps = { 21 | username: 'Anonymous' 22 | }; 23 | 24 | export default ChatApp; 25 | -------------------------------------------------------------------------------- /src/components/ChatInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | // This component is where the user can type their message and send it 4 | // to the chat room. We shouldn't communicate with the server here though. 5 | class ChatInput extends React.Component { 6 | render() { 7 | // Display a user input form and do something when it is submitted 8 | } 9 | } 10 | 11 | ChatInput.defaultProps = { 12 | }; 13 | 14 | export default ChatInput; 15 | -------------------------------------------------------------------------------- /src/components/Message.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | // This component displays an individual message. 4 | // We should have logic to display it on the right if the user sent the 5 | // message, or on the left if it was received from someone else. 6 | class Message extends React.Component { 7 | render() { 8 | // Display the message text and sender's name 9 | } 10 | } 11 | 12 | Message.defaultProps = { 13 | }; 14 | 15 | export default Message; 16 | -------------------------------------------------------------------------------- /src/components/Messages.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Message from './Message'; 4 | 5 | // This is the main display of the application. It shows a list of all the 6 | // messages which have been sent and received during the current chat session. 7 | class Messages extends React.Component { 8 | render() { 9 | // Here we should loop through each message and 10 | // pass it to the Message component 11 | } 12 | } 13 | 14 | Messages.defaultProps = { 15 | }; 16 | 17 | export default Messages; 18 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Here we define where our server is listening. It is best to store this in 4 | // a configuration file because we are able to easily change the server URL 5 | // based on the current environment. I.e if we are in development, we want to 6 | // use a local API, but if the app is running publicly on the internet we want to 7 | // use a publicly accessible version of this API. Storing the address in the config 8 | // file allows us to easily create different settings for various environments. 9 | export default { 10 | api: 'http://localhost:4008' 11 | } 12 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Instant Chat 6 | 7 | 8 | 9 | 10 | 11 |
12 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import 'core-js/fn/object/assign'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import App from './components/App'; 5 | 6 | // This is where the App is bootstrapped. 7 | // We tell the ReactJs library that we want to display our main App component 8 | // inside a specific element in the DOM. In this case, there is a div with id="app" 9 | // inside the index.html file. Our React App component will be injected into here 10 | ReactDOM.render(, document.getElementById('app')); 11 | -------------------------------------------------------------------------------- /src/styles/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'proxima-nova', sans-serif; 3 | margin: 0; 4 | } 5 | 6 | html, body, #app, .container { 7 | min-height: 100vh; 8 | max-height: 100vh; 9 | max-width: 100%; 10 | } 11 | 12 | input[type="text"] { 13 | margin: 10px 0px; 14 | height: 60px; 15 | -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.15); 16 | -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.15); 17 | box-shadow: 0 1px 2px rgba(0,0,0,0.15); 18 | color: #1e1e1e; 19 | font: 400 1rem "proxima-nova", sans-serif; 20 | width: 80%; 21 | border: 1px solid #dee0e0; 22 | padding: 0 20px; 23 | -webkit-transition: border-color 0.2s ease-in-out; 24 | -moz-transition: border-color 0.2s ease-in-out; 25 | transition: border-color 0.2s ease-in-out; 26 | } 27 | 28 | input[type="text"]:focus { 29 | border: 1px solid #AF9570; 30 | outline: none !important; 31 | } 32 | 33 | input[type="submit"] { 34 | background-color: #323232; 35 | color: white; 36 | font: 700 1rem "proxima-nova",sans-serif; 37 | border: none; 38 | position: relative; 39 | -webkit-transition: 0.3s ease-in-out; 40 | -moz-transition: 0.3s ease-in-out; 41 | transition: 0.3s ease-in-out; 42 | text-decoration: none; 43 | padding: 20px 80px; 44 | text-align: center; 45 | cursor: pointer; 46 | -webkit-box-shadow: -7px 7px 0px rgba(50,50,50,0.15); 47 | -moz-box-shadow: -7px 7px 0px rgba(50,50,50,0.15); 48 | box-shadow: -7px 7px 0px rgba(50,50,50,0.15); 49 | margin-top: 20px; 50 | } 51 | 52 | input[type="submit"]:hover { 53 | -webkit-box-shadow: -10px 10px 0px rgba(50,50,50,0.15); 54 | -moz-box-shadow: -10px 10px 0px rgba(50,50,50,0.15); 55 | box-shadow: -10px 10px 0px rgba(50,50,50,0.15); 56 | background-color: #1e1e1e; 57 | } 58 | -------------------------------------------------------------------------------- /src/styles/ChatApp.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .messages { 7 | overflow-y: scroll; 8 | overflow-x: hidden; 9 | flex-grow: 1; 10 | padding: 20px; 11 | } 12 | 13 | h3 { 14 | text-align: center; 15 | padding: 20px 0; 16 | margin: 0; 17 | border-bottom: 1px solid #ddd; 18 | background-color: #eee; 19 | } 20 | 21 | .chat-input { 22 | position: relative; 23 | overflow: hidden; 24 | padding: 0 40px; 25 | flex-shrink: 0; 26 | } 27 | 28 | .chat-input input[type="text"] { 29 | width: 100%; 30 | margin-left: -20px; 31 | margin-right: -20px; 32 | } 33 | 34 | .message.from-me .username { 35 | display: none; 36 | } 37 | 38 | .message.from-me { 39 | display: flex; 40 | justify-content: flex-end; 41 | margin-bottom: 5px; 42 | } 43 | 44 | .message.from-me .message-body { 45 | background-color: #af9570; 46 | color: white; 47 | } 48 | 49 | .message { 50 | margin-bottom: 20px; 51 | } 52 | .message-body { 53 | max-width: 80%; 54 | display: inline-block; 55 | padding: 20px; 56 | background-color: #eee; 57 | border: 1px; 58 | border-radius: 5px; 59 | padding-right: 50px; 60 | } 61 | 62 | .username { 63 | font-weight: bold; 64 | font-size: 0.9rem; 65 | color: #999; 66 | margin-bottom: 5px; 67 | } -------------------------------------------------------------------------------- /src/styles/Login.css: -------------------------------------------------------------------------------- 1 | .username-container { 2 | max-width: 400px; 3 | margin: 0 auto; 4 | text-align: center; 5 | margin-top: 10%; 6 | } 7 | 8 | h1 { 9 | display: block; 10 | } 11 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const args = require('minimist')(process.argv.slice(2)); 5 | 6 | // List of allowed environments 7 | const allowedEnvs = ['dev', 'dist', 'test']; 8 | 9 | // Set the correct environment 10 | let env; 11 | if (args._.length > 0 && args._.indexOf('start') !== -1) { 12 | env = 'test'; 13 | } else if (args.env) { 14 | env = args.env; 15 | } else { 16 | env = 'dev'; 17 | } 18 | process.env.REACT_WEBPACK_ENV = env; 19 | 20 | /** 21 | * Build the webpack configuration 22 | * @param {String} wantedEnv The wanted environment 23 | * @return {Object} Webpack config 24 | */ 25 | function buildConfig(wantedEnv) { 26 | let isValid = wantedEnv && wantedEnv.length > 0 && allowedEnvs.indexOf(wantedEnv) !== -1; 27 | let validEnv = isValid ? wantedEnv : 'dev'; 28 | let config = require(path.join(__dirname, 'cfg/' + validEnv)); 29 | return config; 30 | } 31 | 32 | module.exports = buildConfig(env); 33 | --------------------------------------------------------------------------------