├── .babelrc
├── .eslintrc
├── .gitignore
├── data.js
├── index.html
├── package.json
├── server.js
├── src
├── js
│ ├── components
│ │ ├── App
│ │ │ ├── App.js
│ │ │ └── index.js
│ │ ├── Header
│ │ │ ├── Header.js
│ │ │ ├── Header.scss
│ │ │ ├── SubHeader.js
│ │ │ └── index.js
│ │ └── Widgets
│ │ │ ├── Widget.js
│ │ │ ├── Widgets.js
│ │ │ ├── Widgets.scss
│ │ │ └── index.js
│ └── index.js
├── styles
│ └── base.scss
└── utils
│ └── translator.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react", "stage-0"],
3 | "plugins": [
4 | "transform-object-rest-spread",
5 | "transform-runtime",
6 | [
7 | "react-intl", {
8 | "messagesDir": "./build/messages",
9 | "enforceDescriptions": false
10 | }
11 | ]
12 | ],
13 | "env": {
14 | "development": {
15 | "presets": ["react-hmre"]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "parser": "babel-eslint",
4 | "env": {
5 | "browser": true,
6 | "node": true,
7 | "mocha": true
8 | },
9 | "ecmaFeatures": {
10 | "forOf": true,
11 | "jsx": true,
12 | "es6": true
13 | },
14 | "rules": {
15 | "react/prefer-stateless-function": 0,
16 | "react/jsx-curly-spacing": 0,
17 | },
18 | "plugins": [
19 | "jsx-a11y"
20 | ],
21 | "settings": {
22 | "import/resolver": "webpack",
23 | },
24 | }
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### node etc ###
2 |
3 | # Logs
4 | logs
5 | *.log
6 | *.rdb
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 |
13 | # Directory for instrumented libs generated by jscoverage/JSCover
14 | lib-cov
15 |
16 | # Coverage directory used by tools like istanbul
17 | coverage
18 |
19 | # Compiled Dirs (http://nodejs.org/api/addons.html)
20 | build/
21 |
22 | # Dependency directories
23 | node_modules/
24 |
25 | .DS_Store
26 |
--------------------------------------------------------------------------------
/data.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'Preethi',
3 | lastLogin: 'Thu Aug 04 2016 19:58:19 GMT-0700 (PDT)',
4 | notifications: [
5 | {
6 | id: 1,
7 | read: false,
8 | createdAt: 1469336316043,
9 | },
10 | {
11 | id: 2,
12 | read: false,
13 | createdAt: 1469336288833,
14 | },
15 | {
16 | id: 3,
17 | read: true,
18 | createdAt: 1469332088829,
19 | },
20 | {
21 | id: 4,
22 | read: false,
23 | createdAt: 1469332088732,
24 | },
25 | ],
26 | };
27 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Inbox Internationlized
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "inbox-react-intl",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "scripts": {
7 | "start": "cross-env NODE_ENV=development node server.js",
8 | "lint": "./node_modules/.bin/eslint src --fix",
9 | "build:langs": "./node_modules/.bin/babel-node src/utils/translator.js"
10 | },
11 | "author": "Preethi Kasireddy",
12 | "license": "ISC",
13 | "devDependencies": {
14 | "babel-cli": "^6.24.1",
15 | "babel-core": "^6.11.4",
16 | "babel-eslint": "^6.1.0",
17 | "babel-loader": "^6.2.4",
18 | "babel-plugin-react-intl": "^2.1.3",
19 | "babel-plugin-react-transform": "^2.0.2",
20 | "babel-plugin-transform-object-rest-spread": "^6.8.0",
21 | "babel-plugin-transform-runtime": "^6.9.0",
22 | "babel-preset-es2015": "^6.9.0",
23 | "babel-preset-react": "^6.11.1",
24 | "babel-preset-react-hmre": "^1.1.1",
25 | "babel-preset-stage-0": "^6.5.0",
26 | "babel-register": "^6.11.5",
27 | "cross-env": "^2.0.0",
28 | "css-loader": "^0.23.1",
29 | "eslint": "2.13.1",
30 | "eslint-config-airbnb": "^9.0.1",
31 | "eslint-import-resolver-webpack": "0.3.2",
32 | "eslint-loader": "^1.4.1",
33 | "eslint-plugin-import": "^1.11.1",
34 | "eslint-plugin-jsx-a11y": "1.2.0",
35 | "eslint-plugin-react": "^5.2.2",
36 | "exports-loader": "^0.6.3",
37 | "extract-text-webpack-plugin": "^1.0.1",
38 | "file-loader": "^0.9.0",
39 | "html-webpack-plugin": "^2.22.0",
40 | "imports-loader": "^0.6.5",
41 | "json-loader": "^0.5.4",
42 | "node-sass": "^3.8.0",
43 | "react-json-tree": "^0.10.0",
44 | "react-transform-hmr": "^1.0.4",
45 | "resolve-url": "^0.2.1",
46 | "resolve-url-loader": "^1.6.0",
47 | "sass-loader": "^4.0.0",
48 | "style-loader": "^0.13.1",
49 | "url-loader": "^0.5.7",
50 | "webpack": "^1.13.1",
51 | "webpack-dev-middleware": "^1.6.1",
52 | "webpack-dev-server": "^1.14.1",
53 | "webpack-hot-middleware": "^2.12.1"
54 | },
55 | "dependencies": {
56 | "classnames": "^2.2.5",
57 | "fs": "0.0.2",
58 | "glob": "^7.0.5",
59 | "intl": "^1.2.4",
60 | "lodash": "^4.13.1",
61 | "mkdirp": "^0.5.1",
62 | "path": "^0.12.7",
63 | "react": "^15.2.1",
64 | "react-dom": "^15.2.1",
65 | "react-intl": "^2.1.3",
66 | "sass": "^0.5.0"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const WebpackDevServer = require('webpack-dev-server');
3 | const config = require('./webpack.config');
4 |
5 | new WebpackDevServer(webpack(config), { // Start a server
6 | publicPath: config.output.publicPath,
7 | hot: true, // With hot reloading
8 | inline: false,
9 | historyApiFallback: true,
10 | headers: { 'Access-Control-Allow-Origin': '*' }, // Allow CORS requests
11 | quiet: true // Without logging
12 | }).listen(3000, 'localhost', function(err) {
13 | if (err) {
14 | console.error(err);
15 | } else {
16 | console.info('==> Server started. Listening at http://localhost:3000');
17 | }
18 | });
19 |
--------------------------------------------------------------------------------
/src/js/components/App/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import filter from 'lodash/filter';
3 | import Header from './../Header/index';
4 | import Widgets from './../Widgets/index';
5 | import Data from './../../../../data';
6 |
7 | class App extends Component {
8 | static propTypes = {
9 | children: PropTypes.node,
10 | }
11 |
12 | state = {
13 | name: Data.name,
14 | notifications: Data.notifications,
15 | lastLogin: Data.lastLogin,
16 | unreadCount: filter(Data.notifications, ['read', false]).length,
17 | }
18 |
19 | render() {
20 | const { name, unreadCount, lastLogin } = this.state;
21 | return (
22 |
23 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
34 | export default App;
35 |
--------------------------------------------------------------------------------
/src/js/components/App/index.js:
--------------------------------------------------------------------------------
1 | import App from './App';
2 | export default App;
3 |
--------------------------------------------------------------------------------
/src/js/components/Header/Header.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { FormattedMessage } from 'react-intl';
3 | import SubHeader from './SubHeader';
4 | import styles from './Header.scss';
5 | import classNames from 'classnames/bind';
6 | const cx = classNames.bind(styles);
7 |
8 | class Header extends Component {
9 | static propTypes = {
10 | name: PropTypes.string.isRequired,
11 | lastLogin: PropTypes.string.isRequired,
12 | unreadCount: PropTypes.number.isRequired,
13 | }
14 |
15 | render() {
16 | const { name, unreadCount, lastLogin } = this.props;
17 |
18 | return (
19 |
32 | );
33 | }
34 | }
35 |
36 | export default Header;
37 |
--------------------------------------------------------------------------------
/src/js/components/Header/Header.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/base.scss';
2 |
3 | .container {
4 | width: 100vw;
5 | }
6 |
7 | .text {
8 | text-align: center;
9 | vertical-align: middle;
10 | margin: 1em 0 0.5em 0;
11 | }
12 |
13 | .header-text {
14 | font-size: 32px;
15 | }
16 |
17 | .subheader-text {
18 | font-size: 24px;
19 | }
20 |
21 | .subheader-lastlogin {
22 | font-size: 16px;
23 | }
24 |
--------------------------------------------------------------------------------
/src/js/components/Header/SubHeader.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { FormattedMessage, FormattedPlural, FormattedNumber, FormattedRelative } from 'react-intl';
3 | import styles from './Header.scss';
4 | import classNames from 'classnames/bind';
5 | const cx = classNames.bind(styles);
6 |
7 | class SubHeader extends Component {
8 | static propTypes = {
9 | lastLogin: PropTypes.string.isRequired,
10 | unreadCount: PropTypes.number.isRequired,
11 | }
12 |
13 | render() {
14 | const { unreadCount, lastLogin } = this.props;
15 |
16 | return (
17 |
18 |
19 |
25 |
28 |
29 | ),
30 | notifications: (
31 |
36 | ),
37 | }}
38 | />
39 |
40 |
41 | }}
45 | />
46 |
47 |
48 | );
49 | }
50 | }
51 |
52 | export default SubHeader;
53 |
--------------------------------------------------------------------------------
/src/js/components/Header/index.js:
--------------------------------------------------------------------------------
1 | import Header from './Header';
2 | export default Header;
3 |
--------------------------------------------------------------------------------
/src/js/components/Widgets/Widget.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import styles from './Widgets.scss';
3 | import classNames from 'classnames/bind';
4 | const cx = classNames.bind(styles);
5 |
6 | class Widget extends Component {
7 | static propTypes = {
8 | header: PropTypes.string.isRequired,
9 | body: PropTypes.string.isRequired,
10 | }
11 |
12 | render() {
13 | const { header, body } = this.props;
14 |
15 | return (
16 |
17 |
18 | {header}
19 |
20 |
21 | {body}
22 |
23 |
24 | );
25 | }
26 | }
27 |
28 | export default Widget;
29 |
--------------------------------------------------------------------------------
/src/js/components/Widgets/Widgets.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { injectIntl, defineMessages } from 'react-intl';
3 | import Widget from './Widget';
4 | import styles from './Widgets.scss';
5 | import classNames from 'classnames/bind';
6 | const cx = classNames.bind(styles);
7 |
8 | const messages = defineMessages({
9 | widget1Header: {
10 | id: 'Widgets.widget1.header',
11 | defaultMessage: 'Creative header',
12 | },
13 | widget1Body: {
14 | id: 'Widgets.widget1.body',
15 | defaultMessage: 'Mark todays date: {date}',
16 | },
17 | widget2Header: {
18 | id: 'Widgets.widget2.header',
19 | defaultMessage: 'Here is another widget',
20 | },
21 | widget2Body: {
22 | id: 'Widgets.widget2.body',
23 | defaultMessage: 'Hello. How is your day going?',
24 | },
25 | widget3Header: {
26 | id: 'Widgets.widget3.header',
27 | defaultMessage: 'Yet another widget',
28 | },
29 | widget3Body: {
30 | id: 'Widgets.widget3.body',
31 | defaultMessage: 'What is the meaning of life, my friend?',
32 | },
33 | widget4Header: {
34 | id: 'Widgets.widget4.header',
35 | defaultMessage: 'This is the last widget',
36 | },
37 | widget4Body: {
38 | id: 'Widgets.widget4.body',
39 | defaultMessage: 'I love React so much!',
40 | },
41 | });
42 |
43 | class Widgets extends Component {
44 | static propTypes = {
45 | intl: PropTypes.object.isRequired,
46 | }
47 |
48 | render() {
49 | const { formatMessage, formatDate } = this.props.intl;
50 | const currentDate = Date.now();
51 | return (
52 |
53 |
54 |
64 |
65 |
66 |
70 |
71 |
72 |
76 |
77 |
78 |
82 |
83 |
84 | );
85 | }
86 | }
87 |
88 | export default injectIntl(Widgets);
89 |
--------------------------------------------------------------------------------
/src/js/components/Widgets/Widgets.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/base.scss';
2 |
3 | .widgets-container {
4 | padding: 0;
5 | margin: 40px;
6 | display: flex;
7 | flex-flow: row wrap;
8 | justify-content: space-around;
9 | color: #666A86;
10 | }
11 |
12 | .widget-header {
13 | font-size: 18px;
14 | line-height: 22px;
15 | font-weight: 500;
16 | border-bottom: 1px solid #666A86;
17 | }
18 |
19 | .widget-body {
20 | padding-top: 20px;
21 | font-size: 14px;
22 | }
23 |
24 | .widget-item-container {
25 | padding: 15px;
26 | margin: 20px;
27 | background-color: #DCEDFF;
28 | border: 2px solid;
29 | border-radius: 5px;
30 | text-align: center;
31 | height: 250px;
32 | width: 350px;
33 | }
--------------------------------------------------------------------------------
/src/js/components/Widgets/index.js:
--------------------------------------------------------------------------------
1 | import Widgets from './Widgets';
2 | export default Widgets;
3 |
--------------------------------------------------------------------------------
/src/js/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import App from './components/App/index';
4 | import { IntlProvider, addLocaleData } from 'react-intl';
5 | import en from 'react-intl/locale-data/en';
6 | import es from 'react-intl/locale-data/es';
7 | import fr from 'react-intl/locale-data/fr';
8 | import it from 'react-intl/locale-data/it';
9 | import localeData from './../../build/locales/data.json';
10 |
11 | addLocaleData([...en, ...es, ...fr, ...it]);
12 |
13 | // Define user's language. Different browsers have the user locale defined
14 | // on different fields on the `navigator` object, so we make sure to account
15 | // for these different by checking all of them
16 | const language = (navigator.languages && navigator.languages[0]) ||
17 | navigator.language ||
18 | navigator.userLanguage;
19 |
20 | // Split locales with a region code
21 | const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0];
22 |
23 | // Try full locale, fallback to locale without region code, fallback to en
24 | const messages = localeData[language] || localeData[languageWithoutRegionCode] || localeData.en;
25 |
26 | // Render our root component into the div with id "root"
27 |
28 | // If browser doesn't support Intl (i.e. Safari), then we manually import
29 | // the intl polyfill and locale data.
30 | if (!window.Intl) {
31 | require.ensure([
32 | 'intl',
33 | 'intl/locale-data/jsonp/en.js',
34 | 'intl/locale-data/jsonp/es.js',
35 | 'intl/locale-data/jsonp/fr.js',
36 | 'intl/locale-data/jsonp/it.js',
37 | ], (require) => {
38 | require('intl');
39 | require('intl/locale-data/jsonp/en.js');
40 | require('intl/locale-data/jsonp/es.js');
41 | require('intl/locale-data/jsonp/fr.js');
42 | require('intl/locale-data/jsonp/it.js');
43 | render(
44 |
45 |
46 | ,
47 | document.getElementById('root')
48 | );
49 | });
50 | } else {
51 | render(
52 |
53 |
54 | ,
55 | document.getElementById('root')
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/src/styles/base.scss:
--------------------------------------------------------------------------------
1 | :global {
2 | html {
3 | height: 100%;
4 | }
5 | body {
6 | width: 100%;
7 | float: left;
8 | background-color: #666A86;
9 | font-family: 'Roboto', Arial, 'Helvetica Neue', Helvetica, sans-serif;
10 | color: #EEEEFF;
11 | }
12 | a {
13 | text-decoration: none;
14 | cursor: pointer;
15 | }
16 |
17 | #root {
18 | display: flex;
19 | height: 100%;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/utils/translator.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import * as fs from 'fs';
3 | import { sync as globSync } from 'glob';
4 | import { sync as mkdirpSync } from 'mkdirp';
5 |
6 | const filePattern = './build/messages/**/*.json';
7 | const outputLanguageDataDir = './build/locales/';
8 |
9 | // Aggregates the default messages that were extracted from the example app's
10 | // React components via the React Intl Babel plugin. An error will be thrown if
11 | // there are messages in different components that use the same `id`. The result
12 | // is a flat collection of `id: message` pairs for the app's default locale.
13 | const defaultMessages = globSync(filePattern)
14 | .map((filename) => fs.readFileSync(filename, 'utf8'))
15 | .map((file) => JSON.parse(file))
16 | .reduce((collection, descriptors) => {
17 | descriptors.forEach(({ id, defaultMessage }) => {
18 | if (collection.hasOwnProperty(id)) {
19 | throw new Error(`Duplicate message id: ${id}`);
20 | }
21 | collection[id] = defaultMessage;
22 | });
23 |
24 | return collection;
25 | }, {});
26 |
27 | mkdirpSync(outputLanguageDataDir);
28 |
29 | fs.writeFileSync(outputLanguageDataDir + 'data.json', `{ "en": ${JSON.stringify(defaultMessages, null, 2)} }`);
30 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | const entry = [
6 | 'babel-polyfill',
7 | 'webpack-dev-server/client?http://localhost:3000', // Needed for hot reloading
8 | path.join(__dirname, 'src/js/index.js') // Start with js/index.js
9 | ];
10 | // Output
11 | const output = { // Compile into build/ directory
12 | path: path.join(__dirname, 'build'),
13 | filename: 'js/bundle.js', // in js folder as bundle.js
14 | publicPath: '/build/',
15 | };
16 |
17 | // Hot module replacement plugin
18 | const plugins = [
19 | new webpack.HotModuleReplacementPlugin(), // Make hot loading work
20 | new webpack.optimize.OccurenceOrderPlugin(),
21 | new HtmlWebpackPlugin({
22 | template: 'index.html', // Move the index.html file
23 | inject: true
24 | }),
25 | ];
26 |
27 | const loaders = [
28 | { test: /\.js$/, loader: 'babel', exclude: path.join(__dirname, '/node_modules/') },
29 | { test: /\.scss$/, loaders: ["style", "css", "sass"] },
30 | { test: /\.css$/, loader: 'style-loader!css-loader?modules&importLoaders=1&localIdentName=[local]!resolve-url?outputStyle=expanded' },
31 | { test: /\.jpe?g$|\.gif$|\.png$/i, loader: 'url-loader?limit=8000' },
32 | { test: /\.json$/, loader: 'json' },
33 | ];
34 |
35 | const preLoaders = [
36 | {
37 | test: /\.js$/,
38 | loaders: ['eslint-loader'],
39 | exclude: [path.join(__dirname, '/node_modules/'), path.join(__dirname, '/build/')]
40 | }
41 | ];
42 |
43 | const resolve = {
44 | extensions: ['', '.js', '.json'],
45 | root: path.resolve(__dirname, './src'),
46 | };
47 |
48 | module.exports = {
49 | entry: entry,
50 | output: output,
51 | module: {
52 | preLoaders: preLoaders,
53 | loaders: loaders
54 | },
55 | resolve: resolve,
56 | plugins: plugins,
57 | target: 'web', // Make web variables accessible to webpack, e.g. window
58 | stats: false, // Don't show stats in the console
59 | progress: true
60 | };
61 |
--------------------------------------------------------------------------------