Skip to content

Commit 8307ecd

Browse files
committed
initial commit for github
0 parents  commit 8307ecd

File tree

8 files changed

+299
-0
lines changed

8 files changed

+299
-0
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
## React Coding Challenge
2+
3+
This code challenge tests your skills in react js, testing and modular design. The purpose of the application is to correctly render a stream of messages coming from an api. Different messages will be coded different colors and require slightly different rendering. The rules are described in detail below.
4+
5+
This challenge already includes an API response. It is not required or expected for you to make any changes to this interaction. The 3 priorities that we provide you are:
6+
* 1 = error
7+
* 2 = warning
8+
* 3 = info
9+
10+
### Acceptance Criteria
11+
12+
1. Messages should be rendered in a grid-like structure determined by you. The newest messages should appear at the top of the grid.
13+
2. The messages should be color coded depending on the priority of the message. The appropriate color per priority is:
14+
* error: #F56236
15+
* warning: #FCE788
16+
* info: #88FCA3
17+
3. Each time a message with the priority level of error is received, a snackbar containing the error message should also appear at the top of the application. The error should disappear in 2 seconds or when another error message takes its place.
18+
4. A user should be able to clear the grid of all messages at any point.
19+
5. A user should be able to start and stop incoming messages. By default the messages should be running and displaying on the grid.
20+
6. Use material-ui components and JSS styles.
21+
7. Test your application to the degree that you feel comfortable with. No specific testing frameworks are required.

package.json

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"name": "help.com-react-coding-challange",
3+
"version": "0.0.1",
4+
"description": "help.com react coding challange",
5+
"main": "index.js",
6+
"scripts": {
7+
"start": "node server",
8+
"lint": "esw src/",
9+
"test": "jest"
10+
},
11+
"author": "Help.com",
12+
"license": "No license",
13+
"dependencies": {
14+
"@material-ui/core": "^3.8.2",
15+
"chance": "^0.8.0",
16+
"connect": "^3.4.1",
17+
"html-webpack-plugin": "^2.28.0",
18+
"lodash": "^4.17.2",
19+
"prop-types": "^15.5.8",
20+
"react": "^16.6.3",
21+
"react-dom": "^16.6.3",
22+
"react-hot-loader": "^4.3.4",
23+
"webpack": "^2.4.1"
24+
},
25+
"devDependencies": {
26+
"babel-core": "^6.10.4",
27+
"babel-eslint": "^7.1.0",
28+
"babel-jest": "^19.0.0",
29+
"babel-loader": "^6.2.4",
30+
"babel-plugin-transform-object-assign": "^6.22.0",
31+
"babel-plugin-transform-react-jsx-source": "^6.9.0",
32+
"babel-preset-env": "^1.3.1",
33+
"babel-preset-react": "^6.5.0",
34+
"babel-preset-stage-1": "^6.16.0",
35+
"css-loader": "^0.23.1",
36+
"eslint": "^5.12.0",
37+
"eslint-config-airbnb": "^17.1.0",
38+
"eslint-plugin-import": "^2.14.0",
39+
"eslint-plugin-jsx-a11y": "^6.1.2",
40+
"eslint-plugin-react": "^7.12.3",
41+
"eslint-watch": "^4.0.2",
42+
"jest": "^19.0.0",
43+
"style-loader": "^0.13.0",
44+
"webpack-dev-server": "^2.4.2"
45+
},
46+
"jest": {
47+
"collectCoverageFrom": [
48+
"**/src/**/*.js",
49+
"!**/__tests__/**"
50+
],
51+
"moduleDirectories": [
52+
"node_modules",
53+
"src"
54+
],
55+
"setupFiles": [
56+
"./jest-setup.js"
57+
]
58+
}
59+
}

server.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const path = require('path')
2+
const webpack = require('webpack')
3+
4+
const WebpackDevServer = require('webpack-dev-server')
5+
6+
const config = require('./webpack/config')
7+
const port = process.env.PORT || 9000
8+
9+
new WebpackDevServer(webpack(config), {
10+
publicPath: config.output.publicPath,
11+
hot: true,
12+
historyApiFallback: true,
13+
stats: { colors: true, chunks: false },
14+
}).listen(port, 'localhost', function (err) {
15+
if (err) {
16+
console.log(err)
17+
}
18+
console.log(`Listening at localhost:${port}`)
19+
})

src/api.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import Chance from 'chance'
2+
import lodash from 'lodash'
3+
4+
class MessageGenerator {
5+
constructor(options) {
6+
this.messageCallback = options.messageCallback
7+
this.stopGeneration = false
8+
this.chance = new Chance()
9+
}
10+
11+
stop() {
12+
this.stopGeneration = true
13+
}
14+
15+
start() {
16+
this.stopGeneration = false
17+
this.generate()
18+
}
19+
20+
isStarted() {
21+
return !this.stopGeneration
22+
}
23+
24+
/**
25+
* priority from 1 to 3, 1 = error, 2 = warning, 3 = info
26+
* */
27+
generate() {
28+
if (this.stopGeneration) {
29+
return
30+
}
31+
const message = this.chance.string()
32+
const priority = lodash.random(1, 3)
33+
const nextInMS = lodash.random(500, 3000)
34+
this.messageCallback({
35+
message,
36+
priority,
37+
})
38+
setTimeout(() => {
39+
this.generate()
40+
}, nextInMS)
41+
}
42+
}
43+
44+
export default MessageGenerator

src/components/message-list.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import React, { Component } from 'react'
2+
import Button from '@material-ui/core/Button'
3+
import Api from '../api'
4+
5+
class MessageList extends Component {
6+
constructor(...args) {
7+
super(...args)
8+
this.state = {
9+
messages: [],
10+
}
11+
}
12+
13+
componentWillMount() {
14+
this.api = new Api({
15+
messageCallback: (message) => {
16+
this.messageCallback(message)
17+
},
18+
})
19+
}
20+
21+
componentDidMount() {
22+
this.api.start()
23+
}
24+
25+
messageCallback(message) {
26+
const { messages } = this.state
27+
this.setState({
28+
messages: [
29+
...messages.slice(),
30+
message,
31+
],
32+
}, () => {
33+
// Included to support initial direction. Please remove upon completion
34+
console.log(messages)
35+
})
36+
}
37+
38+
renderButton() {
39+
const isApiStarted = this.api.isStarted()
40+
return (
41+
<Button
42+
variant="contained"
43+
onClick={() => {
44+
if (isApiStarted) {
45+
this.api.stop()
46+
} else {
47+
this.api.start()
48+
}
49+
this.forceUpdate()
50+
}}
51+
>
52+
{isApiStarted ? 'Stop Messages' : 'Start Messages'}
53+
</Button>
54+
)
55+
}
56+
57+
render() {
58+
return (
59+
<div>
60+
{this.renderButton()}
61+
</div>
62+
)
63+
}
64+
}
65+
66+
export default MessageList

src/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<head>
3+
<meta charset="UTF-8">
4+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
5+
<link href='https://fonts.googleapis.com/css?family=Montserrat' rel='stylesheet' type='text/css'/>
6+
<title>Help.com React code challenge</title>
7+
<link rel="shortcut icon" href="//assets.help.com/favicons/favicon.ico">
8+
</head>
9+
<body>
10+
<div class="main"></div>
11+
</body>
12+
</html>

src/index.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from 'react'
2+
import ReactDOM from 'react-dom'
3+
import MessageList from './components/message-list'
4+
5+
const NewApp = require('./components/message-list').default
6+
7+
function renderApp(App) {
8+
ReactDOM.render(<App />, document.querySelector('.main'))
9+
}
10+
11+
renderApp(MessageList)
12+
13+
if (module.hot) {
14+
module.hot.accept('./components/message-list', () => {
15+
renderApp(NewApp)
16+
})
17+
}

webpack/config.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* eslint-env node */
2+
3+
const path = require('path')
4+
const webpack = require('webpack')
5+
const HtmlWebpackPlugin = require('html-webpack-plugin')
6+
7+
8+
const basePath = '/'
9+
10+
module.exports = {
11+
devtool: 'eval-source-map',
12+
13+
entry: [
14+
'react-hot-loader/patch',
15+
`webpack-dev-server/client?http://localhost:${9000}`,
16+
'webpack/hot/only-dev-server',
17+
path.resolve(__dirname, '../src/index'),
18+
],
19+
20+
output: {
21+
path: path.resolve(__dirname, '../dist'),
22+
filename: 'bundle.js',
23+
publicPath: basePath,
24+
},
25+
26+
resolve: {
27+
extensions: ['.js'],
28+
modules: [
29+
'src',
30+
'node_modules',
31+
],
32+
alias: {
33+
react: path.resolve(__dirname, '../node_modules/react'),
34+
'react-dom': path.resolve(__dirname, '../node_modules/react-dom'),
35+
},
36+
},
37+
38+
plugins: [
39+
new HtmlWebpackPlugin({
40+
template: './src/index.html',
41+
}),
42+
new webpack.HotModuleReplacementPlugin(),
43+
new webpack.DefinePlugin({
44+
BASE_PATH: JSON.stringify(basePath),
45+
}),
46+
],
47+
48+
module: {
49+
rules: [{
50+
test: /\.js$/,
51+
use: ['babel-loader?cacheDirectory'],
52+
include: [
53+
path.resolve(__dirname, '../src'),
54+
],
55+
}],
56+
},
57+
58+
performance: {
59+
hints: false,
60+
},
61+
}

0 commit comments

Comments
 (0)