├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .flowconfig ├── .gitignore ├── .tern-port ├── .tern-project ├── .watchmanconfig ├── README.md ├── __mocks__ └── fileMock.js ├── api ├── api.js ├── config.js ├── helpers │ ├── logger.js │ └── url.js └── ws.js ├── app ├── ClientTemplate.js ├── Html.js ├── ServerTemplate.js ├── __tests__ │ └── example.js ├── client.js ├── config │ ├── index.js │ └── routes.js ├── constants.js ├── core │ ├── DevTools │ │ └── index.js │ ├── atoms │ │ ├── CloseIcon │ │ │ ├── close.svg │ │ │ ├── index.css │ │ │ └── index.js │ │ ├── GithubButton │ │ │ └── index.js │ │ ├── Link │ │ │ ├── index.css │ │ │ └── index.js │ │ ├── NavLink │ │ │ ├── index.css │ │ │ └── index.js │ │ ├── NotificationIcon │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.js.snap │ │ │ ├── icon.svg │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ └── index.test.js │ │ ├── Paragraph │ │ │ └── index.js │ │ └── Title │ │ │ └── index.js │ ├── index.js │ ├── molecules │ │ ├── Modal │ │ │ ├── index.css │ │ │ └── index.js │ │ └── PageHeader │ │ │ └── index.js │ ├── organisms │ │ ├── AppearRoute │ │ │ └── index.js │ │ ├── LoginModal │ │ │ └── index.js │ │ └── PrivateRoute │ │ │ └── index.js │ └── utils │ │ ├── formatters │ │ └── index.js │ │ ├── guid.js │ │ ├── index.js │ │ ├── normalizers │ │ └── index.js │ │ ├── selectors │ │ ├── getFieldClassname.js │ │ └── index.js │ │ └── validation │ │ └── index.js ├── css │ ├── global.css │ ├── helpers.css │ ├── icons.css │ ├── icons │ │ └── close.svg │ └── variables.css ├── data │ ├── apiClient.js │ ├── app.js │ ├── index.js │ └── user.js ├── redux │ ├── __tests__ │ │ └── app.test.js │ ├── actions │ │ ├── app.js │ │ ├── creators.js │ │ ├── index.js │ │ ├── ui.js │ │ └── user.js │ ├── createStore.js │ ├── reducers │ │ ├── app.js │ │ ├── index.js │ │ ├── ui.js │ │ └── user.js │ └── sagas │ │ ├── app.js │ │ ├── index.js │ │ ├── user.js │ │ └── utils.js ├── screens │ ├── App │ │ ├── components │ │ │ ├── Header │ │ │ │ ├── index.css │ │ │ │ └── index.js │ │ │ ├── Navigation │ │ │ │ ├── index.css │ │ │ │ └── index.js │ │ │ ├── NotificationBar │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.js.snap │ │ │ │ ├── index.css │ │ │ │ ├── index.js │ │ │ │ └── index.test.js │ │ │ └── index.js │ │ ├── index.css │ │ ├── index.js │ │ └── screens │ │ │ ├── About │ │ │ ├── index.css │ │ │ └── index.js │ │ │ ├── Login │ │ │ ├── index.css │ │ │ └── index.js │ │ │ ├── NotFound │ │ │ ├── index.css │ │ │ └── index.js │ │ │ ├── NotificationCenter │ │ │ ├── index.css │ │ │ └── index.js │ │ │ ├── Projects │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ ├── screens │ │ │ │ ├── SideProjects │ │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ └── shared │ │ │ │ ├── Project │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ ├── SecretSpace │ │ │ └── index.js │ │ │ └── index.js │ └── index.js └── server.js ├── bin ├── api.js ├── server.js └── webpack.js ├── flow-typed └── npm │ ├── axios_v0.15.x.js │ ├── babel-cli_vx.x.x.js │ ├── babel-core_vx.x.x.js │ ├── babel-eslint_vx.x.x.js │ ├── babel-jest_vx.x.x.js │ ├── babel-loader_vx.x.x.js │ ├── babel-plugin-add-module-exports_vx.x.x.js │ ├── babel-plugin-module-resolver_vx.x.x.js │ ├── babel-plugin-react-css-modules_vx.x.x.js │ ├── babel-plugin-syntax-flow_vx.x.x.js │ ├── babel-plugin-system-import-transformer_vx.x.x.js │ ├── babel-plugin-transform-decorators-legacy_vx.x.x.js │ ├── babel-plugin-transform-flow-strip-types_vx.x.x.js │ ├── babel-plugin-transform-react-display-name_vx.x.x.js │ ├── babel-plugin-transform-runtime_vx.x.x.js │ ├── babel-preset-es2015_vx.x.x.js │ ├── babel-preset-react_vx.x.x.js │ ├── babel-preset-stage-0_vx.x.x.js │ ├── babel-register_vx.x.x.js │ ├── babel-runtime_vx.x.x.js │ ├── better-npm-run_vx.x.x.js │ ├── body-parser_vx.x.x.js │ ├── bunyan-request_vx.x.x.js │ ├── bunyan_v1.x.x.js │ ├── clean-webpack-plugin_vx.x.x.js │ ├── compression_vx.x.x.js │ ├── concurrently_vx.x.x.js │ ├── cookie-parser_vx.x.x.js │ ├── css-loader_vx.x.x.js │ ├── debounce_vx.x.x.js │ ├── enzyme_v2.3.x.js │ ├── eslint-config-airbnb_vx.x.x.js │ ├── eslint-config-prettier_vx.x.x.js │ ├── eslint-import-resolver-babel-module_vx.x.x.js │ ├── eslint-loader_vx.x.x.js │ ├── eslint-plugin-flowtype_vx.x.x.js │ ├── eslint-plugin-import_vx.x.x.js │ ├── eslint-plugin-jsx-a11y_vx.x.x.js │ ├── eslint-plugin-prettier_vx.x.x.js │ ├── eslint-plugin-react_vx.x.x.js │ ├── eslint_vx.x.x.js │ ├── express-session_vx.x.x.js │ ├── express_v4.x.x.js │ ├── extract-text-webpack-plugin_vx.x.x.js │ ├── file-loader_vx.x.x.js │ ├── flow-bin_v0.x.x.js │ ├── fontfaceobserver_vx.x.x.js │ ├── hard-source-webpack-plugin_vx.x.x.js │ ├── history_vx.x.x.js │ ├── hoist-non-react-statics_vx.x.x.js │ ├── html-loader_vx.x.x.js │ ├── html-webpack-plugin_vx.x.x.js │ ├── http-proxy_vx.x.x.js │ ├── humps_vx.x.x.js │ ├── identity-obj-proxy_vx.x.x.js │ ├── image-webpack-loader_vx.x.x.js │ ├── jest_v18.x.x.js │ ├── json-loader_vx.x.x.js │ ├── lorem-ipsum_vx.x.x.js │ ├── map-props_vx.x.x.js │ ├── multer_vx.x.x.js │ ├── node-object-hash_vx.x.x.js │ ├── node-sass_vx.x.x.js │ ├── nodemon_vx.x.x.js │ ├── normalize.css_vx.x.x.js │ ├── npm_vx.x.x.js │ ├── object-hash_vx.x.x.js │ ├── postcss-assets_vx.x.x.js │ ├── postcss-browser-reporter_vx.x.x.js │ ├── postcss-cssnext_vx.x.x.js │ ├── postcss-import_vx.x.x.js │ ├── postcss-inline-svg_vx.x.x.js │ ├── postcss-loader_vx.x.x.js │ ├── postcss-modules_vx.x.x.js │ ├── postcss-nested_vx.x.x.js │ ├── postcss-reporter_vx.x.x.js │ ├── postcss-url_vx.x.x.js │ ├── postcss_vx.x.x.js │ ├── prettier_vx.x.x.js │ ├── pretty-error_v2.x.x.js │ ├── react-a11y_vx.x.x.js │ ├── react-addons-shallow-compare_vx.x.x.js │ ├── react-addons-test-utils_v15.x.x.js │ ├── react-autosuggest_vx.x.x.js │ ├── react-css-modules_vx.x.x.js │ ├── react-helmet_vx.x.x.js │ ├── react-hot-loader_vx.x.x.js │ ├── react-immutable-proptypes_vx.x.x.js │ ├── react-inline-css_vx.x.x.js │ ├── react-modal_v1.x.x.js │ ├── react-redux_v5.x.x.js │ ├── react-router-dom_vx.x.x.js │ ├── react-router-redux_vx.x.x.js │ ├── react-router-server_vx.x.x.js │ ├── react-router_vx.x.x.js │ ├── react-test-renderer_vx.x.x.js │ ├── react-transform-catch-errors_vx.x.x.js │ ├── react-waypoint_vx.x.x.js │ ├── reactotron-react-js_vx.x.x.js │ ├── reactotron-redux_vx.x.x.js │ ├── redbox-react_vx.x.x.js │ ├── redux-devtools-dock-monitor_vx.x.x.js │ ├── redux-devtools-log-monitor_vx.x.x.js │ ├── redux-devtools_vx.x.x.js │ ├── redux-form_vx.x.x.js │ ├── redux-immutable_vx.x.x.js │ ├── redux-saga_vx.x.x.js │ ├── redux_v3.x.x.js │ ├── resource-hints-webpack-plugin_vx.x.x.js │ ├── sass-loader_vx.x.x.js │ ├── serialize-javascript_vx.x.x.js │ ├── serve-favicon_vx.x.x.js │ ├── sinon_vx.x.x.js │ ├── socket.io-client_vx.x.x.js │ ├── socket.io_vx.x.x.js │ ├── stats-webpack-plugin_vx.x.x.js │ ├── strip-loader_vx.x.x.js │ ├── style-loader_vx.x.x.js │ ├── svg-url-loader_vx.x.x.js │ ├── timekeeper_vx.x.x.js │ ├── url-loader_vx.x.x.js │ ├── validator_vx.x.x.js │ ├── warning_vx.x.x.js │ ├── webpack-dashboard_vx.x.x.js │ ├── webpack-dev-middleware_vx.x.x.js │ ├── webpack-dev-server_vx.x.x.js │ ├── webpack-hot-middleware_vx.x.x.js │ ├── webpack-isomorphic-tools_vx.x.x.js │ ├── webpack_vx.x.x.js │ └── write-file-webpack-plugin_vx.x.x.js ├── flow ├── declarations │ ├── index.js │ └── redux-form.js └── stub │ ├── css-modules.js │ └── url-loader.js ├── nodemon.js ├── package.json ├── server.babel.js ├── static ├── favicon.ico └── template.html ├── webpack ├── dev.config.js ├── dll.config.js ├── postcss.config.js ├── prod.config.js ├── webpack-dev-server.js └── webpack-isomorphic-tools.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react", "stage-0"], 3 | "plugins": [ 4 | ["module-resolver", { 5 | "root": ["./app"] 6 | }], 7 | "syntax-flow", 8 | "transform-flow-strip-types", 9 | "add-module-exports", 10 | "system-import-transformer", 11 | "transform-decorators-legacy", 12 | "transform-react-display-name" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | end_of_line = lf 4 | indent_size = 2 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | 8 | [*.md] 9 | max_line_length = 0 10 | trim_trailing_whitespace = false 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | webpack/* 2 | karma.conf.js 3 | tests.webpack.js 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint-config-airbnb", 4 | "plugin:flowtype/recommended", 5 | "prettier", 6 | "prettier/react" 7 | ], 8 | "parser": "babel-eslint", 9 | "parserOptions":{ 10 | "ecmaFeatures": { 11 | "experimentalObjectRestSpread": true 12 | } 13 | }, 14 | "env": { 15 | "browser": true, 16 | "node": true, 17 | "jest": true 18 | }, 19 | "rules": { 20 | "jsx-a11y/img-has-alt": 0, 21 | "jsx-a11y/href-no-hash": "off", 22 | "jsx-a11y/anchor-is-valid": ["warn", { "aspects": ["invalidHref"] }], 23 | "prettier/prettier": ["error", {"singleQuote": true}], 24 | "generator-star-spacing": 0, 25 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], 26 | "react/no-multi-comp": [1, { "ignoreStateless": true }], 27 | "react/no-children-prop": 0, 28 | "import/default": 0, 29 | "import/named": 0, 30 | "import/namespace": 0, 31 | "import/extensions": 0, 32 | "import/no-extraneous-dependencies": 0, 33 | "import/imports-first": 0, 34 | "import/no-unresolved": 0, 35 | "import/no-named-as-default": 2, 36 | "react/no-unused-prop-types": 0, 37 | "react/jsx-boolean-value": [2, "always"], 38 | "flowtype/generic-spacing": 0, 39 | "arrow-parens": 0, 40 | "comma-dangle": 0, // not sure why airbnb turned this on. gross! 41 | "no-children-prop": 0, 42 | "no-confusing-arrow": 0, 43 | "jsx-a11y/no-static-element-interactions": 0, 44 | "arrow-body-style": 0, 45 | "prefer-template": 0, 46 | "no-alert": 0, 47 | "space-infix-ops": 0, 48 | "no-duplicate-imports": 0, 49 | "import/no-duplicates": 1, 50 | "flowtype/define-flow-type": 1, 51 | "flowtype/space-before-type-colon": [ 52 | 1, 53 | "never" 54 | ], 55 | "flowtype/use-flow-type": 1, 56 | "flowtype/valid-syntax": 1, 57 | "flowtype/type-id-match": [ 58 | 2, 59 | "^([A-Z]+[a-z0-9A-Z]*)$" 60 | ] 61 | }, 62 | "plugins": [ 63 | "react", 64 | "prettier", 65 | "import", 66 | "flowtype", 67 | ], 68 | "settings": { 69 | "import/parser": "babel-eslint", 70 | "import/resolve": { 71 | "babel-module": {} 72 | }, 73 | "flowtype": { 74 | "onlyFilesWithFlowAnnotation": false 75 | } 76 | }, 77 | "globals": { 78 | "__DEVELOPMENT__": true, 79 | "__CLIENT__": true, 80 | "__SERVER__": true, 81 | "__DISABLE_SSR__": true, 82 | "__DEVTOOLS__": true, 83 | "socket": true, 84 | "webpackIsomorphicTools": true 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*node_modules/fbjs.* 3 | .*node_modules/systemjs-builder/test/fixtures/sourcemaps-expectations/.* 4 | .*/node_modules/.*/test/.* 5 | .*/node_modules/flow-runtime/.* 6 | .*/node_modules/react-timeago/lib/.*.flow 7 | .*/node_modules/babel-plugin-flow-runtime/.* 8 | 9 | [libs] 10 | flow/declarations/ 11 | flow-typed/npm/ 12 | 13 | [options] 14 | module.system.node.resolve_dirname=node_modules 15 | module.system.node.resolve_dirname=src 16 | sharedmemory.hash_table_pow=19 17 | module.name_mapper='^core\(.*\)$' -> '/app/core/\1' 18 | module.name_mapper='^config\(.*\)$' -> '/app/config/\1' 19 | module.name_mapper='^screens\(.*\)$' -> '/app/screens/\1' 20 | module.name_mapper='^data\(.*\)$' -> '/app/data/\1' 21 | module.name_mapper='^css\(.*\)$' -> '/app/css/\1' 22 | module.name_mapper='^redux\(.*\)$' -> '/app/redux/\1' 23 | module.name_mapper='^constants.js$' -> '/app/constants.js' 24 | module.name_mapper='.*\(.css\)' -> 'CSSModule' 25 | module.name_mapper='.*\.\(svg\|png\|jpg\|gif\)$' -> '/flow/stub/url-loader.js' 26 | esproposal.class_static_fields=enable 27 | esproposal.class_instance_fields=enable 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | *.iml 4 | *.log 5 | .happypack 6 | webpack-assets.json 7 | webpack-stats.json 8 | npm-debug.log 9 | wordpress/ 10 | tmp/ 11 | static/uploads/ 12 | static/dist/ 13 | webpack/hard-source-cache/ 14 | tags 15 | .ctags 16 | .tern-port/ 17 | /nbproject/private/ 18 | -------------------------------------------------------------------------------- /.tern-port: -------------------------------------------------------------------------------- 1 | 40903 -------------------------------------------------------------------------------- /.tern-project: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaVersion": 7, 3 | "libs": [ 4 | "browser" 5 | ], 6 | "loadEagerly": [ 7 | "app/**/*.js" 8 | ], 9 | "dontLoad": [ 10 | "app/assets/**/*.js" 11 | ], 12 | "plugins": { 13 | "complete_strings": { 14 | "maxLength": 15 15 | }, 16 | "node": {}, 17 | "webpack": { 18 | "configPath": "webpack/dev.config.js" 19 | }, 20 | "doc_comment": { 21 | "fullDocs": true, 22 | "strong": true 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codejunkienick/starter-lapis/52aa3a1cd1e02e57f0938b98444a5d7e04e60465/.watchmanconfig -------------------------------------------------------------------------------- /__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /api/api.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import http from 'http'; 3 | import express from 'express'; 4 | import session from 'express-session'; 5 | import bodyParser from 'body-parser'; 6 | import cookieParser from 'cookie-parser'; 7 | import SocketIo from 'socket.io'; 8 | import config from './config'; 9 | import handleUserSocket from './ws'; 10 | import { logger, middleware as requestMiddleware } from './helpers/logger'; 11 | 12 | const app = express(); 13 | const server = new http.Server(app); 14 | const io = new SocketIo(server); 15 | io.path('/ws'); 16 | 17 | app.use(cookieParser(config.secret)); 18 | app.use( 19 | session({ 20 | secret: config.secret, 21 | key: 'usersid', 22 | cookie: { maxAge: 1200000 }, 23 | resave: false, 24 | saveUninitialized: false 25 | }) 26 | ); 27 | 28 | 29 | // app.use(httpLogger('dev')); 30 | 31 | app.use(requestMiddleware); 32 | app.use(bodyParser.json()); 33 | app.use(bodyParser.urlencoded({ extended: true })); 34 | 35 | app.use('/static', express.static(config.projectDir + '/public')); 36 | 37 | app.get('/app/load', (req, res) => { 38 | res.json({ data: { testMsg: 'This came from server' } }); 39 | }); 40 | 41 | app.post('/user/login', (req, res) => { 42 | setTimeout( 43 | function() { 44 | res.json({ data: true }); 45 | }, 46 | 2000 47 | ); 48 | }); 49 | 50 | // Log errors 51 | app.use((err, req, res, next) => { 52 | if (err) { 53 | logger.error(err); 54 | next(err); 55 | } 56 | }); 57 | 58 | if (config.apiPort) { 59 | const runnable = app.listen(config.apiPort, err => { 60 | if (err) { 61 | logger.error(err); 62 | } 63 | console.log( 64 | '----\n==> SIMPLE DEBUG API is running on port %s', 65 | config.apiPort 66 | ); 67 | console.log( 68 | '==> Send requests to http://%s:%s', 69 | config.apiHost, 70 | config.apiPort 71 | ); 72 | }); 73 | 74 | io.listen(runnable); 75 | 76 | io.on('connection', socket => { 77 | console.log('user connected'); 78 | handleUserSocket(socket); 79 | }); 80 | } else { 81 | console.error('==> ERROR: No PORT environment variable has been specified'); 82 | } 83 | -------------------------------------------------------------------------------- /api/config.js: -------------------------------------------------------------------------------- 1 | require('babel-polyfill'); 2 | 3 | const environment = { 4 | development: { 5 | isProduction: false 6 | }, 7 | production: { 8 | isProduction: true 9 | } 10 | }[process.env.NODE_ENV || 'development']; 11 | 12 | module.exports = Object.assign({ 13 | apiHost: process.env.APIHOST || '127.0.0.1', 14 | apiPort: process.env.APIPORT || 3010, 15 | projectDir: __dirname + '/../', 16 | secret: 'asdasdafasasf', 17 | server: { 18 | databaseURL: 'mongodb://localhost/starter-lapis' 19 | }, 20 | vk: { 21 | clientId: '', 22 | secret: '' 23 | }, 24 | facebook: { 25 | secret: '', 26 | key: '' 27 | }, 28 | instagram: { 29 | key: '', 30 | secret: '' 31 | }, 32 | twitter: { 33 | key: '', 34 | secret: '' 35 | } 36 | 37 | }, environment); 38 | -------------------------------------------------------------------------------- /api/helpers/logger.js: -------------------------------------------------------------------------------- 1 | import bunyan from 'bunyan'; 2 | import bunyanRequest from 'bunyan-request'; 3 | 4 | export const logger = bunyan.createLogger({ 5 | name: 'starter-lapis', 6 | streams: [ 7 | { 8 | level: 'debug', 9 | path: 'debug.log', 10 | }, 11 | { 12 | path: 'trace.log', 13 | level: 'trace' 14 | } 15 | ], 16 | serializers: { 17 | req: bunyan.stdSerializers.req, 18 | res: bunyan.stdSerializers.res, 19 | }, 20 | }); 21 | 22 | export const middleware = bunyanRequest({ 23 | logger: logger, 24 | headerName: 'x-request-id' 25 | }); 26 | -------------------------------------------------------------------------------- /api/helpers/url.js: -------------------------------------------------------------------------------- 1 | export function mapUrl(availableActions = {}, url = []) { 2 | const notFound = {action: null, params: []}; 3 | 4 | // test for empty input 5 | if (url.length === 0 || Object.keys(availableActions).length === 0) { 6 | return notFound; 7 | } 8 | /*eslint-disable */ 9 | const reducer = (prev, current) => { 10 | if (prev.action && prev.action[current]) { 11 | return {action: prev.action[current], params: []}; // go deeper 12 | } else { 13 | if (typeof prev.action === 'function') { 14 | return {action: prev.action, params: prev.params.concat(current)}; // params are found 15 | } else { 16 | return notFound; 17 | } 18 | } 19 | }; 20 | /*eslint-enable */ 21 | 22 | const actionAndParams = url.reduce(reducer, {action: availableActions, params: []}); 23 | 24 | return (typeof actionAndParams.action === 'function') ? actionAndParams : notFound; 25 | } 26 | -------------------------------------------------------------------------------- /api/ws.js: -------------------------------------------------------------------------------- 1 | import loremIpsum from 'lorem-ipsum'; 2 | import { logger } from './helpers/logger'; 3 | 4 | function generateMsgEvery20Sec(socket) { 5 | return function loop() { 6 | const msg = loremIpsum({ count: 4, units: 'words' }); 7 | socket.emit('notifications.sendOne', { msg }); 8 | setTimeout(loop, 1000 * 20); 9 | }; 10 | } 11 | 12 | export default function handleUserSocket(socket) { 13 | try { 14 | socket.on('notifications.sendOne', data => { 15 | if (data.msg) { 16 | socket.emit('notifications.sendOne', { msg: data.msg }); 17 | } 18 | }); 19 | 20 | generateMsgEvery20Sec(socket)(); 21 | } catch (err) { 22 | logger.error(err); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/ClientTemplate.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import 'babel-polyfill'; 3 | import React from 'react'; 4 | import { Provider } from 'react-redux'; 5 | import { BrowserRouter } from 'react-router-dom'; 6 | import { App } from 'screens'; 7 | 8 | const ClientTemplate = ({ store }: { store: any }) => 9 | 10 | 11 | 12 | 13 | ; 14 | 15 | export default ClientTemplate; 16 | -------------------------------------------------------------------------------- /app/Html.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/server'; 3 | import serialize from 'serialize-javascript'; 4 | import Helmet from 'react-helmet'; 5 | /** 6 | * Wrapper component containing HTML metadata and boilerplate tags. 7 | * Used in server-side code only to wrap the string output of the 8 | * rendered route component. 9 | * 10 | * The only thing this component doesn't (and can't) include is the 11 | * HTML doctype declaration, which is added to the rendered output 12 | * by the server.js file. 13 | */ 14 | 15 | type Props = { 16 | assets: object, 17 | store: object, 18 | component: React$Element 19 | }; 20 | 21 | const Html = ({ assets, component, store }: Props) => { 22 | const content = component ? ReactDOM.renderToString(component) : ''; 23 | const head = Helmet.rewind(); 24 | return ( 25 | 26 | 27 | {head.base.toComponent()} 28 | {head.title.toComponent()} 29 | {head.meta.toComponent()} 30 | {head.link.toComponent()} 31 | {head.script.toComponent()} 32 | 33 | 34 | 35 | {/* styles (will be present only in production with webpack extract text plugin) */} 36 | {Object.keys(assets.styles).map((style, key) => 37 | 45 | )} 46 | {/* (will be present only in development mode) */} 47 | {/* outputs a