├── .babelrc.js ├── .editorconfig ├── .eslintrc.js ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .prettierrc.js ├── .stylelintrc.js ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.txt ├── README.md ├── docs ├── README.md ├── data-fetching.md ├── getting-started.md ├── how-to-configure-text-editors.md ├── react-style-guide.md ├── recipes │ ├── how-to-configure-facebook-login.md │ ├── how-to-implement-routing.md │ ├── how-to-integrate-disqus.md │ ├── how-to-integrate-react-intl.md │ ├── how-to-integrate-redux.md │ ├── how-to-use-sass.md │ └── using-npm-and-webpack-as-a-build-tool.md └── testing-your-application.md ├── jest.config.js ├── package.json ├── public ├── browserconfig.xml ├── crossdomain.xml ├── favicon.ico ├── humans.txt ├── icon.png ├── robots.txt ├── site.webmanifest ├── tile-wide.png └── tile.png ├── src ├── DOMUtils.js ├── actions │ ├── README.md │ └── runtime.js ├── client.js ├── components │ ├── App.js │ ├── Feedback │ │ ├── Feedback.css │ │ ├── Feedback.js │ │ └── package.json │ ├── Footer │ │ ├── Footer.css │ │ ├── Footer.js │ │ └── package.json │ ├── Header │ │ ├── Header.css │ │ ├── Header.js │ │ ├── logo-small.png │ │ ├── logo-small@2x.png │ │ └── package.json │ ├── Html.js │ ├── Layout │ │ ├── Layout.css │ │ ├── Layout.js │ │ ├── Layout.test.js │ │ ├── __snapshots__ │ │ │ └── Layout.test.js.snap │ │ └── package.json │ ├── Link │ │ ├── Link.js │ │ └── package.json │ ├── Navigation │ │ ├── Navigation.css │ │ ├── Navigation.js │ │ └── package.json │ ├── Page │ │ ├── Page.css │ │ ├── Page.js │ │ └── package.json │ ├── antThemeLoader.less │ ├── antThemeVariables.scss │ ├── sassVarsToLess.js │ └── variables.css ├── config.js ├── constants │ └── index.js ├── core │ └── createApolloClient │ │ ├── createApolloClient.client.js │ │ ├── createApolloClient.server.js │ │ ├── createCache.js │ │ └── package.json ├── createFetch.js ├── data │ ├── graphql │ │ ├── Database │ │ │ ├── schema.js │ │ │ └── users │ │ │ │ ├── CreateUser.js │ │ │ │ ├── GetAllUsers.js │ │ │ │ └── GetLoggedInUser.js │ │ └── News │ │ │ ├── reactjsnews.com │ │ │ └── GetAllReactJSNews.js │ │ │ └── schema.js │ ├── models │ │ ├── User.js │ │ ├── UserClaim.js │ │ ├── UserLogin.js │ │ ├── UserProfile.js │ │ └── index.js │ ├── schema.js │ └── sequelize.js ├── history.js ├── passport.js ├── reducers │ ├── index.js │ ├── runtime.js │ └── user.js ├── router.js ├── routes │ ├── about │ │ ├── about.md │ │ └── index.js │ ├── admin │ │ ├── Admin.css │ │ ├── Admin.js │ │ └── index.js │ ├── contact │ │ ├── Contact.css │ │ ├── Contact.js │ │ └── index.js │ ├── error │ │ ├── ErrorPage.css │ │ ├── ErrorPage.js │ │ └── index.js │ ├── home │ │ ├── Home.css │ │ ├── Home.js │ │ ├── index.js │ │ └── news.graphql │ ├── index.js │ ├── login │ │ ├── Login.css │ │ ├── Login.js │ │ └── index.js │ ├── not-found │ │ ├── NotFound.css │ │ ├── NotFound.js │ │ └── index.js │ ├── privacy │ │ ├── index.js │ │ └── privacy.md │ └── register │ │ ├── Register.css │ │ ├── Register.js │ │ └── index.js ├── server.js └── store │ ├── configureStore.js │ ├── createHelpers.js │ └── logger │ ├── logger.client.js │ ├── logger.server.js │ └── package.json ├── test └── README.md ├── tools ├── README.md ├── build.js ├── bundle.js ├── clean.js ├── copy.js ├── deploy.js ├── lib │ ├── cp.js │ ├── fileTransformer.js │ ├── fs.js │ ├── markdown-loader.js │ ├── overrideRules.js │ └── webpackHotDevClient.js ├── postcss.config.js ├── render.js ├── run.js ├── runServer.js ├── start.js └── webpack.config.js └── yarn.lock /.babelrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Starter Kit (https://www.reactstarterkit.com/) 3 | * 4 | * Copyright © 2014-present Kriasoft, LLC. All rights reserved. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.txt file in the root directory of this source tree. 8 | */ 9 | 10 | // Babel configuration 11 | // https://babeljs.io/docs/usage/api/ 12 | module.exports = { 13 | presets: [ 14 | [ 15 | '@babel/preset-env', 16 | { 17 | targets: { 18 | node: 'current', 19 | }, 20 | }, 21 | ], 22 | '@babel/preset-stage-2', 23 | '@babel/preset-flow', 24 | '@babel/preset-react', 25 | ], 26 | ignore: ['node_modules', 'build'], 27 | }; 28 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | # editorconfig-tools is unable to ignore longs strings or urls 20 | max_line_length = null 21 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Starter Kit (https://www.reactstarterkit.com/) 3 | * 4 | * Copyright © 2014-present Kriasoft, LLC. All rights reserved. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.txt file in the root directory of this source tree. 8 | */ 9 | 10 | // ESLint configuration 11 | // http://eslint.org/docs/user-guide/configuring 12 | module.exports = { 13 | parser: 'babel-eslint', 14 | 15 | extends: [ 16 | 'airbnb', 17 | 'plugin:flowtype/recommended', 18 | 'plugin:css-modules/recommended', 19 | 'prettier', 20 | 'prettier/flowtype', 21 | 'prettier/react', 22 | ], 23 | 24 | plugins: ['flowtype', 'css-modules', 'prettier'], 25 | 26 | globals: { 27 | __DEV__: true, 28 | }, 29 | 30 | env: { 31 | browser: true, 32 | }, 33 | 34 | rules: { 35 | // Forbid the use of extraneous packages 36 | // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-extraneous-dependencies.md 37 | 'import/no-extraneous-dependencies': ['error', { packageDir: '.' }], 38 | 39 | // Recommend not to leave any console.log in your code 40 | // Use console.error, console.warn and console.info instead 41 | // https://eslint.org/docs/rules/no-console 42 | 'no-console': [ 43 | 'error', 44 | { 45 | allow: ['warn', 'error', 'info'], 46 | }, 47 | ], 48 | 49 | // Allow only special identifiers 50 | // https://eslint.org/docs/rules/no-underscore-dangle 51 | 'no-underscore-dangle': [ 52 | 'error', 53 | { 54 | allow: ['__typename'], 55 | }, 56 | ], 57 | 58 | // Prefer destructuring from arrays and objects 59 | // http://eslint.org/docs/rules/prefer-destructuring 60 | 'prefer-destructuring': [ 61 | 'error', 62 | { 63 | VariableDeclarator: { 64 | array: false, 65 | object: true, 66 | }, 67 | AssignmentExpression: { 68 | array: false, 69 | object: false, 70 | }, 71 | }, 72 | { 73 | enforceForRenamedProperties: false, 74 | }, 75 | ], 76 | 77 | // Ensure tags are valid 78 | // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-is-valid.md 79 | 'jsx-a11y/anchor-is-valid': [ 80 | 'error', 81 | { 82 | components: ['Link'], 83 | specialLink: ['to'], 84 | aspects: ['noHref', 'invalidHref', 'preferButton'], 85 | }, 86 | ], 87 | 88 | // Allow .js files to use JSX syntax 89 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-filename-extension.md 90 | 'react/jsx-filename-extension': ['error', { extensions: ['.js', '.jsx'] }], 91 | 92 | // Functional and class components are equivalent from React’s point of view 93 | // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-stateless-function.md 94 | 'react/prefer-stateless-function': 'off', 95 | 96 | // ESLint plugin for prettier formatting 97 | // https://github.com/prettier/eslint-plugin-prettier 98 | 'prettier/prettier': 'error', 99 | }, 100 | 101 | settings: { 102 | // Allow absolute paths in imports, e.g. import Button from 'components/Button' 103 | // https://github.com/benmosher/eslint-plugin-import/tree/master/resolvers 104 | 'import/resolver': { 105 | node: { 106 | moduleDirectory: ['node_modules', 'src'], 107 | }, 108 | }, 109 | }, 110 | }; 111 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/build 3 | .*/docs 4 | .*/node_modules 5 | .*/public 6 | 7 | [include] 8 | 9 | [options] 10 | module.system.node.resolve_dirname=node_modules 11 | module.system.node.resolve_dirname=src 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Automatically normalize line endings for all text-based files 2 | # http://git-scm.com/docs/gitattributes#_end_of_line_conversion 3 | * text=auto 4 | 5 | # For the following file types, normalize line endings to LF on 6 | # checkin and prevent conversion to CRLF when they are checked out 7 | # (this is required in order to prevent newline related issues like, 8 | # for example, after the build script is run) 9 | .* text eol=lf 10 | *.html text eol=lf 11 | *.css text eol=lf 12 | *.less text eol=lf 13 | *.styl text eol=lf 14 | *.scss text eol=lf 15 | *.sass text eol=lf 16 | *.sss text eol=lf 17 | *.js text eol=lf 18 | *.jsx text eol=lf 19 | *.json text eol=lf 20 | *.md text eol=lf 21 | *.mjs text eol=lf 22 | *.sh text eol=lf 23 | *.svg text eol=lf 24 | *.txt text eol=lf 25 | *.xml text eol=lf 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | node_modules/ 5 | 6 | # Compiled output 7 | build 8 | 9 | # Runtime data 10 | database.sqlite 11 | 12 | # Test coverage 13 | coverage 14 | 15 | # Logs 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | 20 | # Editors and IDEs 21 | .idea 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # Misc 29 | .DS_Store 30 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Starter Kit (https://www.reactstarterkit.com/) 3 | * 4 | * Copyright © 2014-present Kriasoft, LLC. All rights reserved. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.txt file in the root directory of this source tree. 8 | */ 9 | 10 | // Prettier configuration 11 | // https://prettier.io/docs/en/configuration.html 12 | module.exports = { 13 | printWidth: 80, 14 | singleQuote: true, 15 | trailingComma: 'all', 16 | }; 17 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Starter Kit (https://www.reactstarterkit.com/) 3 | * 4 | * Copyright © 2014-present Kriasoft, LLC. All rights reserved. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.txt file in the root directory of this source tree. 8 | */ 9 | 10 | // stylelint configuration 11 | // https://stylelint.io/user-guide/configuration/ 12 | module.exports = { 13 | // The standard config based on a handful of CSS style guides 14 | // https://github.com/stylelint/stylelint-config-standard 15 | extends: 'stylelint-config-standard', 16 | 17 | plugins: [ 18 | // stylelint plugin to sort CSS rules content with specified order 19 | // https://github.com/hudochenkov/stylelint-order 20 | 'stylelint-order', 21 | ], 22 | 23 | rules: { 24 | 'property-no-unknown': [ 25 | true, 26 | { 27 | ignoreProperties: [ 28 | // CSS Modules composition 29 | // https://github.com/css-modules/css-modules#composition 30 | 'composes', 31 | ], 32 | }, 33 | ], 34 | 35 | 'selector-pseudo-class-no-unknown': [ 36 | true, 37 | { 38 | ignorePseudoClasses: [ 39 | // CSS Modules :global scope 40 | // https://github.com/css-modules/css-modules#exceptions 41 | 'global', 42 | 'local', 43 | ], 44 | }, 45 | ], 46 | 47 | // Opinionated rule, you can disable it if you want 48 | 'string-quotes': 'single', 49 | 50 | // https://github.com/hudochenkov/stylelint-order/blob/master/rules/order/README.md 51 | 'order/order': [ 52 | 'custom-properties', 53 | 'dollar-variables', 54 | 'declarations', 55 | 'at-rules', 56 | 'rules', 57 | ], 58 | 59 | // https://github.com/hudochenkov/stylelint-order/blob/master/rules/properties-order/README.md 60 | 'order/properties-order': [], 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'stable' 4 | - '8' 5 | - '6' 6 | env: 7 | - CXX=g++-4.8 8 | addons: 9 | apt: 10 | sources: 11 | - ubuntu-toolchain-r-test 12 | packages: 13 | - g++-4.8 14 | script: 15 | - yarn lint 16 | - yarn test 17 | - yarn build --release 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## React Starter Kit Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ### [Unreleased][unreleased] 6 | 7 | - Split the `App` component into `App` setting context variables and `Layout` setting general look and feel of the app (BREAKING CHANGE) 8 | - Upgrade `history` npm module to v4.x, update `Link` component (BREAKING CHANGE) 9 | - Remove `core/createHistory.js` in favor of initializing a new history instance inside `server.js` and `client.js` (BREAKING CHANGE) 10 | - Remove Jade dependency in favor of React-based templates: `src/views/index.jade => src/components/Html` 11 | (BREAKING CHANGE) [#711](https://github.com/kriasoft/react-starter-kit/pull/711) 12 | - Update `isomorphic-style-loader` to `v1.0.0`, it adds comparability with ES2015+ decorators. 13 | Code such as `export default withStyles(MyComponent, style1, style2)` must be replaced with 14 | `export default withStyles(style1, style2)(MyComponent)` (BREAKING CHANGE). 15 | - Replace Jest with Mocha, Chai, Sinon. Unit test files must be renamed from 16 | `MyComponent/__test__/MyComponent-test.js` to `MyComponent/MyComponent.test.js` (BREAKING CHANGE). 17 | - Remove `actions`, `stores` folders since there is no Flux library included into the kit 18 | - Rename `server` variable in `server.js` to `app` 19 | - Integrate [Sequelize](http://docs.sequelizejs.com/) to make the project compatible with different types of databases 20 | - Rename `onSetTitle`, `onSetMeta` context variables to `setTitle`, `setMeta` 21 | - Move `Content` component to `src/routes/content` 22 | - Move `ErrorPage` component to `src/routes/error` 23 | - Move the list of top-level routes to `src/routes/index` 24 | - Update routing to use `universal-router` library 25 | - Move Babel, ESLint and JSCS configurations to `package.json` [#497](https://github.com/kriasoft/react-starter-kit/pull/497) 26 | - Convert `Feedback`, `Footer`, `Header`, and `Navigation` to functional stateless components 27 | - Move page / screen components into the `src/routes` folder along with the routing information for them (BREAKING CHANGE). [6553936](https://github.com/kriasoft/react-starter-kit/commit/6553936e693e24a8ac6178f4962af15e0ea87dfd) 28 | 29 | ### [v0.5.1] 30 | > 2016-03-02 31 | 32 | - Remove `Html` React component in favor of compiled Jade templates (`src/views`) (BREAKING CHANGE). [e188388](https://github.com/kriasoft/react-starter-kit/commit/e188388f87069cdc7d501b385d6b0e46c98fed60) 33 | - Add global error handling in Node.js/Express app. [e188388](https://github.com/kriasoft/react-starter-kit/commit/e188388f87069cdc7d501b385d6b0e46c98fed60) 34 | - Add support for Markdown and HTML for static pages. [#469](https://github.com/kriasoft/react-starter-kit/pull/469), [#477](https://github.com/kriasoft/react-starter-kit/pull/477) 35 | 36 | ### [v0.5.0] 37 | > 2016-02-27 38 | 39 | - Replace RESTful API endpoint (`src/api`) with GraphQL (`src/data`) 40 | - Add a sample GraphQL endpoint [localhost:3000/graphql](https://localhost:3000/graphql) 41 | - Change the default Node.js server port from `5000` to `3000` 42 | - Add a JWT-based authentication cookies (see `src/server.js`) 43 | - Add a reference implementation of Facebook authentication strategy (`src/core/passport.js`) 44 | - Add a sample database client utility for PostgreSQL (`src/core/db.js`) 45 | - Optimize the `tools/start.js` script that launches dev server with Browsersync and HMR 46 | - Replace Superagent with WHATWG Fetch library 47 | - Rename `app.js` to `client.js` (aka client-side code) 48 | - Integrate [CSS Modules](https://github.com/css-modules/css-modules) and 49 | [isomorphic-style-loader](https://github.com/kriasoft/isomorphic-style-loader) 50 | - Move `DOMUtils.js` to `src/core` folder; remove `src/utils` folder 51 | - Replace [cssnext](http://cssnext.io/) with [precss](https://github.com/jonathantneal/precss) 52 | - Update build automation scripts to use plain functions 53 | - Add support of `--release` and `--verbose` flags to build scripts 54 | - Add `CHANGELOG.md` file with a list of notable changes to this project 55 | 56 | ### [v0.4.1] 57 | > 2015-10-04 58 | 59 | - Replace React Hot Loader (deprecated) with React Transform 60 | - Replace `index.html` template with `Html` (shell) React component 61 | - Update the deployment script (`tools/deploy.js`), add Git-based deployment example 62 | - Update ESLint and JSCS settings to use AirBnb JavaScript style guide 63 | - Update `docs/how-to-configure-text-editors.md` to cover Atom editor 64 | - Update NPM production and dev dependencies to use the latest versions 65 | 66 | [unreleased]: https://github.com/kriasoft/react-starter-kit/compare/v0.5.1...HEAD 67 | [v0.5.1]: https://github.com/kriasoft/react-starter-kit/compare/v0.5.0...v0.5.1 68 | [v0.5.0]: https://github.com/kriasoft/react-starter-kit/compare/v0.4.1...v0.5.0 69 | [v0.4.1]: https://github.com/kriasoft/react-starter-kit/compare/v0.4.0...v0.4.1 70 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to React Starter Kit 2 | 3 | React Starter Kit is currently the most widely adopted Node.js/React boilerplate used by many 4 | tech startups around the globe. We're working hard to keep it up to date, making sure that it 5 | follows best practices and high coding standards, paying extremely close attention to details. 6 | 7 | Your contributions are welcome and are greatly appreciated! Every little bit helps, and credit 8 | will always be given. 9 | 10 | Please take a moment to review this document in order to make the contribution process easy and 11 | effective for everyone involved. 12 | 13 | ### Conduct 14 | 15 | Please, follow the [golden rule](https://en.wikipedia.org/wiki/Golden_Rule). Be respectful, even to 16 | those that are disrespectful. 17 | 18 | ### Feedback 19 | 20 | Feedback is the breakfast for champions! We'd love to hear your opinions, discuss potential 21 | improvements, architecture, theory, internal implementation, etc. Please, join or start a new 22 | conversation in our [issue tracker](https://github.com/kriasoft/react-starter-kit/issues), 23 | [Gitter](https://gitter.im/kriasoft/react-starter-kit) chat room, or let's talk face-to-face on 24 | [Appear.in](https://appear.in/react) or [Skype](http://hatscripts.com/addskype?koistya). 25 | 26 | ### Documentation 27 | 28 | We need your help with improving documentation to the project. This might be the easiest way for 29 | you to contribute, because you don't even need to clone the repo but can edit or create new `.md` 30 | files right from GitHub website as described [here](https://help.github.com/articles/editing-files-in-your-repository/). 31 | 32 | ### Bugs & Feature Requests 33 | 34 | Before opening an issue, please: 35 | 36 | * Check the [Getting Started](https://github.com/kriasoft/react-starter-kit/blob/master/docs/getting-started.md) guide. 37 | * Search the [issue tracker](https://github.com/kriasoft/react-starter-kit/issues) to make sure 38 | your issue hasn’t already been reported. 39 | * If your issue sounds more like a question, please post it on StackOverflow.com instead with the 40 | tag [react-starter-kit](http://stackoverflow.com/questions/tagged/react-starter-kit). 41 | 42 | ### Pull Requests 43 | 44 | Before you submit a [pull request](https://help.github.com/articles/using-pull-requests/) from your 45 | forked repo, check that it meets these guidelines: 46 | 47 | * If the pull request adds functionality, the docs should be updated as part of the same PR. 48 | * Create a separate PR for each small feature or bug fix. 49 | * [Squash](http://stackoverflow.com/questions/5189560/squash-my-last-x-commits-together-using-git) 50 | your commits into one for each PR. 51 | * Run `yarn test` to make sure that your code style is OK and there are no any regression bugs. 52 | * When contributing to an opt-in feature, apply the `[feature/...]` tag as a prefix to your PR title 53 | 54 | #### Style Guide 55 | 56 | Our linter will catch most styling issues that may exist in your code. You can check the status 57 | of your code styling by simply running: `yarn lint` 58 | 59 | However, there are still some styles that the linter cannot pick up. If you are unsure about 60 | something, looking at [Airbnb's Style Guide](https://github.com/airbnb/javascript) will guide you 61 | in the right direction. 62 | 63 | ### License 64 | 65 | By contributing to React Starter Kit, you agree that your contributions will be licensed under its 66 | [MIT license](https://github.com/kriasoft/react-starter-kit/blob/master/LICENSE.txt). 67 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.10.0-alpine 2 | 3 | # Set a working directory 4 | WORKDIR /usr/src/app 5 | 6 | COPY ./build/package.json . 7 | COPY ./build/yarn.lock . 8 | 9 | # Install Node.js dependencies 10 | RUN yarn install --production --no-progress 11 | 12 | # Copy application files 13 | COPY ./build . 14 | 15 | # Run the container under "node" user by default 16 | USER node 17 | 18 | CMD [ "node", "server.js" ] 19 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014-present Konstantin Tarkus, Kriasoft LLC. 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## Documentation 2 | 3 | ### General 4 | 5 | * [Getting Started](./getting-started.md) 6 | * [React Style Guide](./react-style-guide.md) 7 | * [How to configure text editors and IDEs](./how-to-configure-text-editors.md) 8 | * [Data fetching with WHATWG Fetch](./data-fetching.md) 9 | * [Testing your application](./testing-your-application.md) 10 | 11 | ### Questions 12 | 13 | * [Which module bundler should I use?](https://github.com/kriasoft/react-starter-kit/issues/3) 14 | * [Which Flux implementation should I use?](https://github.com/kriasoft/react-starter-kit/issues/22) 15 | 16 | ### Recipes 17 | 18 | * [How to Implement Routing and Navigation](./recipes/how-to-implement-routing.md) 19 | * [How to Integrate Redux](./recipes/how-to-integrate-redux.md) 20 | * [How to Integrate React Intl](./recipes/how-to-integrate-react-intl.md) 21 | * [How to Integrate Disqus](./recipes/how-to-integrate-disqus.md) 22 | * [How to Use Sass/SCSS](./recipes/how-to-use-sass.md) 23 | * [How to Configure Facebook Login](./recipes/how-to-configure-facebook-login.md) 24 | -------------------------------------------------------------------------------- /docs/data-fetching.md: -------------------------------------------------------------------------------- 1 | ## Data Fetching 2 | 3 | At a bare minimum you may want to use [HTML5 Fetch API][fetch] as an HTTP client 4 | utility for making Ajax request to the [data API server][nodeapi]. This API is 5 | supported natively in all the major browsers except for IE (note, that Edge 6 | browser does support Fetch). 7 | 8 | **React Starter Kit** is pre-configured with [`whatwg-fetch`][wfetch] polyfill 9 | for the browser environment and [`node-fetch`][nfetch] module for the 10 | server-side environment (see [`src/createFetch.js`](../src/createFetch.js)), 11 | allowing you to use the `fetch(url, options)` method universally in both the 12 | client-side and server-side code bases. 13 | 14 | In order to avoid the amount of boilerplate code needed when using the raw 15 | `fetch(..)` function, a simple wrapper was created that provides a base URL of 16 | the data API server, credentials (cookies), CORS etc. For example, in a browser 17 | environment the base URL of the data API server might be an empty string, so 18 | when you make an Ajax request to the `/graphql` endpoint it's being sent to the 19 | same origin, and when the same code is executed on the server, during 20 | server-side rendering, it fetches data from the `http://api:8080/graphql` 21 | endpoint (`node-fetch` doesn't support relative URLs for obvious reasons). 22 | 23 | Because of these subtle differences of how the `fetch` method works internally, 24 | it makes total sense to pass it as a `context` variable to your React 25 | application, so it can be used from either routing level or from inside your 26 | React components as follows: 27 | 28 | #### Route Example 29 | 30 | ```js 31 | { 32 | path: '/posts/:id', 33 | async action({ params, fetch }) { 34 | const resp = await fetch(`/api/posts/${params.id}`, { method: 'GET' }); 35 | const data = await resp.json(); 36 | return { title: data.title, component: }; 37 | } 38 | } 39 | ``` 40 | 41 | #### React Component 42 | 43 | ```js 44 | class Post extends React.Component { 45 | static contextTypes = { fetch: PropTypes.func.isRequired }; 46 | handleDelete = (event) => { 47 | event.preventDefault(); 48 | const id = event.target.dataset['id']; 49 | this.context.fetch(`/api/posts/${id}`, { method: 'DELETE' }).then(...); 50 | }; 51 | render() { ... } 52 | } 53 | ``` 54 | 55 | #### Related articles 56 | 57 | * [That's so fetch!](https://jakearchibald.com/2015/thats-so-fetch/) by 58 | [Jake Archibald](https://twitter.com/jaffathecake) 59 | 60 | [fetch]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch 61 | [wfetch]: https://github.com/github/fetchno 62 | [nfetch]: https://github.com/bitinn/node-fetch 63 | [nodeapi]: https://github.com/kriasoft/nodejs-api-starter 64 | -------------------------------------------------------------------------------- /docs/how-to-configure-text-editors.md: -------------------------------------------------------------------------------- 1 | ## How to Configure Text Editors and IDEs for React.js [![img](https://img.shields.io/badge/discussion-join-green.svg?style=flat-square)](https://github.com/kriasoft/react-starter-kit/issues/117) 2 | 3 | > Tips and tricks on how to configure your favorite text editor or IDE to work 4 | > with React.js/ES6+/JSX. 5 | 6 | ### WebStorm 7 | 8 | Create a new project based on **React Starter Kit template** 9 | 10 | [react-project-template-in-webstorm](https://plugins.jetbrains.com/plugin/7648-react-templates) 11 | 12 | Make sure that **JSX** support is enabled in your project. This is set by 13 | default, if you create a new project based on React.js template. 14 | 15 | [jsx-support-in-webstorm](https://blog.jetbrains.com/webstorm/2015/10/working-with-reactjs-in-webstorm-coding-assistance/) 16 | 17 | Configure JavaScript libraries for **auto-complete** 18 | 19 | [javascript-libraries-in-webstorm](https://blog.jetbrains.com/webstorm/2017/08/how-to-configure-code-completion-in-full-stack-javascript-projects/) 20 | 21 | Enable **ESLint** support 22 | 23 | [eslint-support-in-webstorm](https://www.jetbrains.com/help/webstorm/eslint.html) 24 | 25 | Enable **CSSComb** by following the instructions 26 | [here](https://github.com/csscomb/jetbrains-csscomb). 27 | 28 | **If you have trouble with autoreloading** try to disable "safe write" in 29 | `File > Settings > System Settings > Use "safe write" (save changes to a temporary file first)` 30 | 31 | ### Atom 32 | 33 | Install atom packages 34 | 35 | * [linter](https://atom.io/packages/linter) 36 | * [linter-eslint](https://atom.io/packages/linter-eslint) 37 | * [linter-stylelint](https://atom.io/packages/linter-stylelint) 38 | * [react](https://atom.io/packages/react) 39 | 40 | ```shell 41 | apm install linter linter-eslint react linter-stylelint 42 | ``` 43 | 44 | Install local npm packages 45 | 46 | * [eslint](https://www.npmjs.com/package/eslint) 47 | * [babel-eslint](https://www.npmjs.com/package/babel-eslint) 48 | * [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) 49 | * [stylelint](https://www.npmjs.com/package/stylelint) 50 | 51 | ```shell 52 | yarn add --dev eslint babel-eslint eslint-plugin-react stylelint 53 | ``` 54 | 55 | _You may need to restart atom for changes to take effect_ 56 | 57 | ### SublimeText 58 | 59 | Install SublimeText packages\ 60 | Easiest with [Package Control](https://packagecontrol.io/) and then "Package Control: 61 | Install Package" (Ctrl+Shift+P) 62 | 63 | * [Babel](https://packagecontrol.io/packages/Babel) 64 | * [Sublime-linter](http://www.sublimelinter.com/en/latest/) 65 | * [SublimeLinter-contrib-eslint](https://packagecontrol.io/packages/SublimeLinter-contrib-eslint) 66 | * [SublimeLinter-contrib-stylelint](https://packagecontrol.io/packages/SublimeLinter-contrib-stylelint) 67 | 68 | You can also use 69 | [SublimeLinter-contrib-eslint_d](https://packagecontrol.io/packages/SublimeLinter-contrib-eslint_d) 70 | for faster linting. 71 | 72 | Set Babel as default syntax for a particular extension: 73 | 74 | * Open a file with that extension, 75 | * Select `View` from the menu, 76 | * Then `Syntax` `->` `Open all with current extension as...` `->` `Babel` `->` 77 | `JavaScript (Babel)`. 78 | * Repeat this for each extension (e.g.: .js and .jsx). 79 | 80 | Install local npm packages 81 | 82 | ``` 83 | yarn add --dev eslint@latest 84 | yarn add --dev babel-eslint@latest 85 | yarn add --dev eslint-plugin-react 86 | yarn add --dev stylelint 87 | ``` 88 | -------------------------------------------------------------------------------- /docs/react-style-guide.md: -------------------------------------------------------------------------------- 1 | ## React Style Guide 2 | 3 | > This style guide comes as an addition to 4 | > [Airbnb React/JSX Guide](https://github.com/airbnb/javascript/tree/master/react). 5 | > Feel free to modify it to suit your project's needs. 6 | 7 | ### Table of Contents 8 | 9 | * [Separate folder per UI component](#separate-folder-per-ui-component) 10 | * [Prefer using functional components](#prefer-using-functional-components) 11 | * [Use CSS Modules](#use-css-modules) 12 | * [Use higher-order components](#use-higher-order-components) 13 | 14 | ### Separate folder per UI component 15 | 16 | * Place each major UI component along with its resources in a separate folder\ 17 | This will make it easier to find related resources for any particular UI element 18 | (CSS, images, unit tests, localization files etc.). Removing such components during 19 | refactorings should also be easy. 20 | * Avoid having CSS, images and other resource files shared between multiple 21 | components.\ 22 | This will make your code more maintainable, easy to refactor. 23 | * Add `package.json` file into each component's folder.\ 24 | This will allow to easily reference such components from other places in your code.\ 25 | Use `import Nav from '../Navigation'` instead of `import Nav from '../Navigation/Navigation.js'` 26 | 27 | ``` 28 | /components/Navigation/icon.svg 29 | /components/Navigation/Navigation.css 30 | /components/Navigation/Navigation.js 31 | /components/Navigation/Navigation.test.js 32 | /components/Navigation/Navigation.ru-RU.css 33 | /components/Navigation/package.json 34 | ``` 35 | 36 | ``` 37 | // components/Navigation/package.json 38 | { 39 | "name:": "Navigation", 40 | "main": "./Navigation.js" 41 | } 42 | ``` 43 | 44 | For more information google for 45 | [component-based UI development](https://google.com/search?q=component-based+ui+development). 46 | 47 | ### Prefer using functional components 48 | 49 | * Prefer using stateless functional components whenever possible.\ 50 | Components that don't use state are better to be written as simple pure functions. 51 | 52 | ```jsx 53 | // Bad 54 | class Navigation extends Component { 55 | static propTypes = { items: PropTypes.array.isRequired }; 56 | render() { 57 | return ; 58 | } 59 | } 60 | 61 | // Better 62 | function Navigation({ items }) { 63 | return ( 64 | ; 65 | ); 66 | } 67 | Navigation.propTypes = { items: PropTypes.array.isRequired }; 68 | ``` 69 | 70 | ### Use CSS Modules 71 | 72 | * Use CSS Modules\ 73 | This will allow using short CSS class names and at the same time avoid conflicts. 74 | * Keep CSS simple and declarative. Avoid loops, mixins etc. 75 | * Feel free to use variables in CSS via 76 | [precss](https://github.com/jonathantneal/precss) plugin for 77 | [PostCSS](https://github.com/postcss/postcss) 78 | * Prefer CSS class selectors instead of element and `id` selectors (see 79 | [BEM](https://bem.info/)) 80 | * Avoid nested CSS selectors (see [BEM](https://bem.info/)) 81 | * When in doubt, use `.root { }` class name for the root elements of your 82 | components 83 | 84 | ```scss 85 | // Navigation.scss 86 | @import '../variables.scss'; 87 | 88 | .root { 89 | width: 300px; 90 | } 91 | 92 | .items { 93 | margin: 0; 94 | padding: 0; 95 | list-style-type: none; 96 | text-align: center; 97 | } 98 | 99 | .item { 100 | display: inline-block; 101 | vertical-align: top; 102 | } 103 | 104 | .link { 105 | display: block; 106 | padding: 0 25px; 107 | outline: 0; 108 | border: 0; 109 | color: $default-color; 110 | text-decoration: none; 111 | line-height: 25px; 112 | transition: background-color 0.3s ease; 113 | 114 | &, 115 | .items:hover & { 116 | background: $default-bg-color; 117 | } 118 | 119 | .selected, 120 | .items:hover &:hover { 121 | background: $active-bg-color; 122 | } 123 | } 124 | ``` 125 | 126 | ```jsx 127 | // Navigation.js 128 | import React from 'react'; 129 | import PropTypes from 'prop-types'; 130 | import withStyles from 'isomorphic-style-loader/lib/withStyles'; 131 | import s from './Navigation.scss'; 132 | 133 | function Navigation() { 134 | return ( 135 | 149 | ); 150 | } 151 | 152 | Navigation.propTypes = { className: PropTypes.string }; 153 | 154 | export default withStyles(Navigation, s); 155 | ``` 156 | 157 | ### Use higher-order components 158 | 159 | * Use higher-order components (HOC) to extend existing React components.\ 160 | Here is an example: 161 | 162 | ```js 163 | // withViewport.js 164 | import React, { Component } from 'react'; 165 | import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'; 166 | 167 | function withViewport(ComposedComponent) { 168 | return class WithViewport extends Component { 169 | state = { 170 | viewport: canUseDOM 171 | ? { width: window.innerWidth, height: window.innerHeight } 172 | : { width: 1366, height: 768 }, // Default size for server-side rendering 173 | }; 174 | 175 | componentDidMount() { 176 | window.addEventListener('resize', this.handleResize); 177 | window.addEventListener('orientationchange', this.handleResize); 178 | } 179 | 180 | componentWillUnmount() { 181 | window.removeEventListener('resize', this.handleResize); 182 | window.removeEventListener('orientationchange', this.handleResize); 183 | } 184 | 185 | handleResize = () => { 186 | let viewport = { width: window.innerWidth, height: window.innerHeight }; 187 | if ( 188 | this.state.viewport.width !== viewport.width || 189 | this.state.viewport.height !== viewport.height 190 | ) { 191 | this.setState({ viewport }); 192 | } 193 | }; 194 | 195 | render() { 196 | return ( 197 | 198 | ); 199 | } 200 | }; 201 | } 202 | 203 | export default withViewport; 204 | ``` 205 | 206 | ```js 207 | // MyComponent.js 208 | import React from 'react'; 209 | import withViewport from './withViewport'; 210 | 211 | class MyComponent { 212 | render() { 213 | let { width, height } = this.props.viewport; 214 | return
{`Viewport: ${width}x${height}`}
; 215 | } 216 | } 217 | 218 | export default withViewport(MyComponent); 219 | ``` 220 | 221 | **[⬆ back to top](#table-of-contents)** 222 | -------------------------------------------------------------------------------- /docs/recipes/how-to-configure-facebook-login.md: -------------------------------------------------------------------------------- 1 | How to configure Facebook Login 2 | 3 | 1. Navigate and Login to https://developers.facebook.com/apps 4 | 2. Click "Add New App" 5 | 3. Enter your app name and contact email 6 | 4. In App Dashboard, click `Set Up` in Facebook Login section 7 | 5. Chose "Web" as your Platform 8 | 6. Set Site URL to `http://localhost:3000/` for local testing. (Port of your Node server, not the port of the BrowserSync proxy) 9 | 7. Click Facebook Login on the left panel 10 | 8. Turn on Client OAuth Login and Web OAuth Login. Enter `http://localhost:3000/login/facebook/return` for Valid OAuth redirect URIs. 11 | 9. Get your App ID and Secret and copy it to [`src/config.js`](../src/config.js) 12 | -------------------------------------------------------------------------------- /docs/recipes/how-to-integrate-disqus.md: -------------------------------------------------------------------------------- 1 | ## How to Integrate [Disqus](https://disqus.com) 2 | 3 | https://disqus.com/admin/create/ 4 | 5 | #### `DisqusThread.js` 6 | 7 | ```js 8 | import React from 'react'; 9 | import PropTypes from 'prop-types'; 10 | 11 | const SHORTNAME = 'example'; 12 | const WEBSITE_URL = 'http://www.example.com'; 13 | 14 | function renderDisqus() { 15 | if (window.DISQUS === undefined) { 16 | var script = document.createElement('script'); 17 | script.async = true; 18 | script.src = 'https://' + SHORTNAME + '.disqus.com/embed.js'; 19 | document.getElementsByTagName('head')[0].appendChild(script); 20 | } else { 21 | window.DISQUS.reset({ reload: true }); 22 | } 23 | } 24 | 25 | class DisqusThread extends React.Component { 26 | static propTypes = { 27 | id: PropTypes.string.isRequired, 28 | title: PropTypes.string.isRequired, 29 | path: PropTypes.string.isRequired, 30 | }; 31 | 32 | shouldComponentUpdate(nextProps) { 33 | return ( 34 | this.props.id !== nextProps.id || 35 | this.props.title !== nextProps.title || 36 | this.props.path !== nextProps.path 37 | ); 38 | } 39 | 40 | componentDidMount() { 41 | renderDisqus(); 42 | } 43 | 44 | componentDidUpdate() { 45 | renderDisqus(); 46 | } 47 | 48 | render() { 49 | let { id, title, path, ...other } = this.props; 50 | 51 | if (process.env.BROWSER) { 52 | window.disqus_shortname = SHORTNAME; 53 | window.disqus_identifier = id; 54 | window.disqus_title = title; 55 | window.disqus_url = WEBSITE_URL + path; 56 | } 57 | 58 | return
; 59 | } 60 | } 61 | 62 | export default DisqusThread; 63 | ``` 64 | 65 | #### `MyComponent.js` 66 | 67 | ```js 68 | import React from 'react'; 69 | import DisqusThread from './DisqusThread.js'; 70 | 71 | class MyComponent extends React.Component { 72 | render() { 73 | return ( 74 |
75 | 80 |
81 | ); 82 | } 83 | } 84 | 85 | export default MyComponent; 86 | ``` 87 | -------------------------------------------------------------------------------- /docs/recipes/how-to-integrate-react-intl.md: -------------------------------------------------------------------------------- 1 | ## How to Integrate [React Intl](https://github.com/yahoo/react-intl#react-intl) 2 | 3 | 1. Merge `feature/react-intl` branch with git. 4 | Because react-intl integration is built on top of `feature/redux`, you'll also get all the features. 5 | 6 | 2. Adjust `INTL_REQUIRE_DESCRIPTIONS` constant in `tools/webpack.config.js` around line 17: 7 | 8 | ```js 9 | const INTL_REQUIRE_DESCRIPTIONS = true; 10 | ``` 11 | 12 | When this boolean is set to true, the build will only succeed if a `description` is set for every message descriptor. 13 | 14 | 3. Adjust `locales` settings in `src/config.js`: 15 | 16 | ```js 17 | // default locale is the first one 18 | export const locales = ['en-GB', 'cs-CZ']; 19 | ``` 20 | 21 | Note that you should follow 22 | [BCP 47](https://tools.ietf.org/html/bcp47) 23 | ([RFC 5646](https://tools.ietf.org/html/rfc5646)). 24 | 25 | 4. Add locale support in `src/client.js`: 26 | 27 | ```js 28 | import en from 'react-intl/locale-data/en'; 29 | import cs from 'react-intl/locale-data/cs'; 30 | ... 31 | 32 | [en, cs].forEach(addLocaleData); 33 | ``` 34 | 35 | 5. Execute `yarn run messages` or `yarn start` to strip out messages. 36 | Message files are created in `src/messages` directory. 37 | 38 | 6. Edit `src/messages/*.json` files, change only `message` property. 39 | 40 | 7. Execute `yarn run build`, 41 | your translations should be copied to `build/messages/` directory. 42 | 43 | ### How to write localizable components 44 | 45 | Just import the appropriate [component](https://github.com/yahoo/react-intl/wiki#the-react-intl-module) from `react-intl` 46 | 47 | * For localizable text use 48 | [``](https://github.com/yahoo/react-intl/wiki/Components#formattedmessage). 49 | * You can also use it with 50 | the [`defineMessages()`](https://github.com/yahoo/react-intl/wiki/API#definemessages) helper. 51 | 52 | * For date and time: 53 | [``](https://github.com/yahoo/react-intl/wiki/Components#formatteddate) 54 | [``](https://github.com/yahoo/react-intl/wiki/Components#formattedtime) 55 | [``](https://github.com/yahoo/react-intl/wiki/Components#formattedrelative) 56 | 57 | * For numbers and currencies: 58 | [``](https://github.com/yahoo/react-intl/wiki/Components#formattednumber) 59 | [``](https://github.com/yahoo/react-intl/wiki/Components#formattedplural) 60 | 61 | * If possible, do not use ``, see how to use _Rich Text Formatting_ with 62 | [``](https://github.com/yahoo/react-intl/wiki/Components#formattedmessage) 63 | 64 | * When you need an imperative formatting API, use the [`injectIntl`](https://github.com/yahoo/react-intl/wiki/API#injectintl) High-Order Component. 65 | 66 | #### Example 67 | 68 | ```jsx 69 | import { 70 | defineMessages, 71 | FormattedMessage, 72 | injectIntl, 73 | intlShape, 74 | } from 'react-intl'; 75 | 76 | const messages = defineMessages({ 77 | text: { 78 | id: 'example.text', 79 | defaultMessage: 'Example text', 80 | description: 'Hi Pavel', 81 | }, 82 | textTemplate: { 83 | id: 'example.text.template', 84 | defaultMessage: 'Example text template', 85 | description: 'Hi {name}', 86 | }, 87 | }); 88 | 89 | function Example(props) { 90 | const text = props.intl.formatMessage(messages.textTemplate, { 91 | name: 'Pavel', 92 | }); 93 | return ( 94 |
95 | 100 | 101 | Pavel, 105 | }} 106 | /> 107 |
108 | ); 109 | } 110 | 111 | Example.propTypes = { 112 | intl: intlShape, 113 | }; 114 | 115 | export default injectIntl(Example); 116 | ``` 117 | 118 | ### Updating translations 119 | 120 | When running the development server, every source file is watched and parsed for changed messages. 121 | 122 | Messages files are updated on the fly. 123 | If a new definition is found, this definition is added to the end of every used `src/messages/xx-XX.json` file so when committing, new translations will be at the tail of file. 124 | 125 | When an untranslated message is removed and its `message` field is empty as well, the message will be deleted from all translation files. This is why the `files` array is present. 126 | 127 | When editing a translation file, it should be copied to `build/messages/` directory. 128 | 129 | ### Other References 130 | 131 | * [`Intl documentation on MDN`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Intl) 132 | * [express-request-language](https://github.com/tinganho/express-request-language#readme) 133 | – for more details how initial language negotiation works. 134 | -------------------------------------------------------------------------------- /docs/recipes/how-to-integrate-redux.md: -------------------------------------------------------------------------------- 1 | ## How to Integrate [Redux](http://redux.js.org/index.html) 2 | 3 | Merge `feature/redux` branch with Git. If you are interested in `feature/react-intl`, merge that 4 | branch instead as it also includes Redux. 5 | 6 | **If you don't know Redux well, you should [read about it first](http://redux.js.org/docs/basics/index.html).** 7 | 8 | ### Creating Actions 9 | 10 | 1. Go to `src/constants/index.js` and define action name there. 11 | 12 | 2. Go to `src/actions/` and create file with appropriate name. You can copy 13 | `src/actions/runtime.js` as a template. 14 | 15 | 3. If you need async actions, use [`redux-thunk`](https://github.com/gaearon/redux-thunk#readme). 16 | For inspiration on how to create async actions you can look at 17 | [`setLocale`](https://github.com/kriasoft/react-starter-kit/blob/feature/react-intl/src/actions/intl.js) 18 | action from `feature/react-intl`. 19 | See [Async Flow](http://redux.js.org/docs/advanced/AsyncFlow.html) for more information on this 20 | topic. 21 | 22 | ### Creating Reducer (aka Store) 23 | 24 | 1. Go to [`src/reducers/`](https://github.com/kriasoft/react-starter-kit/tree/feature/redux/src/reducers) and create new file there. 25 | 26 | You can copy [`src/reducers/runtime.js`](https://github.com/kriasoft/react-starter-kit/tree/feature/redux/src/reducers/runtime.js) as a template. 27 | 28 | * Do not forget to always return `state`. 29 | * Never mutate provided `state`. 30 | If you mutate state, rendering of connected component will not be triggered because of `===` equality. 31 | Always return new value if you perform state update. 32 | You can use this construct: `{ ...state, updatedKey: action.payload.value, }` 33 | * Keep in mind that store state _must_ be repeatable by replaying actions on it. 34 | For example, when you store timestamp, pass it into _action payload_. 35 | If you call REST API, do it in action. _Never do this in reducer!_ 36 | 37 | 2. Edit [`src/reducers/index.js`](https://github.com/kriasoft/react-starter-kit/tree/feature/redux/src/reducers/index.js), import your reducer and add it to root reducer created by 38 | [`combineReducers`](http://redux.js.org/docs/api/combineReducers.html) 39 | 40 | ### Connecting Components 41 | 42 | You can use [`connect()`](https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options) High-Order Component from [`react-redux`](https://github.com/reactjs/react-redux#readme) package. 43 | 44 | See [Usage With React](http://redux.js.org/docs/basics/UsageWithReact.html) on redux.js.org. 45 | 46 | For an example you can look at 47 | [``](https://github.com/kriasoft/react-starter-kit/blob/feature/react-intl/src/components/LanguageSwitcher/LanguageSwitcher.js) 48 | component from `feature/react-intl` branch. It demonstrates both subscribing to store and dispatching actions. 49 | 50 | ### Dispatching Actions On Server 51 | 52 | See source of `src/server.js` 53 | -------------------------------------------------------------------------------- /docs/recipes/how-to-use-sass.md: -------------------------------------------------------------------------------- 1 | ## How to Use Sass/SCSS 2 | 3 | > **Note**: Using plain CSS via [PostCSS](http://postcss.org/) is recommended approach because it 4 | > reduces the size of the tech stack used in the project, enforces you to learn vanilla CSS syntax 5 | > with modern CSS Level 3+ features that allow you doing everything you would normally do with 6 | > Sass/SCSS. Also compilation of plain `.css` files should work faster with `postcss` pre-processor 7 | > than `node-sass`. 8 | 9 | ### Step 1 10 | 11 | Install [`node-sass`](https://github.com/sass/node-sass) 12 | (includes [node-gyp](https://github.com/nodejs/node-gyp#readme) 13 | and [prerequisites](https://github.com/nodejs/node-gyp#installation)) and 14 | [`sass-loader`](https://github.com/jtangelder/sass-loader) modules as dev dependencies: 15 | 16 | ```sh 17 | $ yarn add node-sass --dev 18 | $ yarn add sass-loader --dev 19 | ``` 20 | 21 | ### Step 2 22 | 23 | Update [`webpack.config.js`](../../tools/webpack.config.js) file to use `sass-loader` for `.scss` files: 24 | 25 | ```js 26 | const config = { 27 | ... 28 | module: { 29 | rules: [ 30 | ... 31 | { 32 | test: /\.scss$/, 33 | use: [ 34 | { 35 | loader: 'isomorphic-style-loader', 36 | }, 37 | { 38 | loader: 'css-loader', 39 | options: { 40 | sourceMap: isDebug, 41 | minimize: !isDebug, 42 | }, 43 | }, 44 | { 45 | loader: 'postcss-loader', 46 | options: { 47 | config: { 48 | path: './tools/postcss.sass.js', 49 | }, 50 | }, 51 | }, 52 | { 53 | loader: 'sass-loader', 54 | }, 55 | ], 56 | }, 57 | ... 58 | ] 59 | } 60 | ... 61 | } 62 | ``` 63 | 64 | ### Step 3 65 | 66 | Add one more configuration (`tools/postcss.sass.js`) for [PostCSS](https://github.com/postcss/postcss) to 67 | enable [Autoprefixer](https://github.com/postcss/autoprefixer) for your `.scss` files: 68 | 69 | ```js 70 | module.exports = () => ({ 71 | plugins: [require('autoprefixer')()], 72 | }); 73 | ``` 74 | 75 | For more information visit https://github.com/jtangelder/sass-loader and https://github.com/sass/node-sass 76 | -------------------------------------------------------------------------------- /docs/recipes/using-npm-and-webpack-as-a-build-tool.md: -------------------------------------------------------------------------------- 1 | ## Using Yarn and Webpack as a Build Tool 2 | 3 | The [Yarn](https://yarnpkg.com/) command line utility that comes with Node.js 4 | allows you to run arbitrary scripts and [Node.js modules](https://www.npmjs.com/) 5 | without them being globally installed. This is very convenient, because other 6 | developers in your team don't need to worry about having some set of tools 7 | installed globally before they can execute build automation scripts in your 8 | project. 9 | 10 | For example, if you need to lint your JavaScript code with [ESLint](http://eslint.org/) 11 | and [JSCS](http://jscs.info/), you just install them as project's dependencies: 12 | 13 | ```shell 14 | $ yarn add eslint jscs --dev 15 | ``` 16 | 17 | Add a new command line to `package.json/scripts`: 18 | 19 | ```json 20 | { 21 | "devDependencies": { 22 | "eslint": "^1.10.0", 23 | "jscs": "^2.7.0" 24 | }, 25 | "scripts": { 26 | "lint": "eslint src && jscs src" 27 | } 28 | } 29 | ``` 30 | 31 | And execute it by running: 32 | 33 | ```shell 34 | $ yarn run lint # yarn run 35 | ``` 36 | 37 | Which will be the same as running `./node_modules/bin/eslint src && ./node_modules/bin/jscs src`, 38 | except that the former has a shorter syntax and works the the same way on all 39 | platforms (Mac OS X, Windows, Linux). 40 | 41 | The same way you can run [Webpack](http://webpack.github.io/) module bundler 42 | to compile the source code of your app into a distributable format. Since 43 | Webpack has numerous [configuration options](http://webpack.github.io/docs/configuration), 44 | it's a good idea to have all of them in a separate configuration file, as 45 | opposed to feeding them to Webpack's CLI as command line arguments. As a rule 46 | of thumb, you want to keep the "scripts" section in your `package.json` file 47 | short enough and easy to read. 48 | 49 | For example, you may have `src/client.js` and `src/server.js` files that used 50 | as entry points to the client-side and server-side code of your app. The 51 | following Webpack configuration file (`webpack.config.js`) can be used 52 | to bundle them into client-side and server-side application bundles - 53 | `build/public/client.js` and `build/server.js` respectively: 54 | 55 | ```js 56 | module.exports = [{ 57 | context: __dirname + '/src' 58 | entry: './client.js', 59 | output: { 60 | path: __dirname + '/build/public', 61 | filename: 'client.js' 62 | } 63 | }, { 64 | context: __dirname + '/src', 65 | entry: './server.js', 66 | output: { 67 | path: __dirname + '/build', 68 | filename: 'server.js', 69 | libraryTarget: 'commonjs2' 70 | }, 71 | target: 'node', 72 | externals: /node_modules/, 73 | }]; 74 | ``` 75 | 76 | The `npm` script for it may look like this: 77 | 78 | ```json 79 | { 80 | "devDependencies": { 81 | "webpack": "^1.12.0" 82 | }, 83 | "scripts": { 84 | "build": "webpack --config webpack.config.js" 85 | } 86 | } 87 | ``` 88 | 89 | You can run it as follows: 90 | 91 | ```shell 92 | $ yarn run build 93 | ``` 94 | -------------------------------------------------------------------------------- /docs/testing-your-application.md: -------------------------------------------------------------------------------- 1 | ## Testing your application 2 | 3 | ### Used libraries 4 | 5 | RSK comes with the following libraries for testing purposes: 6 | 7 | * [Mocha](https://mochajs.org/) - Node.js and browser test runner 8 | * [Chai](http://chaijs.com/) - Assertion library 9 | * [Enzyme](https://github.com/airbnb/enzyme) - Testing utilities for React 10 | 11 | You may also want to take a look at the following related packages: 12 | 13 | * [jsdom](https://github.com/tmpvar/jsdom) 14 | * [react-addons-test-utils](https://www.npmjs.com/package/react-addons-test-utils) 15 | 16 | ### Running tests 17 | 18 | To test your application simply run the 19 | [`yarn test`](https://github.com/kriasoft/react-starter-kit/blob/b22b1810461cec9c53eedffe632a3ce70a6b29a3/package.json#L154) 20 | command which will: 21 | 22 | * recursively find all files ending with `.test.js` in your `src/` directory 23 | * mocha execute found files 24 | 25 | ```bash 26 | yarn test 27 | ``` 28 | 29 | ### Conventions 30 | 31 | * test filenames MUST end with `test.js` or `yarn test` will not be able to 32 | detect them 33 | * test filenames SHOULD be named after the related component (e.g. create 34 | `Login.test.js` for `Login.js` component) 35 | 36 | ### Basic example 37 | 38 | To help you on your way RSK comes with the following 39 | [basic test case](https://github.com/kriasoft/react-starter-kit/blob/master/src/components/Layout/Layout.test.js) 40 | you can use as a starting point: 41 | 42 | ```js 43 | import React from 'react'; 44 | import { expect } from 'chai'; 45 | import { shallow } from 'enzyme'; 46 | import App from '../App'; 47 | import Layout from './Layout'; 48 | 49 | describe('Layout', () => { 50 | it('renders children correctly', () => { 51 | const wrapper = shallow( 52 | {} }}> 53 | 54 |
55 | 56 | , 57 | ); 58 | 59 | expect(wrapper.contains(
)).to.be.true; 60 | }); 61 | }); 62 | ``` 63 | 64 | ### React-intl exampleß 65 | 66 | React-intl users MUST render/wrap components inside an IntlProvider like the 67 | example below: 68 | 69 | The example below example is a drop-in test for the RSK `Header` component: 70 | 71 | ```js 72 | import React from 'react'; 73 | import Header from './Header'; 74 | import IntlProvider from 'react-intl'; 75 | import Navigation from '../../components/Navigation'; 76 | 77 | describe('A test suite for
', () => { 78 | it('should contain a component', () => { 79 | it('rendering', () => { 80 | const wrapper = renderIntoDocument( 81 | 82 |
83 | , 84 | ); 85 | expect(wrapper.find(Navigation)).to.have.length(1); 86 | }); 87 | }); 88 | }); 89 | ``` 90 | 91 | Please note that NOT using IntlProvider will produce the following error: 92 | 93 | > Invariant Violation: [React Intl] Could not find required `intl` object. 94 | > needs to exist in the component ancestry. 95 | 96 | ### Linting 97 | 98 | In order to check if your JavaScript and CSS code follows the suggested style 99 | guidelines run: 100 | 101 | ```bash 102 | yarn run lint 103 | ``` 104 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Starter Kit (https://www.reactstarterkit.com/) 3 | * 4 | * Copyright © 2014-present Kriasoft, LLC. All rights reserved. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.txt file in the root directory of this source tree. 8 | */ 9 | 10 | // Jest configuration 11 | // https://facebook.github.io/jest/docs/en/configuration.html 12 | module.exports = { 13 | // Modules can be explicitly auto-mocked using jest.mock(moduleName). 14 | // https://facebook.github.io/jest/docs/en/configuration.html#automock-boolean 15 | automock: false, // [boolean] 16 | 17 | // Respect Browserify's "browser" field in package.json when resolving modules. 18 | // https://facebook.github.io/jest/docs/en/configuration.html#browser-boolean 19 | browser: false, // [boolean] 20 | 21 | // This config option can be used here to have Jest stop running tests after the first failure. 22 | // https://facebook.github.io/jest/docs/en/configuration.html#bail-boolean 23 | bail: false, // [boolean] 24 | 25 | // The directory where Jest should store its cached dependency information. 26 | // https://facebook.github.io/jest/docs/en/configuration.html#cachedirectory-string 27 | // cacheDirectory: '/tmp/', // [string] 28 | 29 | // Indicates whether the coverage information should be collected while executing the test. 30 | // Because this retrofits all executed files with coverage collection statements, 31 | // it may significantly slow down your tests. 32 | // https://facebook.github.io/jest/docs/en/configuration.html#collectcoverage-boolean 33 | // collectCoverage: false, // [boolean] 34 | 35 | // https://facebook.github.io/jest/docs/en/configuration.html#collectcoveragefrom-array 36 | collectCoverageFrom: [ 37 | 'src/**/*.{js,jsx}', 38 | '!**/node_modules/**', 39 | '!**/vendor/**', 40 | ], 41 | 42 | // https://facebook.github.io/jest/docs/en/configuration.html#coveragedirectory-string 43 | coverageDirectory: '/coverage', // [string] 44 | 45 | // coveragePathIgnorePatterns: // [array] 46 | // coverageReporters: [], // [array] 47 | // coverageThreshold: {}, // [object] 48 | 49 | globals: { 50 | __DEV__: true, 51 | }, 52 | 53 | // https://facebook.github.io/jest/docs/en/configuration.html#mapcoverage-boolean 54 | // mapCoverage: false, // [boolean] 55 | 56 | // The default extensions Jest will look for. 57 | // https://facebook.github.io/jest/docs/en/configuration.html#modulefileextensions-array-string 58 | moduleFileExtensions: ['js', 'json', 'jsx', 'node'], 59 | 60 | // moduleDirectories: // [array] 61 | 62 | // A map from regular expressions to module names that allow to stub out resources, 63 | // like images or styles with a single module. 64 | moduleNameMapper: { 65 | '\\.(css|less|styl|scss|sass|sss)$': 'identity-obj-proxy', 66 | }, 67 | 68 | // modulePathIgnorePatterns: // [array] 69 | // modulePaths: // [array] 70 | // notify: false, // [boolean] 71 | // preset: // [string] 72 | // projects: // [array] 73 | // clearMocks: // [boolean] 74 | // reporters: // [array] 75 | // resetMocks: // [boolean] 76 | // resetModules: // [boolean] 77 | // resolver: // [string] 78 | // rootDir: // [string] 79 | // roots: // [array] 80 | // setupFiles: // [array] 81 | // setupTestFrameworkScriptFile: // [string] 82 | // snapshotSerializers: // [array] 83 | // testEnvironment: // [string] 84 | // testMatch: // [array] 85 | // testPathIgnorePatterns: // [array] 86 | // testRegex: // [string] 87 | // testResultsProcessor: // [string] 88 | // testRunner: // [string] 89 | // testURL: // [string] 90 | // timers: // [string] 91 | 92 | transform: { 93 | '\\.(js|jsx)$': '/node_modules/babel-jest', 94 | '\\.(gql|graphql)$': '/node_modules/jest-transform-graphql', 95 | '^(?!.*\\.(js|jsx|json|css|less|styl|scss|sass|sss)$)': 96 | '/tools/lib/fileTransformer.js', 97 | }, 98 | 99 | // transformIgnorePatterns: // [array] 100 | // unmockedModulePathPatterns: // [array] 101 | 102 | verbose: true, // [boolean] 103 | }; 104 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.0.0", 4 | "private": true, 5 | "engines": { 6 | "node": ">=6.13.1", 7 | "npm": ">=3.10.10" 8 | }, 9 | "browserslist": [ 10 | ">1%", 11 | "last 4 versions", 12 | "Firefox ESR", 13 | "not ie < 9" 14 | ], 15 | "dependencies": { 16 | "@babel/polyfill": "^7.0.0-beta.42", 17 | "antd": "^3.3.3", 18 | "apollo-cache-inmemory": "^1.1.4", 19 | "apollo-client": "^2.0.4", 20 | "apollo-link": "^1.0.7", 21 | "apollo-link-error": "^1.0.3", 22 | "apollo-link-http": "^1.3.2", 23 | "apollo-link-logger": "^1.1.0", 24 | "apollo-link-schema": "^1.0.1", 25 | "babel-plugin-import": "^1.6.7", 26 | "bluebird": "^3.5.1", 27 | "body-parser": "^1.18.2", 28 | "classnames": "^2.2.5", 29 | "cookie-parser": "^1.4.3", 30 | "core-js": "^2.5.3", 31 | "express": "^4.16.3", 32 | "express-graphql": "^0.6.12", 33 | "express-jwt": "^5.3.1", 34 | "graphql": "^0.13.2", 35 | "graphql-tag": "^2.6.1", 36 | "graphql-tools": "^2.16.0", 37 | "history": "^4.7.2", 38 | "immutable": "^3.8.2", 39 | "isomorphic-style-loader": "^4.0.0", 40 | "jsonwebtoken": "^8.2.0", 41 | "lodash": "^4.17.5", 42 | "node-fetch": "^2.1.1", 43 | "normalize.css": "^8.0.0", 44 | "passport": "^0.4.0", 45 | "passport-facebook": "^2.1.1", 46 | "pretty-error": "^2.1.1", 47 | "prop-types": "^15.6.1", 48 | "query-string": "^6.0.0", 49 | "react": "^16.2.0", 50 | "react-apollo": "^2.0.4", 51 | "react-dom": "^16.2.0", 52 | "react-redux": "^5.0.6", 53 | "redux": "^3.7.2", 54 | "redux-devtools-extension": "^2.13.2", 55 | "redux-logger": "^3.0.6", 56 | "redux-thunk": "^2.2.0", 57 | "sequelize": "^4.37.3", 58 | "serialize-javascript": "^1.4.0", 59 | "source-map-support": "^0.5.4", 60 | "sqlite3": "^4.0.0", 61 | "universal-router": "^6.0.0", 62 | "whatwg-fetch": "^2.0.3" 63 | }, 64 | "devDependencies": { 65 | "@babel/core": "^7.0.0-beta.42", 66 | "@babel/node": "^7.0.0-beta.42", 67 | "@babel/plugin-transform-react-constant-elements": "^7.0.0-beta.42", 68 | "@babel/plugin-transform-react-inline-elements": "^7.0.0-beta.42", 69 | "@babel/preset-env": "^7.0.0-beta.42", 70 | "@babel/preset-flow": "^7.0.0-beta.42", 71 | "@babel/preset-react": "^7.0.0-beta.42", 72 | "@babel/preset-stage-2": "^7.0.0-beta.42", 73 | "autoprefixer": "^8.1.0", 74 | "babel-core": "^7.0.0-0", 75 | "babel-eslint": "^8.2.2", 76 | "babel-jest": "^22.4.3", 77 | "babel-loader": "^8.0.0-beta.2", 78 | "babel-plugin-transform-react-remove-prop-types": "^0.4.13", 79 | "browser-sync": "^2.23.6", 80 | "chokidar": "^2.0.2", 81 | "css-loader": "^0.28.11", 82 | "enzyme": "^3.3.0", 83 | "eslint": "^4.19.0", 84 | "eslint-config-airbnb": "^16.1.0", 85 | "eslint-config-prettier": "^2.9.0", 86 | "eslint-import-resolver-node": "^0.3.2", 87 | "eslint-loader": "^2.0.0", 88 | "eslint-plugin-css-modules": "^2.7.5", 89 | "eslint-plugin-flowtype": "^2.46.1", 90 | "eslint-plugin-import": "^2.9.0", 91 | "eslint-plugin-jsx-a11y": "^6.0.3", 92 | "eslint-plugin-prettier": "^2.6.0", 93 | "eslint-plugin-react": "^7.7.0", 94 | "file-loader": "^1.1.11", 95 | "flow-bin": "^0.68.0", 96 | "front-matter": "^2.3.0", 97 | "glob": "^7.1.2", 98 | "husky": "^0.14.3", 99 | "identity-obj-proxy": "^3.0.0", 100 | "jest": "^22.4.3", 101 | "jest-codemods": "^0.13.9", 102 | "jest-transform-graphql": "^2.1.0", 103 | "jscodeshift": "^0.5.0", 104 | "less": "^3.0.1", 105 | "less-loader": "^4.1.0", 106 | "less-vars-to-js": "^1.2.1", 107 | "lint-staged": "^7.0.0", 108 | "markdown-it": "^8.4.1", 109 | "mini-css-extract-plugin": "^0.2.0", 110 | "mkdirp": "^0.5.1", 111 | "null-loader": "^0.1.1", 112 | "opn-cli": "^3.1.0", 113 | "pixrem": "^4.0.1", 114 | "pleeease-filters": "^4.0.0", 115 | "postcss": "^6.0.20", 116 | "postcss-calc": "^6.0.1", 117 | "postcss-color-function": "^4.0.1", 118 | "postcss-custom-media": "^6.0.0", 119 | "postcss-custom-properties": "^7.0.0", 120 | "postcss-custom-selectors": "^4.0.1", 121 | "postcss-flexbugs-fixes": "^3.3.0", 122 | "postcss-import": "^11.1.0", 123 | "postcss-loader": "^2.1.3", 124 | "postcss-media-minmax": "^3.0.0", 125 | "postcss-nested": "^3.0.0", 126 | "postcss-nesting": "^4.2.1", 127 | "postcss-pseudoelements": "^5.0.0", 128 | "postcss-selector-matches": "^3.0.1", 129 | "postcss-selector-not": "^3.0.1", 130 | "prettier": "^1.11.1", 131 | "raw-loader": "^0.5.1", 132 | "react-deep-force-update": "^2.1.1", 133 | "react-dev-utils": "^5.0.0", 134 | "react-error-overlay": "^4.0.0", 135 | "react-test-renderer": "^16.2.0", 136 | "redux-mock-store": "^1.4.0", 137 | "rimraf": "^2.6.2", 138 | "style-loader": "^0.20.3", 139 | "stylelint": "^9.1.3", 140 | "stylelint-config-standard": "^18.2.0", 141 | "stylelint-order": "^0.8.1", 142 | "svg-url-loader": "^2.3.2", 143 | "url-loader": "^1.0.1", 144 | "webpack": "^4.2.0", 145 | "webpack-assets-manifest": "^2.0.0", 146 | "webpack-bundle-analyzer": "^2.11.1", 147 | "webpack-dev-middleware": "^3.0.1", 148 | "webpack-hot-middleware": "^2.21.2", 149 | "webpack-node-externals": "^1.6.0" 150 | }, 151 | "lint-staged": { 152 | "*.{js,jsx}": [ 153 | "eslint --no-ignore --fix", 154 | "git add --force" 155 | ], 156 | "*.{json,md,graphql}": [ 157 | "prettier --write", 158 | "git add --force" 159 | ], 160 | "*.{css,less,styl,scss,sass,sss}": [ 161 | "stylelint --fix", 162 | "git add --force" 163 | ] 164 | }, 165 | "scripts": { 166 | "precommit": "lint-staged", 167 | "lint-js": "eslint --ignore-path .gitignore --ignore-pattern \"!**/.*\" .", 168 | "lint-css": "stylelint \"src/**/*.{css,less,styl,scss,sass,sss}\"", 169 | "lint": "yarn run lint-js && yarn run lint-css", 170 | "fix-js": "yarn run lint-js --fix", 171 | "fix-css": "yarn run lint-css --fix", 172 | "fix": "yarn run fix-js && yarn run fix-css", 173 | "flow": "flow", 174 | "flow:check": "flow check", 175 | "test": "jest", 176 | "test-watch": "yarn run test --watch --notify", 177 | "test-cover": "yarn run test --coverage", 178 | "coverage": "yarn run test-cover && opn coverage/lcov-report/index.html", 179 | "clean": "babel-node tools/run clean", 180 | "copy": "babel-node tools/run copy", 181 | "bundle": "babel-node tools/run bundle", 182 | "build": "babel-node tools/run build", 183 | "build-stats": "yarn run build --release --analyse", 184 | "deploy": "babel-node tools/run deploy", 185 | "render": "babel-node tools/run render", 186 | "serve": "babel-node tools/run runServer", 187 | "start": "babel-node tools/run start" 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tim-soft/react-starter-kit-antd/cb3b9d1b71d208d1673eb7b1b9299e269ad0a1c1/public/favicon.ico -------------------------------------------------------------------------------- /public/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | -- -- 7 | 8 | # THANKS 9 | 10 | 11 | 12 | # TECHNOLOGY COLOPHON 13 | 14 | CSS3, HTML5, JavaScript 15 | React Starter Kit -- https://reactstarter.com/ 16 | -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tim-soft/react-starter-kit-antd/cb3b9d1b71d208d1673eb7b1b9299e269ad0a1c1/public/icon.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | 3 | # Allow crawling of all content 4 | User-agent: * 5 | Disallow: 6 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "icons": [ 3 | { 4 | "src": "icon.png", 5 | "sizes": "192x192", 6 | "type": "image/png" 7 | } 8 | ], 9 | "start_url": "/" 10 | } 11 | -------------------------------------------------------------------------------- /public/tile-wide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tim-soft/react-starter-kit-antd/cb3b9d1b71d208d1673eb7b1b9299e269ad0a1c1/public/tile-wide.png -------------------------------------------------------------------------------- /public/tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tim-soft/react-starter-kit-antd/cb3b9d1b71d208d1673eb7b1b9299e269ad0a1c1/public/tile.png -------------------------------------------------------------------------------- /src/DOMUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Starter Kit (https://www.reactstarterkit.com/) 3 | * 4 | * Copyright © 2014-present Kriasoft, LLC. All rights reserved. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.txt file in the root directory of this source tree. 8 | */ 9 | 10 | export function updateTag(tagName, keyName, keyValue, attrName, attrValue) { 11 | const node = document.head.querySelector( 12 | `${tagName}[${keyName}="${keyValue}"]`, 13 | ); 14 | if (node && node.getAttribute(attrName) === attrValue) return; 15 | 16 | // Remove and create a new tag in order to make it work with bookmarks in Safari 17 | if (node) { 18 | node.parentNode.removeChild(node); 19 | } 20 | if (typeof attrValue === 'string') { 21 | const nextNode = document.createElement(tagName); 22 | nextNode.setAttribute(keyName, keyValue); 23 | nextNode.setAttribute(attrName, attrValue); 24 | document.head.appendChild(nextNode); 25 | } 26 | } 27 | 28 | export function updateMeta(name, content) { 29 | updateTag('meta', 'name', name, 'content', content); 30 | } 31 | 32 | export function updateCustomMeta(property, content) { 33 | updateTag('meta', 'property', property, 'content', content); 34 | } 35 | 36 | export function updateLink(rel, href) { 37 | updateTag('link', 'rel', rel, 'href', href); 38 | } 39 | -------------------------------------------------------------------------------- /src/actions/README.md: -------------------------------------------------------------------------------- 1 | # Redux Actions and Action Creators 2 | 3 | ### Action Types 4 | 5 | Should go to `src/constants/…` 6 | 7 | ```js 8 | export const ADD_TODO = 'ADD_TODO'; 9 | ``` 10 | 11 | ### Action Creators 12 | 13 | Should go to `src/actions/…` 14 | 15 | ```js 16 | export function addTodo({ text }) { 17 | return { 18 | type: ADD_TODO, 19 | payload: { 20 | text, 21 | // ... 22 | }, 23 | }; 24 | } 25 | ``` 26 | 27 | ## Flux Standard Action 28 | 29 | An action _MUST_ 30 | 31 | * be a plain JavaScript object. 32 | * have a `type` property. (_string_) 33 | 34 | An action _MAY_ 35 | 36 | * have an `error` property. (`true` → `payload` _should be instance of `Error`_) 37 | If `error` has any other value besides `true`, the action _MUST NOT_ be interpreted as an error. 38 | * have a `payload` property. (_any, object is reccomended_) 39 | * have a `meta` property. (_any_) 40 | It is intended for any extra information that is not part of the payload 41 | 42 | An action _MUST NOT_ include properties other than `type`, `payload`, `error`, and `meta`. 43 | 44 | [**Read more about FSA**](https://github.com/redux-utilities/flux-standard-action#flux-standard-action) 45 | 46 | ### Examples 47 | 48 | ```js 49 | // Action example: 50 | { 51 | type: ADD_TODO, 52 | payload: { 53 | text: 'Contribute to React Starter Kit.', 54 | }, 55 | } 56 | // Error action example: 57 | { 58 | type: 'ADD_TODO', 59 | error: true, 60 | payload: new Error('Database Error'), 61 | } 62 | ``` 63 | 64 | # Other Resources 65 | 66 | * [Actions on redux.js.org](https://redux.js.org/basics/actions) 67 | * [Flux Standard Action](https://github.com/redux-utilities/flux-standard-action#flux-standard-action) 68 | -------------------------------------------------------------------------------- /src/actions/runtime.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | 3 | import { SET_RUNTIME_VARIABLE } from '../constants'; 4 | 5 | export function setRuntimeVariable({ name, value }) { 6 | return { 7 | type: SET_RUNTIME_VARIABLE, 8 | payload: { 9 | name, 10 | value, 11 | }, 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Starter Kit (https://www.reactstarterkit.com/) 3 | * 4 | * Copyright © 2014-present Kriasoft, LLC. All rights reserved. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.txt file in the root directory of this source tree. 8 | */ 9 | 10 | import 'whatwg-fetch'; 11 | import React from 'react'; 12 | import ReactDOM from 'react-dom'; 13 | import deepForceUpdate from 'react-deep-force-update'; 14 | import queryString from 'query-string'; 15 | import { createPath } from 'history/PathUtils'; 16 | import App from './components/App'; 17 | import createFetch from './createFetch'; 18 | import configureStore from './store/configureStore'; 19 | import { updateMeta } from './DOMUtils'; 20 | import history from './history'; 21 | import createApolloClient from './core/createApolloClient'; 22 | import router from './router'; 23 | 24 | // Universal HTTP client 25 | const fetch = createFetch(window.fetch, { 26 | baseUrl: window.App.apiUrl, 27 | }); 28 | 29 | const apolloClient = createApolloClient(); 30 | 31 | // Global (context) variables that can be easily accessed from any React component 32 | // https://facebook.github.io/react/docs/context.html 33 | const context = { 34 | // Enables critical path CSS rendering 35 | // https://github.com/kriasoft/isomorphic-style-loader 36 | insertCss: (...styles) => { 37 | // eslint-disable-next-line no-underscore-dangle 38 | const removeCss = styles.map(x => x._insertCss()); 39 | return () => { 40 | removeCss.forEach(f => f()); 41 | }; 42 | }, 43 | // For react-apollo 44 | client: apolloClient, 45 | // Initialize a new Redux store 46 | // http://redux.js.org/docs/basics/UsageWithReact.html 47 | store: configureStore(window.App.state, { fetch, history }), 48 | fetch, 49 | storeSubscription: null, 50 | }; 51 | 52 | const container = document.getElementById('app'); 53 | let currentLocation = history.location; 54 | let appInstance; 55 | 56 | const scrollPositionsHistory = {}; 57 | 58 | // Re-render the app when window.location changes 59 | async function onLocationChange(location, action) { 60 | // Remember the latest scroll position for the previous location 61 | scrollPositionsHistory[currentLocation.key] = { 62 | scrollX: window.pageXOffset, 63 | scrollY: window.pageYOffset, 64 | }; 65 | // Delete stored scroll position for next page if any 66 | if (action === 'PUSH') { 67 | delete scrollPositionsHistory[location.key]; 68 | } 69 | currentLocation = location; 70 | 71 | const isInitialRender = !action; 72 | try { 73 | context.pathname = location.pathname; 74 | context.query = queryString.parse(location.search); 75 | 76 | // Traverses the list of routes in the order they are defined until 77 | // it finds the first route that matches provided URL path string 78 | // and whose action method returns anything other than `undefined`. 79 | const route = await router.resolve(context); 80 | 81 | // Prevent multiple page renders during the routing process 82 | if (currentLocation.key !== location.key) { 83 | return; 84 | } 85 | 86 | if (route.redirect) { 87 | history.replace(route.redirect); 88 | return; 89 | } 90 | 91 | const renderReactApp = isInitialRender ? ReactDOM.hydrate : ReactDOM.render; 92 | appInstance = renderReactApp( 93 | {route.component}, 94 | container, 95 | () => { 96 | if (isInitialRender) { 97 | // Switch off the native scroll restoration behavior and handle it manually 98 | // https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration 99 | if (window.history && 'scrollRestoration' in window.history) { 100 | window.history.scrollRestoration = 'manual'; 101 | } 102 | 103 | const elem = document.getElementById('css'); 104 | if (elem) elem.parentNode.removeChild(elem); 105 | return; 106 | } 107 | 108 | document.title = route.title; 109 | 110 | updateMeta('description', route.description); 111 | // Update necessary tags in at runtime here, ie: 112 | // updateMeta('keywords', route.keywords); 113 | // updateCustomMeta('og:url', route.canonicalUrl); 114 | // updateCustomMeta('og:image', route.imageUrl); 115 | // updateLink('canonical', route.canonicalUrl); 116 | // etc. 117 | 118 | let scrollX = 0; 119 | let scrollY = 0; 120 | const pos = scrollPositionsHistory[location.key]; 121 | if (pos) { 122 | scrollX = pos.scrollX; 123 | scrollY = pos.scrollY; 124 | } else { 125 | const targetHash = location.hash.substr(1); 126 | if (targetHash) { 127 | const target = document.getElementById(targetHash); 128 | if (target) { 129 | scrollY = window.pageYOffset + target.getBoundingClientRect().top; 130 | } 131 | } 132 | } 133 | 134 | // Restore the scroll position if it was saved into the state 135 | // or scroll to the given #hash anchor 136 | // or scroll to top of the page 137 | window.scrollTo(scrollX, scrollY); 138 | 139 | // Google Analytics tracking. Don't send 'pageview' event after 140 | // the initial rendering, as it was already sent 141 | if (window.ga) { 142 | window.ga('send', 'pageview', createPath(location)); 143 | } 144 | }, 145 | ); 146 | } catch (error) { 147 | if (__DEV__) { 148 | throw error; 149 | } 150 | 151 | console.error(error); 152 | 153 | // Do a full page reload if error occurs during client-side navigation 154 | if (!isInitialRender && currentLocation.key === location.key) { 155 | console.error('RSK will reload your page after error'); 156 | window.location.reload(); 157 | } 158 | } 159 | } 160 | 161 | // Handle client-side navigation by using HTML5 History API 162 | // For more information visit https://github.com/mjackson/history#readme 163 | history.listen(onLocationChange); 164 | onLocationChange(currentLocation); 165 | 166 | // Enable Hot Module Replacement (HMR) 167 | if (module.hot) { 168 | module.hot.accept('./router', () => { 169 | if (appInstance && appInstance.updater.isMounted(appInstance)) { 170 | // Force-update the whole tree, including components that refuse to update 171 | deepForceUpdate(appInstance); 172 | } 173 | 174 | onLocationChange(currentLocation); 175 | }); 176 | } 177 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Starter Kit (https://www.reactstarterkit.com/) 3 | * 4 | * Copyright © 2014-present Kriasoft, LLC. All rights reserved. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.txt file in the root directory of this source tree. 8 | */ 9 | 10 | import React from 'react'; 11 | import PropTypes from 'prop-types'; 12 | import { Provider as ReduxProvider } from 'react-redux'; 13 | import { ApolloProvider } from 'react-apollo'; 14 | 15 | const ContextType = { 16 | // Enables critical path CSS rendering 17 | // https://github.com/kriasoft/isomorphic-style-loader 18 | insertCss: PropTypes.func.isRequired, 19 | // Universal HTTP client 20 | fetch: PropTypes.func.isRequired, 21 | pathname: PropTypes.string.isRequired, 22 | query: PropTypes.object, 23 | // Integrate Redux 24 | // http://redux.js.org/docs/basics/UsageWithReact.html 25 | ...ReduxProvider.childContextTypes, 26 | // Apollo Client 27 | client: PropTypes.object.isRequired, 28 | }; 29 | 30 | /** 31 | * The top-level React component setting context (global) variables 32 | * that can be accessed from all the child components. 33 | * 34 | * https://facebook.github.io/react/docs/context.html 35 | * 36 | * Usage example: 37 | * 38 | * const context = { 39 | * history: createBrowserHistory(), 40 | * store: createStore(), 41 | * }; 42 | * 43 | * ReactDOM.render( 44 | * 45 | * 46 | * 47 | * 48 | * , 49 | * container, 50 | * ); 51 | */ 52 | class App extends React.PureComponent { 53 | static propTypes = { 54 | context: PropTypes.shape(ContextType).isRequired, 55 | children: PropTypes.element.isRequired, 56 | }; 57 | 58 | static childContextTypes = ContextType; 59 | 60 | getChildContext() { 61 | return this.props.context; 62 | } 63 | 64 | render() { 65 | // Here, we are at universe level, sure? ;-) 66 | const { client } = this.props.context; 67 | // NOTE: If you need to add or modify header, footer etc. of the app, 68 | // please do that inside the Layout component. 69 | return ( 70 | {this.props.children} 71 | ); 72 | } 73 | } 74 | 75 | export default App; 76 | -------------------------------------------------------------------------------- /src/components/Feedback/Feedback.css: -------------------------------------------------------------------------------- 1 | /** 2 | * React Starter Kit (https://www.reactstarterkit.com/) 3 | * 4 | * Copyright © 2014-present Kriasoft, LLC. All rights reserved. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.txt file in the root directory of this source tree. 8 | */ 9 | 10 | @import '../variables.css'; 11 | 12 | .root { 13 | background: #f5f5f5; 14 | color: #333; 15 | } 16 | 17 | .container { 18 | margin: 0 auto; 19 | padding: 20px 8px; 20 | max-width: var(--max-content-width); 21 | text-align: center; 22 | font-size: 1.5em; /* ~24px */ 23 | } 24 | 25 | .link, 26 | .link:active, 27 | .link:hover, 28 | .link:visited { 29 | color: #333; 30 | text-decoration: none; 31 | } 32 | 33 | .link:hover { 34 | text-decoration: underline; 35 | } 36 | 37 | .spacer { 38 | padding-right: 15px; 39 | padding-left: 15px; 40 | } 41 | -------------------------------------------------------------------------------- /src/components/Feedback/Feedback.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Starter Kit (https://www.reactstarterkit.com/) 3 | * 4 | * Copyright © 2014-present Kriasoft, LLC. All rights reserved. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.txt file in the root directory of this source tree. 8 | */ 9 | 10 | import React from 'react'; 11 | import withStyles from 'isomorphic-style-loader/lib/withStyles'; 12 | import s from './Feedback.css'; 13 | 14 | class Feedback extends React.Component { 15 | render() { 16 | return ( 17 | 34 | ); 35 | } 36 | } 37 | 38 | export default withStyles(s)(Feedback); 39 | -------------------------------------------------------------------------------- /src/components/Feedback/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Feedback", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./Feedback.js" 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.css: -------------------------------------------------------------------------------- 1 | /** 2 | * React Starter Kit (https://www.reactstarterkit.com/) 3 | * 4 | * Copyright © 2014-present Kriasoft, LLC. All rights reserved. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.txt file in the root directory of this source tree. 8 | */ 9 | 10 | @import '../variables.css'; 11 | 12 | .root { 13 | background: #333; 14 | color: #fff; 15 | } 16 | 17 | .container { 18 | margin: 0 auto; 19 | padding: 20px 15px; 20 | max-width: var(--max-content-width); 21 | text-align: center; 22 | } 23 | 24 | .text { 25 | color: rgba(255, 255, 255, 0.5); 26 | } 27 | 28 | .spacer { 29 | color: rgba(255, 255, 255, 0.3); 30 | } 31 | 32 | .text, 33 | .link { 34 | padding: 2px 5px; 35 | font-size: 1em; 36 | } 37 | 38 | .link, 39 | .link:active, 40 | .link:visited { 41 | color: rgba(255, 255, 255, 0.6); 42 | text-decoration: none; 43 | } 44 | 45 | .link:hover { 46 | color: rgba(255, 255, 255, 1); 47 | } 48 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Starter Kit (https://www.reactstarterkit.com/) 3 | * 4 | * Copyright © 2014-present Kriasoft, LLC. All rights reserved. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.txt file in the root directory of this source tree. 8 | */ 9 | 10 | import React from 'react'; 11 | import withStyles from 'isomorphic-style-loader/lib/withStyles'; 12 | import s from './Footer.css'; 13 | import Link from '../Link'; 14 | 15 | class Footer extends React.Component { 16 | render() { 17 | return ( 18 |
19 |
20 | © Your Company 21 | · 22 | 23 | Home 24 | 25 | · 26 | 27 | Admin 28 | 29 | · 30 | 31 | Privacy 32 | 33 | · 34 | 35 | Not Found 36 | 37 |
38 |
39 | ); 40 | } 41 | } 42 | 43 | export default withStyles(s)(Footer); 44 | -------------------------------------------------------------------------------- /src/components/Footer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Footer", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./Footer.js" 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Header/Header.css: -------------------------------------------------------------------------------- 1 | /** 2 | * React Starter Kit (https://www.reactstarterkit.com/) 3 | * 4 | * Copyright © 2014-present Kriasoft, LLC. All rights reserved. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.txt file in the root directory of this source tree. 8 | */ 9 | 10 | @import '../variables.css'; 11 | 12 | :root { 13 | --brand-color: #61dafb; 14 | } 15 | 16 | .root { 17 | background: #373277; 18 | color: #fff; 19 | } 20 | 21 | .container { 22 | margin: 0 auto; 23 | padding: 20px 0; 24 | max-width: var(--max-content-width); 25 | } 26 | 27 | .brand { 28 | color: color(var(--brand-color) lightness(+10%)); 29 | text-decoration: none; 30 | font-size: 1.75em; /* ~28px */ 31 | } 32 | 33 | .brandTxt { 34 | margin-left: 10px; 35 | } 36 | 37 | .banner { 38 | text-align: center; 39 | } 40 | 41 | .bannerTitle { 42 | margin: 0; 43 | padding: 10px; 44 | font-weight: normal; 45 | font-size: 4em; 46 | line-height: 1em; 47 | } 48 | 49 | .bannerDesc { 50 | padding: 0; 51 | color: rgba(255, 255, 255, 0.5); 52 | font-size: 1.25em; 53 | margin: 0; 54 | } 55 | -------------------------------------------------------------------------------- /src/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Starter Kit (https://www.reactstarterkit.com/) 3 | * 4 | * Copyright © 2014-present Kriasoft, LLC. All rights reserved. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.txt file in the root directory of this source tree. 8 | */ 9 | 10 | import React from 'react'; 11 | import withStyles from 'isomorphic-style-loader/lib/withStyles'; 12 | import s from './Header.css'; 13 | import Link from '../Link'; 14 | import Navigation from '../Navigation'; 15 | import logoUrl from './logo-small.png'; 16 | import logoUrl2x from './logo-small@2x.png'; 17 | 18 | class Header extends React.Component { 19 | render() { 20 | return ( 21 |
22 |
23 | 24 | 25 | React 32 | Your Company 33 | 34 |
35 |

React

36 |

Complex web apps made easy

37 |
38 |
39 |
40 | ); 41 | } 42 | } 43 | 44 | export default withStyles(s)(Header); 45 | -------------------------------------------------------------------------------- /src/components/Header/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tim-soft/react-starter-kit-antd/cb3b9d1b71d208d1673eb7b1b9299e269ad0a1c1/src/components/Header/logo-small.png -------------------------------------------------------------------------------- /src/components/Header/logo-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tim-soft/react-starter-kit-antd/cb3b9d1b71d208d1673eb7b1b9299e269ad0a1c1/src/components/Header/logo-small@2x.png -------------------------------------------------------------------------------- /src/components/Header/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Header", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./Header.js" 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Html.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Starter Kit (https://www.reactstarterkit.com/) 3 | * 4 | * Copyright © 2014-present Kriasoft, LLC. All rights reserved. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.txt file in the root directory of this source tree. 8 | */ 9 | 10 | import React from 'react'; 11 | import PropTypes from 'prop-types'; 12 | import serialize from 'serialize-javascript'; 13 | import config from '../config'; 14 | 15 | /* eslint-disable react/no-danger */ 16 | 17 | class Html extends React.Component { 18 | static propTypes = { 19 | title: PropTypes.string.isRequired, 20 | description: PropTypes.string.isRequired, 21 | styles: PropTypes.arrayOf( 22 | PropTypes.shape({ 23 | id: PropTypes.string.isRequired, 24 | cssText: PropTypes.string.isRequired, 25 | }).isRequired, 26 | ), 27 | scripts: PropTypes.arrayOf(PropTypes.string.isRequired), 28 | app: PropTypes.object, // eslint-disable-line 29 | children: PropTypes.string.isRequired, 30 | }; 31 | 32 | static defaultProps = { 33 | styles: [], 34 | scripts: [], 35 | }; 36 | 37 | render() { 38 | const { title, description, styles, scripts, app, children } = this.props; 39 | return ( 40 | 41 | 42 | 43 | 44 | {title} 45 | 46 | 47 | {scripts.map(script => ( 48 | 49 | ))} 50 | 51 | 52 | {styles.map(style => ( 53 |