Skip to content

Commit a7f8051

Browse files
committed
Initial commit
0 parents  commit a7f8051

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+11282
-0
lines changed

.babelrc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": ["react", "es2015", "stage-2"]
3+
}

.eslintrc

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"parser": "babel-eslint",
3+
"extends": "airbnb",
4+
"rules": {
5+
"react/prefer-stateless-function": 0,
6+
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
7+
"no-useless-constructor": 0,
8+
"comma-dangle": ["error", "never"],
9+
"class-methods-use-this": 0,
10+
"react/forbid-prop-types": 0
11+
},
12+
"env": {
13+
"browser": true,
14+
"mocha": true
15+
}
16+
}

.gitignore

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
6+
# Runtime data
7+
pids
8+
*.pid
9+
*.seed
10+
11+
# Directory for instrumented libs generated by jscoverage/JSCover
12+
lib-cov
13+
14+
# Coverage directory used by tools like istanbul
15+
coverage
16+
17+
# nyc test coverage
18+
.nyc_output
19+
20+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21+
.grunt
22+
23+
# node-waf configuration
24+
.lock-wscript
25+
26+
# Compiled binary addons (http://nodejs.org/api/addons.html)
27+
build/Release
28+
29+
# Dependency directories
30+
node_modules
31+
jspm_packages
32+
33+
# Optional npm cache directory
34+
.npm
35+
36+
# Optional REPL history
37+
.node_repl_history
38+
39+
# Build
40+
dist

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2016 Tushar Arora
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.

README.md

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# react-flux-chat (whatsapp-web-demo)
2+
3+
## Installation
4+
5+
```
6+
npm install
7+
```
8+
## Launch
9+
10+
```
11+
npm start
12+
```
13+
Visit [http://localhost:8080](http://localhost:8080)
14+
## Test
15+
```
16+
npm test
17+
```
18+
## Technologies Used
19+
* [ReactJS](https://facebook.github.io/react/) ([Flux](https://facebook.github.io/flux/))
20+
* [Bootstrap](http://getbootstrap.com/)
21+
* [Webpack](https://webpack.github.io/) (Module bundler)
22+
* [Eslint](http://eslint.org/) (Linting ES6 and ReactJS)
23+
* [Babel](https://babeljs.io/) (ES6 transpilation)
24+
* [Mocha](https://mochajs.org/) (Test runner)
25+
* [Enzyme](http://airbnb.io/enzyme/) (Test utility for ReactJS)
26+
* [Chai](http://chaijs.com/) (Assertions)
27+
* [Sinon](http://sinonjs.org/) (Test Spy)
28+
* Websockets
29+
* [DexieJS](http://dexie.org/) (IndexedDB wrapper library)
30+
31+
## Thought Process
32+
1. I started with setting up a basic build system for **React** and **ES6** using **babel** and **webpack**. It involved basic housekeeping and moving some files from src to dist.
33+
2. Then I focused on getting a single component on the screen to look and feel like a chat application with two column layout.
34+
3. Looked for some lightweight css library or framework. After looking at multiple options like Skeleton and Cardinal, I settled for **Bootstrap** (Even though it has a lot of components which I did not want but it's possible to compile whichever less or sass components we need for the project) for its simplicity and my previous experience using it.
35+
4. Started writing css for the component. At first I took css classnames as whatever looked appropriate at that time, then renamed them afterwards according to the layout and semantics.
36+
5. Made sure that layouts worked on mobile and tablet devices and wrote media queries for that. (Only 2-3 media queries were required to be manually written, rest were handled by bootstrap).
37+
6. Then I thought about breaking that single component into pieces. Now, there is one main component 'App'. It has two child components as 'ThreadPanel' as the left column and 'ChatPanel' as the right column. These two components have their own child components as well.
38+
7. Wrote APIUtils as POJO Singletons so that instance need not be created before use.
39+
8. Then I reasoned about Flux architecture and how to integrate it to the application to ensure unidirectional data flow.
40+
9. Meanwhile I setup the test environment using **Mocha**. Used **chai** for assertions, **sinon** for spying on functions and **jsdom** for mocking browser like enviromnment. Among testing utilities I compared between Jest and Enzyme and found Enzyme's API more expressive and simpler to use.
41+
10. Started writing components and their respective tests. Had to refactor and rewrite code multiple times to create components with featurs like single responsiblity and easy to test.
42+
11. Worked on setting up unidirectional data flow using **Flux**. Setup **Dispatcher**, **Action Creators**, **Stores** and made sure that components are receiving the appropriate data and dispatched events.
43+
12. Started looking for client side storage solutions. I was familiar with cookies/localStorage/sessionStorage but wanted to try out some something more structured. IndexedDB was my first choice (supports indexes) but it would have taken some time to setup low level stuff, so I used **DexieJS** which is a wrapper library for cross browser IndexedDB implementation.
44+
13. Wrote **WebSockets** and **Dexie** utils (POJO Singletons) for getting echoed response and saving data to IndexedDB respectively. Created basic schema for message object.
45+
14. Improved upon build process by moving frontend resources to be installed as node_modules and made sure that css, html and other resources bundling was automated.
46+
47+
## Structure
48+
![React-Flux-Demo-Structure](https://github.com/tushararora/react-flux-chat/blob/master/structure.png "react-flux-chat structure")
49+
50+
## Parameters for choice of technologies
51+
1. Does it follow the current direction of web? (Web Components, Web Views)
52+
2. Does it have good documentation and community support?
53+
3. Is it actively maintained?
54+
4. Does it have simple and expressive API?
55+
5. Is it ready to use?
56+
57+
## Notes
58+
1. Tested on latest versions of Firefox and Chrome on Ubuntu 14.04
59+
2. Used Node.js v6.7.0 and npm v3.10.3

package.json

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"name": "react-flux-chat",
3+
"version": "1.0.0",
4+
"description": "React-Flux-Chat example inspired by whatsapp web",
5+
"main": "index.js",
6+
"scripts": {
7+
"start": "npm run build",
8+
"build": "webpack -d && webpack-dev-server --hot --inline --progress --colors --watch --display-error-details --display-cached --content-base dist/ ",
9+
"test": "mocha test/.setup.js ./test/ --recursive --reporter progress"
10+
},
11+
"keywords": [
12+
"reactjs",
13+
"flux",
14+
"chat",
15+
"webpack",
16+
"mocha",
17+
"chai",
18+
"sinon",
19+
"babel"
20+
],
21+
"author": "Tushar Arora",
22+
"license": "MIT",
23+
"dependencies": {
24+
"bootstrap": "^3.3.7",
25+
"chai": "^3.5.0",
26+
"classnames": "^2.2.5",
27+
"dexie": "^1.4.2",
28+
"dirty-chai": "^1.2.2",
29+
"enzyme": "^2.4.1",
30+
"eslint-plugin-chai-expect": "^1.1.1",
31+
"eslint-plugin-import": "^2.0.0",
32+
"extract-text-webpack-plugin": "^1.0.1",
33+
"fbemitter": "^2.1.1",
34+
"flux": "^3.0.0",
35+
"font-awesome": "^4.6.3",
36+
"html-webpack-plugin": "^2.22.0",
37+
"keymirror": "^0.1.1",
38+
"object-assign": "^4.1.0",
39+
"react": "^15.3.2",
40+
"react-dom": "^15.3.2",
41+
"react-timeago": "^3.1.3",
42+
"sinon": "^1.17.6",
43+
"webpack": "^1.13.2"
44+
},
45+
"devDependencies": {
46+
"babel-eslint": "^7.0.0",
47+
"babel-loader": "^6.2.5",
48+
"babel-preset-es2015": "^6.16.0",
49+
"babel-preset-react": "^6.16.0",
50+
"babel-preset-stage-2": "^6.16.0",
51+
"babel-register": "^6.16.3",
52+
"css-loader": "^0.25.0",
53+
"eslint": "^3.7.0",
54+
"eslint-config-airbnb": "^12.0.0",
55+
"eslint-loader": "^1.5.0",
56+
"eslint-plugin-import": "^1.16.0",
57+
"eslint-plugin-jsx-a11y": "^2.2.2",
58+
"eslint-plugin-react": "^6.3.0",
59+
"file-loader": "^0.9.0",
60+
"html-webpack-plugin": "^2.22.0",
61+
"jsdom": "^9.5.0",
62+
"mocha": "^3.1.0",
63+
"react-addons-test-utils": "^15.3.2",
64+
"style-loader": "^0.13.1",
65+
"url-loader": "^0.5.7",
66+
"webpack-dev-server": "^1.16.1"
67+
}
68+
}

src/app/App.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React, { Component } from 'react';
2+
import ReactDOM from 'react-dom';
3+
import ThreadPanel from './components/ThreadPanel';
4+
import ChatPanel from './components/ChatPanel';
5+
import APIUtils from './utils/APIUtils';
6+
import WebSocketUtils from './utils/WebSocketUtils';
7+
import DBUtils from './utils/DBUtils';
8+
9+
/* Require css resources to be bundled with html */
10+
11+
require('bootstrap/dist/css/bootstrap.min.css');
12+
require('font-awesome/css/font-awesome.min.css');
13+
require('../assets/css/style.css');
14+
15+
/* Hardcoded current user passed on to child components */
16+
17+
const currentUser = {
18+
name: 'FooBar'
19+
};
20+
21+
/**
22+
* Class representing entry point
23+
* @extends Component
24+
*/
25+
26+
class App extends Component {
27+
28+
render() {
29+
return (
30+
<div className="container">
31+
<div className="row">
32+
<ThreadPanel />
33+
<ChatPanel currentUser={currentUser} />
34+
</div>
35+
</div>
36+
);
37+
}
38+
}
39+
40+
/* Initialize utils */
41+
42+
APIUtils.fetchAllThreads();
43+
WebSocketUtils.init();
44+
DBUtils.init();
45+
46+
/* Render App component to root */
47+
48+
ReactDOM.render(<App />, document.getElementById('root'));
49+
50+
export default App;
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import AppDispatcher from '../dispatcher/AppDispatcher';
2+
import AppConstants from '../constants/AppConstants';
3+
import APIUtils from '../utils/APIUtils';
4+
import MessageUtils from '../utils/MessageUtils';
5+
6+
const ACTION_TYPES = AppConstants.ACTION_TYPES;
7+
8+
/* MessageActionCreators for message actions */
9+
10+
const MessageActionCreators = {
11+
12+
/**
13+
* Create message and dispatch to MessageStore
14+
* @param {string} text
15+
* @param {number} currentThreadId
16+
* @param {string} author
17+
*/
18+
19+
createMessage(text, currentThreadId, author) {
20+
AppDispatcher.dispatch({
21+
type: ACTION_TYPES.CREATE_MESSAGE,
22+
text,
23+
currentThreadId,
24+
author
25+
});
26+
const message = MessageUtils.getCreatedMessageData(text, currentThreadId, author);
27+
APIUtils.createMessage(message);
28+
},
29+
30+
/**
31+
* Receive messages and dispatch to MessageStore
32+
* @param {array} messages
33+
*/
34+
35+
receiveAllMessages(messages) {
36+
AppDispatcher.dispatch({
37+
type: ACTION_TYPES.RECEIVE_MESSAGES,
38+
messages
39+
});
40+
},
41+
42+
/**
43+
* Receive message and dispatch to MessageStore
44+
* @param {string} text
45+
*/
46+
47+
receiveMessage(text) {
48+
AppDispatcher.dispatch({
49+
type: ACTION_TYPES.RECEIVE_MESSAGE,
50+
text
51+
});
52+
}
53+
};
54+
55+
export default MessageActionCreators;
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import AppDispatcher from '../dispatcher/AppDispatcher';
2+
import AppConstants from '../constants/AppConstants';
3+
import APIUtils from '../utils/APIUtils';
4+
5+
const ACTION_TYPES = AppConstants.ACTION_TYPES;
6+
7+
/* ThreadActionCreators for thread actions */
8+
9+
const ThreadActionCreators = {
10+
11+
/**
12+
* Click thread and dispatch to ThreadStore
13+
* @param {number} threadId
14+
*/
15+
16+
clickThread(threadId) {
17+
AppDispatcher.dispatch({
18+
type: ACTION_TYPES.CLICK_THREAD,
19+
threadId
20+
});
21+
APIUtils.fetchAllMessages(threadId);
22+
},
23+
24+
/**
25+
* Recieve threads and dispatch to ThreadStore
26+
* @param {array} threads
27+
*/
28+
29+
receiveAllThreads(threads) {
30+
AppDispatcher.dispatch({
31+
type: ACTION_TYPES.RECEIVE_ALL_THREADS,
32+
threads
33+
});
34+
}
35+
};
36+
37+
export default ThreadActionCreators;

0 commit comments

Comments
 (0)