├── .yo-rc.json ├── .babelrc ├── src ├── config │ └── index.js ├── styles │ ├── Login.css │ ├── ChatApp.css │ └── App.css ├── index.js ├── index.html └── components │ ├── Message.js │ ├── Messages.js │ ├── ChatInput.js │ ├── App.js │ └── ChatApp.js ├── .editorconfig ├── karma.conf.js ├── server.js ├── .gitignore ├── README.md ├── .eslintrc ├── webpack.config.js ├── cfg ├── dev.js ├── dist.js ├── base.js ├── test.js └── defaults.js ├── LICENSE └── package.json /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-react-webpack": {} 3 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-0", 5 | "react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Settings configured here will be merged into the final config object. 4 | export default { 5 | api: 'http://localhost:4008' 6 | } 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | // Render the main component into the dom 7 | ReactDOM.render(, document.getElementById('app')); 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Instant Chat 6 | 7 | 8 | 9 | 10 | 11 |
APPLICATION CONTENT
12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/components/Message.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class Message extends React.Component { 4 | render() { 5 | // Was the message sent by the current user. If so, add a css class 6 | const fromMe = this.props.fromMe ? 'from-me' : ''; 7 | 8 | return ( 9 |
10 |
11 | { this.props.username } 12 |
13 |
14 | { this.props.message } 15 |
16 |
17 | ); 18 | } 19 | } 20 | 21 | Message.defaultProps = { 22 | message: '', 23 | username: '', 24 | fromMe: false 25 | }; 26 | 27 | export default Message; 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-instant-chat 2 | A simple Facebook messenger style chat application using socket.io and React 3 | 4 | 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) 5 | 6 | 7 | To start the app, run: 8 | ``` 9 | npm install 10 | 11 | npm start 12 | ``` 13 | 14 | Once the webpack server has started you can access it in a browser at http://localhost:8000 15 | 16 | Ensure that you also have the [simple-chat-api](https://github.com/kentandlime/simple-chat-api) running as well 17 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Messages.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Message from './Message'; 4 | 5 | class Messages extends React.Component { 6 | componentDidUpdate() { 7 | // There is a new message in the state, scroll to bottom of list 8 | const objDiv = document.getElementById('messageList'); 9 | objDiv.scrollTop = objDiv.scrollHeight; 10 | } 11 | 12 | render() { 13 | // Loop through all the messages in the state and create a Message component 14 | const messages = this.props.messages.map((message, i) => { 15 | return ( 16 | 21 | ); 22 | }); 23 | 24 | return ( 25 |
26 | { messages } 27 |
28 | ); 29 | } 30 | } 31 | 32 | Messages.defaultProps = { 33 | messages: [] 34 | }; 35 | 36 | export default Messages; 37 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/ChatInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class ChatInput extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { chatInput: '' }; 7 | 8 | // React ES6 does not bind 'this' to event handlers by default 9 | this.submitHandler = this.submitHandler.bind(this); 10 | this.textChangeHandler = this.textChangeHandler.bind(this); 11 | } 12 | 13 | submitHandler(event) { 14 | // Stop the form from refreshing the page on submit 15 | event.preventDefault(); 16 | 17 | // Clear the input box 18 | this.setState({ chatInput: '' }); 19 | 20 | // Call the onSend callback with the chatInput message 21 | this.props.onSend(this.state.chatInput); 22 | } 23 | 24 | textChangeHandler(event) { 25 | this.setState({ chatInput: event.target.value }); 26 | } 27 | 28 | render() { 29 | return ( 30 |
31 | 36 |
37 | ); 38 | } 39 | } 40 | 41 | ChatInput.defaultProps = { 42 | }; 43 | 44 | export default ChatInput; 45 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | class App extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { username: '' }; 11 | 12 | // Bind 'this' to event handlers. React ES6 does not do this by default 13 | this.usernameChangeHandler = this.usernameChangeHandler.bind(this); 14 | this.usernameSubmitHandler = this.usernameSubmitHandler.bind(this); 15 | } 16 | 17 | usernameChangeHandler(event) { 18 | this.setState({ username: event.target.value }); 19 | } 20 | 21 | usernameSubmitHandler(event) { 22 | event.preventDefault(); 23 | this.setState({ submitted: true, username: this.state.username }); 24 | } 25 | 26 | render() { 27 | if (this.state.submitted) { 28 | // Form was submitted, now show the main App 29 | return ( 30 | 31 | ); 32 | } 33 | 34 | // Initial page load, show a simple login form 35 | return ( 36 |
37 |

React Instant Chat

38 |
39 | 44 |
45 | 46 |
47 | ); 48 | } 49 | 50 | } 51 | App.defaultProps = { 52 | }; 53 | 54 | export default App; 55 | -------------------------------------------------------------------------------- /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 | class ChatApp extends React.Component { 11 | socket = {}; 12 | constructor(props) { 13 | super(props); 14 | this.state = { messages: [] }; 15 | this.sendHandler = this.sendHandler.bind(this); 16 | 17 | // Connect to the server 18 | this.socket = io(config.api, { query: `username=${props.username}` }).connect(); 19 | 20 | // Listen for messages from the server 21 | this.socket.on('server:message', message => { 22 | this.addMessage(message); 23 | }); 24 | } 25 | 26 | sendHandler(message) { 27 | const messageObject = { 28 | username: this.props.username, 29 | message 30 | }; 31 | 32 | // Emit the message to the server 33 | this.socket.emit('client:message', messageObject); 34 | 35 | messageObject.fromMe = true; 36 | this.addMessage(messageObject); 37 | } 38 | 39 | addMessage(message) { 40 | // Append the message to the component state 41 | const messages = this.state.messages; 42 | messages.push(message); 43 | this.setState({ messages }); 44 | } 45 | 46 | render() { 47 | return ( 48 |
49 |

React Chat App

50 | 51 | 52 |
53 | ); 54 | } 55 | 56 | } 57 | ChatApp.defaultProps = { 58 | username: 'Anonymous' 59 | }; 60 | 61 | export default ChatApp; 62 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------