diff --git a/README.md b/README.md index 0b56443..8c25b42 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,89 @@ -# gitter-clone +# Gitter-clone [![Build Status](https://semaphoreci.com/api/v1/tcrosse/gitter-clone/branches/master/badge.svg)](https://semaphoreci.com/tcrosse/gitter-clone) [![codecov](https://codecov.io/gh/tylercrosse/gitter-clone/branch/master/graph/badge.svg)](https://codecov.io/gh/tylercrosse/gitter-clone) -Portfolio Project clone of gitter.im -- Hire me! - -Built with: -- react -- redux -- jest -- webpack -- babel -- express -- mongodb -- semaphore -- digital ocean +
+
+ +This is app was built as portfolio piece representing a subset of the features of Troupe Technology's wonderful chat app, [Gitter](https://gitter.im/). I'm currently looking for a new position - Hire me! + +--- +### Demo + +You can test a fully working live demo at http://138.68.24.248:3333/ + +--- +### Major features + +- MD / syntax highlighting support for messages +- Messages grouped into bursts +- Create Channels +- Load previous messages in channel +- Send Messages to all subscribed clients on channel + +--- +### Built with + +##### [React](https://facebook.github.io/react/) + +React makes it really easy to focus on the view in a declarative way. I like that it makes it easy to write composable, testable UI. Routing is handled by [react router](https://reacttraining.com/react-router/). + +##### [Redux](http://redux.js.org/) + +Redux is where the fun is at. Maintain a flat minimal state, with dictionary of normalized objects. I use [reselect](https://github.com/reactjs/reselect) to compute derived data. [Redux devtools](https://github.com/zalmoxisus/redux-devtools-extension) are also great, I kept it enabled on production for anyone wanting to easily take a look at the app's state. + +##### [Webpack 2](https://webpack.js.org/) + +Fantastic code bundler once you get past the learning curve. I use it for a number of things including: transpile ES2015+ javascript to ES5 with [Babel](https://babeljs.io/), compile [Sass](http://sass-lang.com/) into css, optimize assets, hot reload code, build minimized split production code, + more. + +##### [Express](https://expressjs.com/) + +It's nice to have JS everywhere. Express is fast and minimal. The backend is pretty simple with a router, a few controllers, and basic db interactions. Logging handled by [winston](https://github.com/winstonjs/winston). + +##### [MongoDB](https://www.mongodb.com/) + +This project doesn't currently require a ton of data persistence. MongoDB + [Mongoose](http://mongoosejs.com/) make it easy to quickly update the Schema. If the project continues to grow I will probably switch to a relational DB. Also Mongo is still trendy. + +##### [Jest](http://facebook.github.io/jest/) + +Unit tests run by Jest. If you haven't seen Jest recently, you should take another look. [Enzyme](https://github.com/airbnb/enzyme) is used for React support and [SuperTest](https://github.com/visionmedia/supertest) is used for HTTP assertions. [Enzyme-to-JSON](https://github.com/adriantoine/enzyme-to-json) is also great and worth checking out in conjunction with the other test utilities. + +##### [SemaphoreCI](https://semaphoreci.com/) + +Continuous integration handled by the super fast SemaphoreCI. Passing merges to master are auto deployed with help from [pm2](http://pm2.keymetrics.io/). + +##### [Digital Ocean](https://www.digitalocean.com/) + +Application hosted on Ubuntu Digital Ocean droplet. SSH is fun! + +--- +### Setup + +If you don't have [yarn](https://yarnpkg.com/en/) commands can be run with `npm`. First, clone and cd into the repo and install the dependencies. + +```sh +$ git clone https://github.com/tylercrosse/gitter-clone.git +$ cd gitter-clone +$ yarn install +``` + +Additional commands: +##### `yarn dev` +- Start development server on `127.0.0.1:3333` + +##### `yarn build` +- Build a production version of the app. + +##### `yarn start` +- Start production server on `127.0.0.1:3333` to serve built app. Requires the build command to have already been run. + +##### `yarn test` +- Run all of the projects tests using jest. + +##### `yarn lint` +- Lint all of the projects javascript files using eslint. + +--- +### Contributing + +Thank you for your interest! Unfortunately, I'm not currently taking contributions. diff --git a/config/sw-precache.js b/config/sw-precache.js new file mode 100644 index 0000000..1f77d91 --- /dev/null +++ b/config/sw-precache.js @@ -0,0 +1,10 @@ +module.exports = { + stirpPrefix: '/static/dist/', + // replacePrefix: '/dist/', + staticFileGlobs: [ + 'static/dist/manifest.json', + 'static/dist/**.js', + 'static/dist/**.min.css', + ], + swFilePath: 'static/dist/serviceWorker.js' +}; diff --git a/config/webpack.config.base.js b/config/webpack.config.base.js new file mode 100644 index 0000000..c86ff42 --- /dev/null +++ b/config/webpack.config.base.js @@ -0,0 +1,41 @@ +const path = require('path'); +const webpack = require('webpack'); + +module.exports = { + output: { + path: path.resolve(__dirname, '../static/dist'), + filename: '[name].js', + publicPath: '/dist/' + }, + resolve: { + extensions: ['.js', '.jsx', '.json', '.scss'] + }, + plugins: [ + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + new webpack.optimize.CommonsChunkPlugin({ + names: ['vendor', 'manifest'], + minChunks: Infinity + }) + ], + module: { + loaders: [ + // Images + // Inline base64 URLs for <=8k images, direct URLs for the rest + { + test: /\.(png|jpg|jpeg|gif|svg)$/, + loader: 'url-loader', + query: { + limit: 8192, + } + }, + // Fonts + { + test: /\.(woff|woff2|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/, + loader: 'url-loader', + query: { + limit: 8192, + } + } + ] + } +}; diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js new file mode 100644 index 0000000..449d17a --- /dev/null +++ b/config/webpack.config.dev.js @@ -0,0 +1,94 @@ +const path = require('path'); +const webpack = require('webpack'); +const merge = require('webpack-merge'); +const config = require('./webpack.config.base'); + +module.exports = merge(config, { + devtool: 'source-map', + entry: { + application: [ + 'babel-polyfill', + 'webpack-hot-middleware/client', + './src/client/index.js' + ], + vendor: [ + 'moment', + 'normalizr', + 'react', + 'react-dom', + 'react-modal', + 'react-router', + 'react-scroll', + 'react-redux', + 'react-router-redux', + 'redux', + 'redux-thunk', + 'redux-api-middleware', + 'redux-socket.io', + 'reselect', + 'socket.io-client', + ] + }, + plugins: [ + new webpack.HotModuleReplacementPlugin(), + new webpack.NoEmitOnErrorsPlugin(), + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development') + } + }) + ], + module: { + loaders: [ + { + test: /\.(js|jsx)?$/, + loader: 'babel-loader', + query: { + plugins: [ + [ + 'react-transform', { + transforms: [ + { + transform: 'react-transform-hmr', + imports: ['react'], + locals: ['module'] + }, { + transform: 'react-transform-catch-errors', + imports: ['react', 'redbox-react'] + } + ] + } + ] + ] + }, + include: [path.resolve(__dirname, '../src')] + }, { + // test: /\.css?$/, + // loaders: ['style-loader', 'raw-loader'] + // }, { + test: /\.scss$/, + loaders : [ + { + loader: 'style-loader' + }, { + loader: 'css-loader', + }, { + loader: 'postcss-loader', + options: { + plugins() { + return [ + require('autoprefixer') + ] + } + } + }, { + loader: 'resolve-url-loader' + }, { + loader: 'sass-loader', + options: {sourceMap: true} + } + ] + } + ] + } +}); diff --git a/config/webpack.config.prod.js b/config/webpack.config.prod.js new file mode 100644 index 0000000..7ce7def --- /dev/null +++ b/config/webpack.config.prod.js @@ -0,0 +1,102 @@ +// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; +const path = require('path'); +const webpack = require('webpack'); +const merge = require('webpack-merge'); +const CleanPlugin = require('clean-webpack-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const config = require('./webpack.config.base'); + +module.exports = merge(config, { + devtool: 'cheap-module-source-map', + entry: { + application: ['./src/client/index'], + vendor: [ + 'moment', + 'normalizr', + 'react', + 'react-dom', + 'react-modal', + 'react-router', + 'react-scroll', + 'react-redux', + 'react-router-redux', + 'redux', + 'redux-thunk', + 'redux-api-middleware', + 'redux-socket.io', + 'reselect', + 'socket.io-client', + ] + }, + plugins: [ + new CleanPlugin(['../static/dist'], {verbose: true}), + new CopyWebpackPlugin([ + { + from: path.join(__dirname, '../src/client/manifest.json'), + to: 'manifest.json' + } + ]), + new webpack.DefinePlugin({ + 'process.env': { + 'NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production') + } + }), + new webpack.optimize.UglifyJsPlugin({ + compress: { + warnings: false, + 'screw_ie8': true + }, + output: { + comments: false + }, + sourceMap: false + }), + new webpack.LoaderOptionsPlugin({ + minimize: true, + debug: false + }), + new ExtractTextPlugin({ + filename: 'style.min.css', + allChunks: true + }), + // new BundleAnalyzerPlugin() + ], + module: { + noParse: /\.min\.js$/, + loaders: [ + { + test: /\.(js|jsx)?$/, + use: 'babel-loader', + include: [path.resolve(__dirname, '../src')] + }, { + test: /\.css?$/, + loaders: ['style-loader', 'raw-loader'] + }, { + test: /\.scss$/, + loader: ExtractTextPlugin.extract({ + fallbackLoader: 'style-loader', + use: [ + { + loader: 'css-loader', + }, { + loader: 'postcss-loader', + options: { + plugins() { + return [ + require('autoprefixer') + ] + } + } + }, { + loader: 'resolve-url-loader' + }, { + loader: 'sass-loader', + options: {sourceMap: true} + } + ] + }) + } + ] + } +}); diff --git a/package.json b/package.json index 888b548..215bc7b 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "dev": "NODE_ENV=development node ./src/server/", "test": "jest --silent --coverage --runInBand", "lint": "eslint .", - "build": "rimraf ./static/compiled && npm run build:node && npm run build:dist", - "build:dist": "NODE_ENV=production webpack --config ./webpack.config.prod.js --progress --profile --colors", + "build": "rimraf ./static/compiled && npm run build:node && npm run build:dist && sw-precache --config=./config/sw-precache.js", + "build:dist": "NODE_ENV=production webpack --config ./config/webpack.config.prod.js --progress --profile --colors", "build:node": "npm run babel -- ./src/server/ -d ./static/compiled", "babel": "babel --presets es2015,stage-0 --ignore node_modules,static,*.spec.js" }, @@ -26,6 +26,7 @@ "dependencies": { "bluebird": "^3.4.7", "body-parser": "^1.16.0", + "compression": "^1.6.2", "express": "^4.14.0", "express-winston": "^2.2.0", "isomorphic-fetch": "^2.2.1", @@ -33,7 +34,6 @@ "marked": "^0.3.6", "moment": "^2.17.1", "mongoose": "^4.7.7", - "node-uuid": "^1.4.7", "normalizr": "^3.1.0", "prismjs": "^1.6.0", "react": "^15.4.2", @@ -48,11 +48,13 @@ "redux-socket.io": "^1.3.1", "redux-thunk": "^2.1.0", "reselect": "^2.5.4", + "shortid": "^2.2.8", "socket.io": "^1.7.2", "socket.io-client": "^1.7.2", "winston": "^2.3.1" }, "devDependencies": { + "autoprefixer": "^6.7.7", "babel-cli": "^6.22.2", "babel-core": "^6.21.0", "babel-eslint": "^7.1.1", @@ -66,6 +68,7 @@ "babel-preset-stage-0": "^6.16.0", "babel-register": "^6.18.0", "clean-webpack-plugin": "^0.1.15", + "copy-webpack-plugin": "^4.0.1", "css-loader": "^0.26.1", "enzyme": "^2.7.1", "enzyme-to-json": "^1.5.0", @@ -74,12 +77,14 @@ "eslint-plugin-import": "^2.2.0", "eslint-plugin-jsx-a11y": "^3.0.2", "eslint-plugin-react": "^6.9.0", - "extract-text-webpack-plugin": "^1.0.1", + "extract-text-webpack-plugin": "^2.0.0", + "file-loader": "^0.10.1", "html-webpack-plugin": "^2.26.0", "jest": "^19.0.2", "jest-enzyme": "^2.1.0", "json-loader": "^0.5.4", "node-sass": "^4.4.0", + "postcss-loader": "^1.3.3", "pre-commit": "^1.2.2", "raw-loader": "^0.5.1", "react-addons-test-utils": "^15.4.2", @@ -89,17 +94,21 @@ "react-transform-hmr": "^1.0.4", "redbox-react": "^1.3.3", "redux-mock-store": "^1.2.1", + "resolve-url-loader": "^2.0.2", "rimraf": "^2.5.4", "sass-loader": "^4.1.1", "sinon": "^1.17.7", "style-loader": "^0.13.1", "supertest": "^2.0.1", - "webpack": "^1.14.0", + "sw-precache": "^5.0.0", + "url-loader": "^0.5.8", + "webpack": "^2.2.1", "webpack-bundle-analyzer": "^2.2.1", "webpack-dev-middleware": "^1.9.0", "webpack-dev-server": "^1.16.2", "webpack-hot-middleware": "^2.15.0", - "webpack-isomorphic-tools": "^2.6.6" + "webpack-isomorphic-tools": "^2.6.6", + "webpack-merge": "^3.0.0" }, "pre-commit": [ "lint", diff --git a/src/client/actions/index.js b/src/client/actions/index.js index 6f04206..4f92b41 100644 --- a/src/client/actions/index.js +++ b/src/client/actions/index.js @@ -1,4 +1,4 @@ -import { v4 } from 'node-uuid'; +import shortid from 'shortid'; import { normalize } from 'normalizr'; import { push } from 'react-router-redux'; import { CALL_API, @@ -75,7 +75,7 @@ export const SIGN_IN = 'SIGN_IN'; export const signIn = (username, id) => (dispatch) => { dispatch({ type: SIGN_IN, - id: id || v4(), + id: id || shortid.generate(), username, }); dispatch(push('/chat')); diff --git a/src/client/actions/index.spec.js b/src/client/actions/index.spec.js index 4d4d880..b96132d 100644 --- a/src/client/actions/index.spec.js +++ b/src/client/actions/index.spec.js @@ -1,4 +1,4 @@ -import { v4 } from 'node-uuid'; +import shortid from 'shortid'; import * as actions from './index'; describe('actions', () => { @@ -38,7 +38,7 @@ describe('actions', () => { const getState = () => ('Bob'); const dispatch = jest.fn(); const name = 'Dan'; - const id = v4(); + const id = shortid.generate(); actions.signIn(name, id)(dispatch, getState); expect(dispatch) .toHaveBeenCalledWith({ diff --git a/src/client/assets/font/SourceSansPro-Light.ttf b/src/client/assets/font/SourceSansPro-Light.ttf new file mode 100755 index 0000000..5f64679 Binary files /dev/null and b/src/client/assets/font/SourceSansPro-Light.ttf differ diff --git a/src/client/assets/font/SourceSansPro-Regular.ttf b/src/client/assets/font/SourceSansPro-Regular.ttf new file mode 100755 index 0000000..91e9ea5 Binary files /dev/null and b/src/client/assets/font/SourceSansPro-Regular.ttf differ diff --git a/src/client/assets/font/SourceSansPro-Semibold.ttf b/src/client/assets/font/SourceSansPro-Semibold.ttf new file mode 100755 index 0000000..5020594 Binary files /dev/null and b/src/client/assets/font/SourceSansPro-Semibold.ttf differ diff --git a/src/client/assets/img/babel.svg b/src/client/assets/img/babel.svg new file mode 100644 index 0000000..0cd268d --- /dev/null +++ b/src/client/assets/img/babel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/client/assets/img/cloneBanner.jpg b/src/client/assets/img/cloneBanner.jpg new file mode 100644 index 0000000..14f99ba Binary files /dev/null and b/src/client/assets/img/cloneBanner.jpg differ diff --git a/src/client/assets/img/digitalocean.svg b/src/client/assets/img/digitalocean.svg new file mode 100644 index 0000000..c543650 --- /dev/null +++ b/src/client/assets/img/digitalocean.svg @@ -0,0 +1 @@ +digitalocean \ No newline at end of file diff --git a/src/client/assets/img/express.svg b/src/client/assets/img/express.svg new file mode 100644 index 0000000..e33a588 --- /dev/null +++ b/src/client/assets/img/express.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/client/assets/img/jest.svg b/src/client/assets/img/jest.svg new file mode 100644 index 0000000..028d96b --- /dev/null +++ b/src/client/assets/img/jest.svg @@ -0,0 +1 @@ + diff --git a/src/client/assets/img/mongodb.svg b/src/client/assets/img/mongodb.svg new file mode 100644 index 0000000..5abc861 --- /dev/null +++ b/src/client/assets/img/mongodb.svg @@ -0,0 +1 @@ + diff --git a/src/client/assets/img/react.svg b/src/client/assets/img/react.svg new file mode 100644 index 0000000..68698d0 --- /dev/null +++ b/src/client/assets/img/react.svg @@ -0,0 +1 @@ + diff --git a/src/client/assets/img/redux.svg b/src/client/assets/img/redux.svg new file mode 100644 index 0000000..6e9049d --- /dev/null +++ b/src/client/assets/img/redux.svg @@ -0,0 +1 @@ + diff --git a/src/client/assets/img/semaphor.svg b/src/client/assets/img/semaphor.svg new file mode 100644 index 0000000..2536158 --- /dev/null +++ b/src/client/assets/img/semaphor.svg @@ -0,0 +1 @@ +semaphor \ No newline at end of file diff --git a/src/client/assets/img/webpack.svg b/src/client/assets/img/webpack.svg new file mode 100644 index 0000000..1da1670 --- /dev/null +++ b/src/client/assets/img/webpack.svg @@ -0,0 +1 @@ +icon-square-big diff --git a/src/client/components/chatmain/ChatContent.jsx b/src/client/components/chatmain/ChatContent.jsx index f9ca99d..36fd681 100644 --- a/src/client/components/chatmain/ChatContent.jsx +++ b/src/client/components/chatmain/ChatContent.jsx @@ -13,10 +13,10 @@ class ChatContent extends React.Component { id="chat-content" className="chat-content scroller" > - {messages.length && messages.map((message) => + {messages.length > 0 && messages.map((message) => )} diff --git a/src/client/components/chatmain/ChatMain.jsx b/src/client/components/chatmain/ChatMain.jsx index 98023f9..87195d8 100644 --- a/src/client/components/chatmain/ChatMain.jsx +++ b/src/client/components/chatmain/ChatMain.jsx @@ -39,7 +39,10 @@ export class ChatMain extends React.Component { }
- +
', () => { expect(toJson(wrapper)).toMatchSnapshot(); }); - xit('should call fetchMessages() at componentDidMount', () => { - // FIXME TODO need store to render nested connected component + it('should call fetchData() at lifecycle changes', () => { + const { props, component } = setup(); + const newProps = setup({convoName: 'react'}).props; + const wrapper = mount(component); + // fetchMessages called once on mount + expect(props.fetchMessages).toHaveBeenCalledTimes(1); + // fetchMessages not called, no change in props + wrapper.instance().componentDidUpdate(props); + expect(props.fetchMessages).toHaveBeenCalledTimes(1); + // fetchMessages called again on update + wrapper.instance().componentDidUpdate(newProps); + expect(props.fetchMessages).toHaveBeenCalledTimes(2); }); it('should receive the correct props from state', () => { diff --git a/src/client/components/chatmain/ChatToolbar.jsx b/src/client/components/chatmain/ChatToolbar.jsx index de8bec2..b46f279 100644 --- a/src/client/components/chatmain/ChatToolbar.jsx +++ b/src/client/components/chatmain/ChatToolbar.jsx @@ -1,9 +1,27 @@ import React from 'react'; -export const ChatToolbar = () => ( - -); +export const ChatToolbar = ({ messages, user }) => { + const currentUser = user.username ? [{...user}] : []; + const users = messages + .concat(currentUser) + .map((message) => message.username) + .filter((username, i, arr) => arr.indexOf(username) === i) + .map((username) => ( +
  • + {username} + {/*
    */} +
  • + )); + return ( + + ); +}; export default ChatToolbar; diff --git a/src/client/components/chatmain/ChatToolbar.spec.js b/src/client/components/chatmain/ChatToolbar.spec.js index d1548bb..3ef005c 100644 --- a/src/client/components/chatmain/ChatToolbar.spec.js +++ b/src/client/components/chatmain/ChatToolbar.spec.js @@ -3,18 +3,31 @@ import React from 'react'; import renderer from 'react-test-renderer'; import ChatToolbar from './ChatToolbar.jsx'; -const setup = () => { - const component = ; +const setup = (propOverrides) => { + const props = Object.assign({ + messages: [ + {username: 'Joe'}, + {username: 'Josh'}, + ], + user: {username: 'Liz'} + }, propOverrides); return { - component + props }; }; describe('', () => { - it('should render correctly', () => { - const { component } = setup(); - const renderedComponent = renderer.create(component); + it('should render correctly when logged in', () => { + const { props } = setup(); + const renderedComponent = renderer.create(); + const tree = renderedComponent.toJSON(); + expect(tree).toMatchSnapshot(); + }); + + it('should render correctly when not logged in', () => { + const { props } = setup({user: {}}); + const renderedComponent = renderer.create(); const tree = renderedComponent.toJSON(); expect(tree).toMatchSnapshot(); }); diff --git a/src/client/components/chatmain/chatmain.scss b/src/client/components/chatmain/chatmain.scss index eb4e9de..a435fdb 100644 --- a/src/client/components/chatmain/chatmain.scss +++ b/src/client/components/chatmain/chatmain.scss @@ -1,7 +1,7 @@ @import '../../mixins/_variables.scss'; .loading-container { - background-color: rgba(0, 0, 0, 0.5); + background-color: $trans-black5; display: flex; align-items: center; justify-content: center; @@ -14,8 +14,8 @@ width: 80px; height: 80px; border-radius: 100%; - border: 10px solid rgba(255, 255, 255, 0.2); - border-top-color: #FFF; + border: 10px solid $trans-white2; + border-top-color: $white; animation: spin 1s infinite linear; } @@ -58,6 +58,7 @@ .chat-toolbar { position: relative; + padding: 20px; top: 0; bottom: 0; right: 0; @@ -65,8 +66,42 @@ flex-shrink: 0; display: flex; width: 70px; - // - border-left: 1px solid #f1f1f1; + border-left: 1px solid $seashell; + + .roster { + list-style: none; + margin: 0; + padding: 0; + } + + .avatar { + display: inline-block; + position: relative; + background-size: cover; + + img { + height: 30px; + width: 30px; + border-radius: 4px; + } + } + + .status { + background-color: $white; + box-shadow: inset 0 0 0 2px $supernova; + position: absolute; + top: 5px; + left: -6px; + overflow: hidden; + display: flex; + flex-direction: column; + justify-content: center; + width: 7px; + height: 7px; + border: 2px solid $white; + border-radius: 80px; + box-sizing: content-box; + } } .chat-header { @@ -86,7 +121,7 @@ padding-right: 18px; img { - border: 3px solid white; + border: 3px solid $white; border-radius: 4px; } } @@ -97,7 +132,7 @@ overflow: hidden; display: inline-block; max-width: 100%; - color: white; + color: $white; font-size: 24px; line-height: 40px; font-weight: 300; @@ -105,7 +140,7 @@ } .chat-user-avatar { - border-left: 1px solid rgba(0, 0, 0, 0.1); + border-left: 1px solid $trans-black1; padding: 8px 0 8px 19px; margin-right: 19px; } @@ -129,7 +164,7 @@ overflow-y: auto; flex-grow: 1; flex-shrink: 1; - background-color: #fdfdfd; + background-color: $romance; .chat-item { transition: background-color 0.2s linear; @@ -337,7 +372,7 @@ .token.important, .token.bold { - font-weight: bold; + font-weight: 600; } .token.italic { @@ -352,7 +387,7 @@ .chat-input { align-self: flex-end; - background-color: #fdfdfd; + background-color: $romance; flex-shrink: 0; width: 100%; border-top: 1px solid rgba(0, 0, 0, 0.05); @@ -379,11 +414,11 @@ min-height: 44px; margin: 0; padding: 0; - background-color: #fff; + background-color: $white; box-shadow: none; border: 0; outline: none; - color: black; + color: $black; font-size: 1em; line-height: 1.38em; resize: none; @@ -393,10 +428,11 @@ position: absolute; right: 0; top: 0; - height: 50px; + height: 42px; background-color: $maroon; - color: white; + color: $white; border-radius: 4px; + margin-right: 4px; border: 0; } } @@ -412,8 +448,8 @@ text-decoration: none; border: none; border-radius: 6px; - background: #ea9448; - color: #fff; + background: $sea-buckthorn; + color: $white; -webkit-flex-shrink: 0; -ms-flex-negative: 0; flex-shrink: 0; @@ -427,11 +463,11 @@ box-sizing: border-box; display: inline-block; padding: 9.6px 42px; - background-color: #46bc99; + background-color: $puerto-rico; border: none; border-radius: 6px; outline: none; - color: #ffffff; + color: $white; text-transform: uppercase; letter-spacing: 0.1em; text-align: center; diff --git a/src/client/components/chatmenu/ChatMenu.jsx b/src/client/components/chatmenu/ChatMenu.jsx index a482377..e6da959 100644 --- a/src/client/components/chatmenu/ChatMenu.jsx +++ b/src/client/components/chatmenu/ChatMenu.jsx @@ -1,5 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; +import { Link } from 'react-router'; import { openCreateRoomModal, closeModal, @@ -26,7 +27,8 @@ export class ChatMenu extends React.Component { })); } handleMouseLeave(e) { - if (e.clientX < 74) return; // ignore leaving to minibar + /* istanbul ignore next: ignore leaving to minibar */ + if (e.clientX < 74) return; this.toggleActive(); } handleMinibarButtonClick() { @@ -39,9 +41,14 @@ export class ChatMenu extends React.Component {