├── internals ├── templates │ ├── translations │ │ └── en.json │ ├── containers │ │ ├── LanguageProvider │ │ │ ├── constants.js │ │ │ ├── actions.js │ │ │ ├── selectors.js │ │ │ ├── reducer.js │ │ │ └── index.js │ │ ├── HomePage │ │ │ ├── messages.js │ │ │ ├── tests │ │ │ │ └── index.test.js │ │ │ └── index.js │ │ ├── NotFoundPage │ │ │ ├── messages.js │ │ │ ├── tests │ │ │ │ └── index.test.js │ │ │ └── index.js │ │ └── App │ │ │ ├── tests │ │ │ ├── index.test.js │ │ │ └── selectors.test.js │ │ │ ├── constants.js │ │ │ ├── selectors.js │ │ │ └── index.js │ ├── global-styles.js │ ├── tests │ │ └── store.test.js │ ├── i18n.js │ ├── index.html │ ├── reducers.js │ ├── routes.js │ ├── store.js │ └── utils │ │ └── asyncInjectors.js ├── generators │ ├── language │ │ ├── translations-json.hbs │ │ ├── app-locale.hbs │ │ ├── add-locale-data.hbs │ │ ├── polyfill-intl-locale.hbs │ │ ├── intl-locale-data.hbs │ │ ├── translation-messages.hbs │ │ └── format-translation-messages.hbs │ ├── container │ │ ├── constants.js.hbs │ │ ├── actions.js.hbs │ │ ├── sagas.js.hbs │ │ ├── test.js.hbs │ │ ├── reducer.test.js.hbs │ │ ├── selectors.test.js.hbs │ │ ├── messages.js.hbs │ │ ├── sagas.test.js.hbs │ │ ├── actions.test.js.hbs │ │ ├── reducer.js.hbs │ │ ├── selectors.js.hbs │ │ └── index.js.hbs │ ├── route │ │ ├── route.hbs │ │ ├── routeWithReducer.hbs │ │ └── index.js │ ├── component │ │ ├── test.js.hbs │ │ ├── messages.js.hbs │ │ ├── stateless.js.hbs │ │ ├── es6.js.hbs │ │ ├── es6.pure.js.hbs │ │ └── index.js │ ├── utils │ │ └── componentExists.js │ └── index.js ├── mocks │ ├── cssModule.js │ └── image.js ├── testing │ └── test-bundler.js ├── scripts │ ├── helpers │ │ ├── xmark.js │ │ ├── checkmark.js │ │ └── progress.js │ ├── npmcheckversion.js │ ├── analyze.js │ ├── dependencies.js │ └── clean.js ├── webpack │ └── webpack.dll.babel.js └── config.js ├── .lgtm ├── app ├── favicon.ico ├── components │ ├── Header │ │ ├── banner.jpg │ │ ├── NavBar.js │ │ ├── A.js │ │ ├── Img.js │ │ ├── tests │ │ │ ├── index.test.js │ │ │ ├── A.test.js │ │ │ └── Img.test.js │ │ ├── messages.js │ │ ├── HeaderLink.js │ │ └── index.js │ ├── H2 │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── H3 │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── H1 │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── Button │ │ ├── A.js │ │ ├── StyledButton.js │ │ ├── Wrapper.js │ │ ├── buttonStyles.js │ │ ├── tests │ │ │ ├── A.test.js │ │ │ ├── Wrapper.test.js │ │ │ ├── StyledButton.test.js │ │ │ └── index.test.js │ │ └── index.js │ ├── Toggle │ │ ├── Select.js │ │ ├── index.js │ │ └── tests │ │ │ ├── Select.test.js │ │ │ └── index.test.js │ ├── ProgressBar │ │ ├── Percent.js │ │ ├── tests │ │ │ ├── Percent.test.js │ │ │ └── Wrapper.test.js │ │ ├── Wrapper.js │ │ └── index.js │ ├── LoadingIndicator │ │ ├── Wrapper.js │ │ ├── tests │ │ │ ├── index.test.js │ │ │ ├── Circle.test.js │ │ │ └── Wrapper.test.js │ │ ├── index.js │ │ └── Circle.js │ ├── List │ │ ├── Ul.js │ │ ├── Wrapper.js │ │ ├── tests │ │ │ ├── index.test.js │ │ │ ├── Ul.test.js │ │ │ └── Wrapper.test.js │ │ └── index.js │ ├── Footer │ │ ├── Wrapper.js │ │ ├── messages.js │ │ ├── index.js │ │ └── tests │ │ │ ├── Wrapper.test.js │ │ │ └── index.test.js │ ├── ListItem │ │ ├── Item.js │ │ ├── Wrapper.js │ │ ├── index.js │ │ └── tests │ │ │ ├── index.test.js │ │ │ ├── Item.test.js │ │ │ └── Wrapper.test.js │ ├── A │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── IssueIcon │ │ ├── tests │ │ │ └── index.test.js │ │ └── index.js │ ├── ToggleOption │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── Img │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ └── ReposList │ │ ├── index.js │ │ └── tests │ │ └── index.test.js ├── containers │ ├── HomePage │ │ ├── Form.js │ │ ├── AtPrefix.js │ │ ├── Input.js │ │ ├── Section.js │ │ ├── CenteredSection.js │ │ ├── selectors.js │ │ ├── tests │ │ │ ├── actions.test.js │ │ │ ├── reducer.test.js │ │ │ ├── selectors.test.js │ │ │ ├── Form.test.js │ │ │ ├── Input.test.js │ │ │ ├── AtPrefix.test.js │ │ │ ├── Section.test.js │ │ │ ├── CenteredSection.test.js │ │ │ └── __snapshots__ │ │ │ │ └── sagas.test.js.snap │ │ ├── constants.js │ │ ├── reducer.js │ │ ├── actions.js │ │ ├── messages.js │ │ └── sagas.js │ ├── FeaturePage │ │ ├── ListItem.js │ │ ├── ListItemTitle.js │ │ ├── List.js │ │ └── tests │ │ │ ├── index.test.js │ │ │ ├── List.test.js │ │ │ ├── ListItem.test.js │ │ │ └── ListItemTitle.test.js │ ├── LanguageProvider │ │ ├── constants.js │ │ ├── actions.js │ │ ├── tests │ │ │ ├── selectors.test.js │ │ │ ├── actions.test.js │ │ │ ├── reducer.test.js │ │ │ └── index.test.js │ │ ├── selectors.js │ │ ├── reducer.js │ │ └── index.js │ ├── LocaleToggle │ │ ├── Wrapper.js │ │ ├── messages.js │ │ ├── tests │ │ │ ├── Wrapper.test.js │ │ │ └── index.test.js │ │ └── index.js │ ├── RepoListItem │ │ ├── Wrapper.js │ │ ├── IssueIcon.js │ │ ├── RepoLink.js │ │ ├── IssueLink.js │ │ ├── tests │ │ │ ├── Wrapper.test.js │ │ │ ├── IssueIcon.test.js │ │ │ ├── RepoLink.test.js │ │ │ ├── IssueLink.test.js │ │ │ └── index.test.js │ │ └── index.js │ ├── NotFoundPage │ │ ├── messages.js │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ └── App │ │ ├── constants.js │ │ ├── tests │ │ ├── index.test.js │ │ ├── actions.test.js │ │ └── reducer.test.js │ │ ├── index.js │ │ ├── selectors.js │ │ ├── reducer.js │ │ └── actions.js ├── global-styles.js ├── tests │ ├── store.test.js │ └── i18n.test.js ├── manifest.json ├── utils │ ├── request.js │ ├── tests │ │ └── request.test.js │ └── asyncInjectors.js ├── index.html ├── i18n.js ├── reducers.js ├── routes.js ├── store.js └── .htaccess ├── docs ├── general │ ├── workflow.png │ ├── webstorm-debug.png │ ├── webstorm-eslint.png │ ├── gotchas.md │ ├── progressbar.md │ ├── server-configs.md │ ├── files.md │ └── remove.md ├── css │ ├── remove.md │ ├── README.md │ ├── sanitize.md │ ├── sass.md │ └── styled-components.md ├── testing │ ├── remote-testing.md │ └── README.md └── js │ ├── README.md │ ├── redux.md │ ├── remove.md │ └── reselect.md ├── MAINTAINERS ├── .gitignore ├── .editorconfig ├── .travis.yml ├── LICENSE.md ├── server ├── logger.js └── index.js ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── appveyor.yml └── .gitattributes /internals/templates/translations/en.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /internals/generators/language/translations-json.hbs: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /internals/mocks/cssModule.js: -------------------------------------------------------------------------------- 1 | module.exports = 'CSS_MODULE'; 2 | -------------------------------------------------------------------------------- /internals/mocks/image.js: -------------------------------------------------------------------------------- 1 | module.exports = 'IMAGE_MOCK'; 2 | -------------------------------------------------------------------------------- /internals/generators/language/app-locale.hbs: -------------------------------------------------------------------------------- 1 | $1 '{{language}}', 2 | -------------------------------------------------------------------------------- /.lgtm: -------------------------------------------------------------------------------- 1 | approvals = 2 2 | pattern = "(?i):shipit:|LGTM" 3 | self_approval_off = true 4 | -------------------------------------------------------------------------------- /internals/generators/language/add-locale-data.hbs: -------------------------------------------------------------------------------- 1 | $1addLocaleData({{language}}LocaleData); 2 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrevorHinesley/react-boilerplate/master/app/favicon.ico -------------------------------------------------------------------------------- /internals/generators/language/polyfill-intl-locale.hbs: -------------------------------------------------------------------------------- 1 | $1 import('intl/locale-data/jsonp/{{language}}.js'), 2 | -------------------------------------------------------------------------------- /docs/general/workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrevorHinesley/react-boilerplate/master/docs/general/workflow.png -------------------------------------------------------------------------------- /docs/general/webstorm-debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrevorHinesley/react-boilerplate/master/docs/general/webstorm-debug.png -------------------------------------------------------------------------------- /internals/generators/language/intl-locale-data.hbs: -------------------------------------------------------------------------------- 1 | $1import {{language}}LocaleData from 'react-intl/locale-data/{{language}}'; 2 | -------------------------------------------------------------------------------- /app/components/Header/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrevorHinesley/react-boilerplate/master/app/components/Header/banner.jpg -------------------------------------------------------------------------------- /docs/general/webstorm-eslint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrevorHinesley/react-boilerplate/master/docs/general/webstorm-eslint.png -------------------------------------------------------------------------------- /internals/generators/language/translation-messages.hbs: -------------------------------------------------------------------------------- 1 | $1import {{language}}TranslationMessages from './translations/{{language}}.json'; 2 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | mxstbr 2 | oliverturner 3 | justingreenberg 4 | gihrig 5 | sedubois 6 | chaintng 7 | samit4me 8 | amilajack 9 | Dattaya 10 | -------------------------------------------------------------------------------- /app/components/Header/NavBar.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export default styled.div` 4 | text-align: center; 5 | `; 6 | -------------------------------------------------------------------------------- /internals/testing/test-bundler.js: -------------------------------------------------------------------------------- 1 | // needed for regenerator-runtime 2 | // (ES7 generator support is required by redux-saga) 3 | import 'babel-polyfill'; 4 | -------------------------------------------------------------------------------- /app/components/H2/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const H2 = styled.h2` 4 | font-size: 1.5em; 5 | `; 6 | 7 | export default H2; 8 | -------------------------------------------------------------------------------- /internals/generators/language/format-translation-messages.hbs: -------------------------------------------------------------------------------- 1 | $1 {{language}}: formatTranslationMessages('{{language}}', {{language}}TranslationMessages), 2 | -------------------------------------------------------------------------------- /app/containers/HomePage/Form.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Form = styled.form` 4 | margin-bottom: 1em; 5 | `; 6 | 7 | export default Form; 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't check auto-generated stuff into git 2 | coverage 3 | build 4 | node_modules 5 | stats.json 6 | 7 | # Cruft 8 | .DS_Store 9 | npm-debug.log 10 | .idea 11 | -------------------------------------------------------------------------------- /app/components/H3/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function H3(props) { 4 | return ( 5 |

6 | ); 7 | } 8 | 9 | export default H3; 10 | -------------------------------------------------------------------------------- /app/containers/FeaturePage/ListItem.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const ListItem = styled.li` 4 | margin: 1em 0; 5 | `; 6 | 7 | export default ListItem; 8 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider constants 4 | * 5 | */ 6 | 7 | export const CHANGE_LOCALE = 'app/LanguageToggle/CHANGE_LOCALE'; 8 | -------------------------------------------------------------------------------- /app/containers/LocaleToggle/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Wrapper = styled.div` 4 | padding: 2px; 5 | `; 6 | 7 | export default Wrapper; 8 | -------------------------------------------------------------------------------- /app/components/H1/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const H1 = styled.h1` 4 | font-size: 2em; 5 | margin-bottom: 0.25em; 6 | `; 7 | 8 | export default H1; 9 | -------------------------------------------------------------------------------- /app/components/Button/A.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import buttonStyles from './buttonStyles'; 4 | 5 | const A = styled.a`${buttonStyles}`; 6 | 7 | export default A; 8 | -------------------------------------------------------------------------------- /app/components/Toggle/Select.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Select = styled.select` 4 | line-height: 1em; 5 | height: 20px; 6 | `; 7 | 8 | export default Select; 9 | -------------------------------------------------------------------------------- /internals/generators/container/constants.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * {{ properCase name }} constants 4 | * 5 | */ 6 | 7 | export const DEFAULT_ACTION = 'app/{{ properCase name }}/DEFAULT_ACTION'; 8 | -------------------------------------------------------------------------------- /app/components/ProgressBar/Percent.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export default styled.div` 4 | height: 2px; 5 | background: #29D; 6 | transition: all 300ms ease; 7 | `; 8 | -------------------------------------------------------------------------------- /app/containers/FeaturePage/ListItemTitle.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const ListItemTitle = styled.p` 4 | font-weight: bold; 5 | `; 6 | 7 | export default ListItemTitle; 8 | -------------------------------------------------------------------------------- /app/containers/HomePage/AtPrefix.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const AtPrefix = styled.span` 4 | color: black; 5 | margin-left: 0.4em; 6 | `; 7 | 8 | export default AtPrefix; 9 | -------------------------------------------------------------------------------- /docs/css/remove.md: -------------------------------------------------------------------------------- 1 | ## Removing `sanitize.css` 2 | 3 | Delete [lines 31 and 32 in `app.js`](../../app/app.js#L31-L32) and remove it 4 | from the `dependencies` in [`package.json`](../../package.json)! 5 | -------------------------------------------------------------------------------- /app/containers/HomePage/Input.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Input = styled.input` 4 | outline: none; 5 | border-bottom: 1px dotted #999; 6 | `; 7 | 8 | export default Input; 9 | -------------------------------------------------------------------------------- /app/components/Header/A.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import NormalA from 'components/A'; 4 | 5 | const A = styled(NormalA)` 6 | padding: 2em 0; 7 | `; 8 | 9 | export default A; 10 | -------------------------------------------------------------------------------- /app/components/Button/StyledButton.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import buttonStyles from './buttonStyles'; 4 | 5 | const StyledButton = styled.button`${buttonStyles}`; 6 | 7 | export default StyledButton; 8 | -------------------------------------------------------------------------------- /app/components/Button/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Wrapper = styled.div` 4 | width: 100%; 5 | text-align: center; 6 | margin: 4em 0; 7 | `; 8 | 9 | export default Wrapper; 10 | -------------------------------------------------------------------------------- /app/containers/FeaturePage/List.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const List = styled.ul` 4 | font-family: Georgia, Times, 'Times New Roman', serif; 5 | padding-left: 1.75em; 6 | `; 7 | 8 | export default List; 9 | -------------------------------------------------------------------------------- /internals/templates/containers/LanguageProvider/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider constants 4 | * 5 | */ 6 | 7 | export const CHANGE_LOCALE = 'app/LanguageToggle/CHANGE_LOCALE'; 8 | export const DEFAULT_LOCALE = 'en'; 9 | -------------------------------------------------------------------------------- /app/containers/HomePage/Section.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Section = styled.section` 4 | margin: 3em auto; 5 | 6 | &:first-child { 7 | margin-top: 0; 8 | } 9 | `; 10 | 11 | export default Section; 12 | -------------------------------------------------------------------------------- /app/components/LoadingIndicator/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Wrapper = styled.div` 4 | margin: 2em auto; 5 | width: 40px; 6 | height: 40px; 7 | position: relative; 8 | `; 9 | 10 | export default Wrapper; 11 | -------------------------------------------------------------------------------- /app/containers/HomePage/CenteredSection.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import Section from './Section'; 4 | 5 | const CenteredSection = styled(Section)` 6 | text-align: center; 7 | `; 8 | 9 | export default CenteredSection; 10 | -------------------------------------------------------------------------------- /app/containers/RepoListItem/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Wrapper = styled.div` 4 | width: 100%; 5 | height: 100%; 6 | display: flex; 7 | align-items: space-between; 8 | `; 9 | 10 | export default Wrapper; 11 | -------------------------------------------------------------------------------- /app/components/Header/Img.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import NormalImg from 'components/Img'; 4 | 5 | const Img = styled(NormalImg)` 6 | width: 100%; 7 | margin: 0 auto; 8 | display: block; 9 | `; 10 | 11 | export default Img; 12 | -------------------------------------------------------------------------------- /app/components/List/Ul.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Ul = styled.ul` 4 | list-style: none; 5 | margin: 0; 6 | width: 100%; 7 | max-height: 30em; 8 | overflow-y: auto; 9 | padding: 0 1em; 10 | `; 11 | 12 | export default Ul; 13 | -------------------------------------------------------------------------------- /app/components/Footer/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Wrapper = styled.footer` 4 | display: flex; 5 | justify-content: space-between; 6 | padding: 3em 0; 7 | border-top: 1px solid #666; 8 | `; 9 | 10 | export default Wrapper; 11 | -------------------------------------------------------------------------------- /app/components/ListItem/Item.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Item = styled.div` 4 | display: flex; 5 | justify-content: space-between; 6 | width: 100%; 7 | height: 100%; 8 | align-items: center; 9 | `; 10 | 11 | export default Item; 12 | -------------------------------------------------------------------------------- /internals/scripts/helpers/xmark.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | /** 4 | * Adds mark cross symbol 5 | */ 6 | function addXMark(callback) { 7 | process.stdout.write(chalk.red(' ✘')); 8 | if (callback) callback(); 9 | } 10 | 11 | module.exports = addXMark; 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /app/components/A/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A link to a certain page, an anchor tag 3 | */ 4 | 5 | import styled from 'styled-components'; 6 | 7 | const A = styled.a` 8 | color: #41addd; 9 | 10 | &:hover { 11 | color: #6cc0e5; 12 | } 13 | `; 14 | 15 | export default A; 16 | -------------------------------------------------------------------------------- /app/containers/RepoListItem/IssueIcon.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import NormalIssueIcon from 'components/IssueIcon'; 4 | 5 | const IssueIcon = styled(NormalIssueIcon)` 6 | fill: #ccc; 7 | margin-right: 0.25em; 8 | `; 9 | 10 | export default IssueIcon; 11 | -------------------------------------------------------------------------------- /internals/generators/container/actions.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * {{ properCase name }} actions 4 | * 5 | */ 6 | 7 | import { 8 | DEFAULT_ACTION, 9 | } from './constants'; 10 | 11 | export function defaultAction() { 12 | return { 13 | type: DEFAULT_ACTION, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /internals/scripts/helpers/checkmark.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | /** 4 | * Adds mark check symbol 5 | */ 6 | function addCheckMark(callback) { 7 | process.stdout.write(chalk.green(' ✓')); 8 | if (callback) callback(); 9 | } 10 | 11 | module.exports = addCheckMark; 12 | -------------------------------------------------------------------------------- /app/containers/RepoListItem/RepoLink.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import A from 'components/A'; 4 | 5 | const RepoLink = styled(A)` 6 | height: 100%; 7 | color: black; 8 | display: flex; 9 | align-items: center; 10 | width: 100%; 11 | `; 12 | 13 | export default RepoLink; 14 | -------------------------------------------------------------------------------- /internals/scripts/npmcheckversion.js: -------------------------------------------------------------------------------- 1 | const exec = require('child_process').exec; 2 | exec('npm -v', function (err, stdout, stderr) { 3 | if (err) throw err; 4 | if (parseFloat(stdout) < 3) { 5 | throw new Error('[ERROR: React Boilerplate] You need npm version @>=3'); 6 | process.exit(1); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /internals/generators/route/route.hbs: -------------------------------------------------------------------------------- 1 | { 2 | path: '{{ path }}', 3 | name: '{{ camelCase component }}', 4 | getComponent(location, cb) { 5 | import('{{{directory (properCase component)}}}') 6 | .then(loadModule(cb)) 7 | .catch(errorLoading); 8 | }, 9 | },$1 10 | -------------------------------------------------------------------------------- /app/components/List/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Wrapper = styled.div` 4 | padding: 0; 5 | margin: 0; 6 | width: 100%; 7 | background-color: white; 8 | border: 1px solid #ccc; 9 | border-radius: 3px; 10 | overflow: hidden; 11 | `; 12 | 13 | export default Wrapper; 14 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/actions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider actions 4 | * 5 | */ 6 | 7 | import { 8 | CHANGE_LOCALE, 9 | } from './constants'; 10 | 11 | export function changeLocale(languageLocale) { 12 | return { 13 | type: CHANGE_LOCALE, 14 | locale: languageLocale, 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /app/components/ProgressBar/tests/Percent.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import Percent from '../Percent'; 5 | 6 | it('should render an
tag', () => { 7 | const renderedComponent = shallow(); 8 | expect(renderedComponent.type()).toEqual('div'); 9 | }); 10 | -------------------------------------------------------------------------------- /app/components/ProgressBar/tests/Wrapper.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import Wrapper from '../Wrapper'; 5 | 6 | it('should render an
tag', () => { 7 | const renderedComponent = shallow(); 8 | expect(renderedComponent.type()).toEqual('div'); 9 | }); 10 | -------------------------------------------------------------------------------- /app/containers/RepoListItem/IssueLink.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import A from 'components/A'; 4 | 5 | const IssueLink = styled(A)` 6 | height: 100%; 7 | color: black; 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | `; 12 | 13 | export default IssueLink; 14 | -------------------------------------------------------------------------------- /internals/generators/container/sagas.js.hbs: -------------------------------------------------------------------------------- 1 | // import { take, call, put, select } from 'redux-saga/effects'; 2 | 3 | // Individual exports for testing 4 | export function* defaultSaga() { 5 | // See example in containers/HomePage/sagas.js 6 | } 7 | 8 | // All sagas to be loaded 9 | export default [ 10 | defaultSaga, 11 | ]; 12 | -------------------------------------------------------------------------------- /internals/templates/containers/LanguageProvider/actions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider actions 4 | * 5 | */ 6 | 7 | import { 8 | CHANGE_LOCALE, 9 | } from './constants'; 10 | 11 | export function changeLocale(languageLocale) { 12 | return { 13 | type: CHANGE_LOCALE, 14 | locale: languageLocale, 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /internals/generators/component/test.js.hbs: -------------------------------------------------------------------------------- 1 | // import React from 'react'; 2 | // import { shallow } from 'enzyme'; 3 | 4 | // import {{ properCase name }} from '../index'; 5 | 6 | describe('<{{ properCase name }} />', () => { 7 | it('Expect to have unit tests specified', () => { 8 | expect(true).toEqual(false); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /internals/generators/container/test.js.hbs: -------------------------------------------------------------------------------- 1 | // import React from 'react'; 2 | // import { shallow } from 'enzyme'; 3 | 4 | // import { {{ properCase name }} } from '../index'; 5 | 6 | describe('<{{ properCase name }} />', () => { 7 | it('Expect to have unit tests specified', () => { 8 | expect(true).toEqual(false); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /internals/generators/container/reducer.test.js.hbs: -------------------------------------------------------------------------------- 1 | 2 | import { fromJS } from 'immutable'; 3 | import {{ camelCase name }}Reducer from '../reducer'; 4 | 5 | describe('{{ camelCase name }}Reducer', () => { 6 | it('returns the initial state', () => { 7 | expect({{ camelCase name }}Reducer(undefined, {})).toEqual(fromJS({})); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /app/components/ListItem/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Wrapper = styled.li` 4 | width: 100%; 5 | height: 3em; 6 | display: flex; 7 | align-items: center; 8 | position: relative; 9 | border-top: 1px solid #eee; 10 | 11 | &:first-child { 12 | border-top: none; 13 | } 14 | `; 15 | 16 | export default Wrapper; 17 | -------------------------------------------------------------------------------- /app/containers/NotFoundPage/messages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * NotFoundPage Messages 3 | * 4 | * This contains all the text for the NotFoundPage component. 5 | */ 6 | import { defineMessages } from 'react-intl'; 7 | 8 | export default defineMessages({ 9 | header: { 10 | id: 'boilerplate.containers.NotFoundPage.header', 11 | defaultMessage: 'Page not found.', 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /internals/templates/containers/HomePage/messages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * HomePage Messages 3 | * 4 | * This contains all the text for the HomePage component. 5 | */ 6 | import { defineMessages } from 'react-intl'; 7 | 8 | export default defineMessages({ 9 | header: { 10 | id: 'app.components.HomePage.header', 11 | defaultMessage: 'This is HomePage component!', 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /app/components/Header/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import Header from '../index'; 5 | 6 | describe('
', () => { 7 | it('should render a div', () => { 8 | const renderedComponent = shallow( 9 |
10 | ); 11 | expect(renderedComponent.find('div').length).toEqual(1); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /app/containers/HomePage/selectors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Homepage selectors 3 | */ 4 | 5 | import { createSelector } from 'reselect'; 6 | 7 | const selectHome = (state) => state.get('home'); 8 | 9 | const makeSelectUsername = () => createSelector( 10 | selectHome, 11 | (homeState) => homeState.get('username') 12 | ); 13 | 14 | export { 15 | selectHome, 16 | makeSelectUsername, 17 | }; 18 | -------------------------------------------------------------------------------- /app/components/IssueIcon/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import IssueIcon from '../index'; 5 | 6 | describe('', () => { 7 | it('should render a SVG', () => { 8 | const renderedComponent = shallow( 9 | 10 | ); 11 | expect(renderedComponent.find('svg').length).toBe(1); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /internals/templates/containers/NotFoundPage/messages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * NotFoundPage Messages 3 | * 4 | * This contains all the text for the NotFoundPage component. 5 | */ 6 | import { defineMessages } from 'react-intl'; 7 | 8 | export default defineMessages({ 9 | header: { 10 | id: 'app.components.NotFoundPage.header', 11 | defaultMessage: 'This is NotFoundPage component!', 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /app/components/ListItem/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Item from './Item'; 4 | import Wrapper from './Wrapper'; 5 | 6 | function ListItem(props) { 7 | return ( 8 | 9 | 10 | {props.item} 11 | 12 | 13 | ); 14 | } 15 | 16 | ListItem.propTypes = { 17 | item: React.PropTypes.any, 18 | }; 19 | 20 | export default ListItem; 21 | -------------------------------------------------------------------------------- /app/components/ProgressBar/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export default styled.div` 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | width: 100%; 8 | visibility: ${(props) => props.hidden ? 'hidden' : 'visible'}; 9 | opacity: ${(props) => props.hidden ? '0' : '1'}; 10 | transition: all 500ms ease-in-out; 11 | z-index: ${(props) => props.hidden ? '-10' : '9999'}; 12 | `; 13 | -------------------------------------------------------------------------------- /internals/generators/container/selectors.test.js.hbs: -------------------------------------------------------------------------------- 1 | // import { fromJS } from 'immutable'; 2 | // import { makeSelect{{ properCase name }}Domain } from '../selectors'; 3 | 4 | // const selector = makeSelect{{ properCase name}}Domain(); 5 | 6 | describe('makeSelect{{ properCase name }}Domain', () => { 7 | it('Expect to have unit tests specified', () => { 8 | expect(true).toEqual(false); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /app/components/LoadingIndicator/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'enzyme'; 3 | 4 | import LoadingIndicator from '../index'; 5 | 6 | describe('', () => { 7 | it('should render 13 divs', () => { 8 | const renderedComponent = render( 9 | 10 | ); 11 | expect(renderedComponent.find('div').length).toBe(13); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /internals/generators/container/messages.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * {{properCase name }} Messages 3 | * 4 | * This contains all the text for the {{properCase name }} component. 5 | */ 6 | import { defineMessages } from 'react-intl'; 7 | 8 | export default defineMessages({ 9 | header: { 10 | id: 'app.containers.{{properCase name }}.header', 11 | defaultMessage: 'This is {{properCase name}} container !', 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /internals/generators/component/messages.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * {{ properCase name }} Messages 3 | * 4 | * This contains all the text for the {{ properCase name }} component. 5 | */ 6 | import { defineMessages } from 'react-intl'; 7 | 8 | export default defineMessages({ 9 | header: { 10 | id: 'app.components.{{ properCase name }}.header', 11 | defaultMessage: 'This is the {{ properCase name}} component !', 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /internals/generators/container/sagas.test.js.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * Test sagas 3 | */ 4 | 5 | /* eslint-disable redux-saga/yield-effects */ 6 | // import { take, call, put, select } from 'redux-saga/effects'; 7 | // import { defaultSaga } from '../sagas'; 8 | 9 | // const generator = defaultSaga(); 10 | 11 | describe('defaultSaga Saga', () => { 12 | it('Expect to have unit tests specified', () => { 13 | expect(true).toEqual(false); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /docs/testing/remote-testing.md: -------------------------------------------------------------------------------- 1 | # Remote testing 2 | 3 | ```Shell 4 | npm run start:tunnel 5 | ``` 6 | 7 | This command will start a server and tunnel it with `ngrok`. You'll get a URL 8 | that looks a bit like this: `http://abcdef.ngrok.com` 9 | 10 | This URL will show the version of your application that's in the `build` folder, 11 | and it's accessible from the entire world! This is great for testing on different 12 | devices and from different locations! 13 | -------------------------------------------------------------------------------- /app/components/Header/messages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * HomePage Messages 3 | * 4 | * This contains all the text for the HomePage component. 5 | */ 6 | import { defineMessages } from 'react-intl'; 7 | 8 | export default defineMessages({ 9 | home: { 10 | id: 'boilerplate.components.Header.home', 11 | defaultMessage: 'Home', 12 | }, 13 | features: { 14 | id: 'boilerplate.components.Header.features', 15 | defaultMessage: 'Features', 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/tests/selectors.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import { 4 | selectLanguage, 5 | } from '../selectors'; 6 | 7 | describe('selectLanguage', () => { 8 | it('should select the global state', () => { 9 | const globalState = fromJS({}); 10 | const mockedState = fromJS({ 11 | language: globalState, 12 | }); 13 | expect(selectLanguage(mockedState)).toEqual(globalState); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /app/containers/LocaleToggle/messages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * LocaleToggle Messages 3 | * 4 | * This contains all the text for the LanguageToggle component. 5 | */ 6 | import { defineMessages } from 'react-intl'; 7 | 8 | export default defineMessages({ 9 | en: { 10 | id: 'boilerplate.containers.LocaleToggle.en', 11 | defaultMessage: 'en', 12 | }, 13 | de: { 14 | id: 'boilerplate.containers.LocaleToggle.de', 15 | defaultMessage: 'de', 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /internals/templates/containers/App/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import App from '../index'; 5 | 6 | describe('', () => { 7 | it('should render its children', () => { 8 | const children = (

Test

); 9 | const renderedComponent = shallow( 10 | 11 | {children} 12 | 13 | ); 14 | expect(renderedComponent.contains(children)).toBe(true); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /internals/generators/container/actions.test.js.hbs: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | defaultAction, 4 | } from '../actions'; 5 | import { 6 | DEFAULT_ACTION, 7 | } from '../constants'; 8 | 9 | describe('{{ properCase name }} actions', () => { 10 | describe('Default Action', () => { 11 | it('has a type of DEFAULT_ACTION', () => { 12 | const expected = { 13 | type: DEFAULT_ACTION, 14 | }; 15 | expect(defaultAction()).toEqual(expected); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | /** 4 | * Direct selector to the languageToggle state domain 5 | */ 6 | const selectLanguage = (state) => state.get('language'); 7 | 8 | /** 9 | * Select the language locale 10 | */ 11 | 12 | const makeSelectLocale = () => createSelector( 13 | selectLanguage, 14 | (languageState) => languageState.get('locale') 15 | ); 16 | 17 | export { 18 | selectLanguage, 19 | makeSelectLocale, 20 | }; 21 | -------------------------------------------------------------------------------- /internals/templates/containers/LanguageProvider/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | /** 4 | * Direct selector to the languageToggle state domain 5 | */ 6 | const selectLanguage = (state) => state.get('language'); 7 | 8 | /** 9 | * Select the language locale 10 | */ 11 | 12 | const makeSelectLocale = () => createSelector( 13 | selectLanguage, 14 | (languageState) => languageState.get('locale') 15 | ); 16 | 17 | export { 18 | selectLanguage, 19 | makeSelectLocale, 20 | }; 21 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/tests/actions.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | changeLocale, 3 | } from '../actions'; 4 | 5 | import { 6 | CHANGE_LOCALE, 7 | } from '../constants'; 8 | 9 | describe('LanguageProvider actions', () => { 10 | describe('Change Local Action', () => { 11 | it('has a type of CHANGE_LOCALE', () => { 12 | const expected = { 13 | type: CHANGE_LOCALE, 14 | locale: 'de', 15 | }; 16 | expect(changeLocale('de')).toEqual(expected); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /docs/css/README.md: -------------------------------------------------------------------------------- 1 | # CSS 2 | 3 | This boilerplate uses [`styled-components`](https://github.com/styled-components/styled-components) 4 | allowing you to write your CSS in your JavaScript, 5 | removing the mapping between styles and components. 6 | 7 | `styled-components` let's us embrace component encapsulation while sanitize.css gives us 8 | data-driven cross-browser normalisation. 9 | 10 | Learn more: 11 | 12 | - [`styled-components`](styled-components.md) 13 | - [sanitize.css](sanitize.md) 14 | - [Using Sass](sass.md) 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 7 5 | - 6 6 | - 5 7 | 8 | script: 9 | - node ./internals/scripts/generate-templates-for-linting 10 | - npm run test 11 | - npm run build 12 | 13 | before_install: 14 | - export CHROME_BIN=chromium-browser 15 | - export DISPLAY=:99.0 16 | - sh -e /etc/init.d/xvfb start 17 | 18 | notifications: 19 | email: 20 | on_failure: change 21 | 22 | after_success: 'npm run coveralls' 23 | 24 | cache: 25 | yarn: true 26 | directories: 27 | - node_modules 28 | -------------------------------------------------------------------------------- /app/containers/NotFoundPage/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NotFoundPage 3 | * 4 | * This is the page we show when the user visits a url that doesn't have a route 5 | */ 6 | 7 | import React from 'react'; 8 | import { FormattedMessage } from 'react-intl'; 9 | 10 | import H1 from 'components/H1'; 11 | import messages from './messages'; 12 | 13 | export default function NotFound() { 14 | return ( 15 |
16 |

17 | 18 |

19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /internals/generators/container/reducer.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * {{ properCase name }} reducer 4 | * 5 | */ 6 | 7 | import { fromJS } from 'immutable'; 8 | import { 9 | DEFAULT_ACTION, 10 | } from './constants'; 11 | 12 | const initialState = fromJS({}); 13 | 14 | function {{ camelCase name }}Reducer(state = initialState, action) { 15 | switch (action.type) { 16 | case DEFAULT_ACTION: 17 | return state; 18 | default: 19 | return state; 20 | } 21 | } 22 | 23 | export default {{ camelCase name }}Reducer; 24 | -------------------------------------------------------------------------------- /app/components/ToggleOption/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * ToggleOption 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | import { injectIntl, intlShape } from 'react-intl'; 9 | 10 | const ToggleOption = ({ value, message, intl }) => ( 11 | 14 | ); 15 | 16 | ToggleOption.propTypes = { 17 | value: React.PropTypes.string.isRequired, 18 | message: React.PropTypes.object, 19 | intl: intlShape.isRequired, 20 | }; 21 | 22 | export default injectIntl(ToggleOption); 23 | -------------------------------------------------------------------------------- /internals/templates/containers/App/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * AppConstants 3 | * Each action has a corresponding type, which the reducer knows and picks up on. 4 | * To avoid weird typos between the reducer and the actions, we save them as 5 | * constants here. We prefix them with 'yourproject/YourComponent' so we avoid 6 | * reducers accidentally picking up actions they shouldn't. 7 | * 8 | * Follow this format: 9 | * export const YOUR_ACTION_CONSTANT = 'yourproject/YourContainer/YOUR_ACTION_CONSTANT'; 10 | */ 11 | 12 | export const DEFAULT_LOCALE = 'en'; 13 | -------------------------------------------------------------------------------- /app/components/IssueIcon/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function IssueIcon(props) { 4 | return ( 5 | 10 | 11 | 12 | ); 13 | } 14 | 15 | IssueIcon.propTypes = { 16 | className: React.PropTypes.string, 17 | }; 18 | 19 | export default IssueIcon; 20 | -------------------------------------------------------------------------------- /internals/templates/containers/HomePage/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormattedMessage } from 'react-intl'; 3 | import { shallow } from 'enzyme'; 4 | 5 | import HomePage from '../index'; 6 | import messages from '../messages'; 7 | 8 | describe('', () => { 9 | it('should render the page message', () => { 10 | const renderedComponent = shallow( 11 | 12 | ); 13 | expect(renderedComponent.contains( 14 | 15 | )).toEqual(true); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /app/components/Footer/messages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Footer Messages 3 | * 4 | * This contains all the text for the Footer component. 5 | */ 6 | import { defineMessages } from 'react-intl'; 7 | 8 | export default defineMessages({ 9 | licenseMessage: { 10 | id: 'boilerplate.components.Footer.license.message', 11 | defaultMessage: 'This project is licensed under the MIT license.', 12 | }, 13 | authorMessage: { 14 | id: 'boilerplate.components.Footer.author.message', 15 | defaultMessage: ` 16 | Made with love by {author}. 17 | `, 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /app/containers/HomePage/tests/actions.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | CHANGE_USERNAME, 3 | } from '../constants'; 4 | 5 | import { 6 | changeUsername, 7 | } from '../actions'; 8 | 9 | describe('Home Actions', () => { 10 | describe('changeUsername', () => { 11 | it('should return the correct type and the passed name', () => { 12 | const fixture = 'Max'; 13 | const expectedResult = { 14 | type: CHANGE_USERNAME, 15 | name: fixture, 16 | }; 17 | 18 | expect(changeUsername(fixture)).toEqual(expectedResult); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /app/containers/HomePage/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * HomeConstants 3 | * Each action has a corresponding type, which the reducer knows and picks up on. 4 | * To avoid weird typos between the reducer and the actions, we save them as 5 | * constants here. We prefix them with 'yourproject/YourComponent' so we avoid 6 | * reducers accidentally picking up actions they shouldn't. 7 | * 8 | * Follow this format: 9 | * export const YOUR_ACTION_CONSTANT = 'yourproject/YourContainer/YOUR_ACTION_CONSTANT'; 10 | */ 11 | 12 | export const CHANGE_USERNAME = 'boilerplate/Home/CHANGE_USERNAME'; 13 | -------------------------------------------------------------------------------- /internals/templates/containers/NotFoundPage/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormattedMessage } from 'react-intl'; 3 | import { shallow } from 'enzyme'; 4 | 5 | import NotFoundPage from '../index'; 6 | import messages from '../messages'; 7 | 8 | describe('', () => { 9 | it('should render the page message', () => { 10 | const renderedComponent = shallow( 11 | 12 | ); 13 | expect(renderedComponent.contains( 14 | 15 | )).toEqual(true); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /internals/templates/containers/App/selectors.js: -------------------------------------------------------------------------------- 1 | // makeSelectLocationState expects a plain JS object for the routing state 2 | const makeSelectLocationState = () => { 3 | let prevRoutingState; 4 | let prevRoutingStateJS; 5 | 6 | return (state) => { 7 | const routingState = state.get('route'); // or state.route 8 | 9 | if (!routingState.equals(prevRoutingState)) { 10 | prevRoutingState = routingState; 11 | prevRoutingStateJS = routingState.toJS(); 12 | } 13 | 14 | return prevRoutingStateJS; 15 | }; 16 | }; 17 | 18 | export { 19 | makeSelectLocationState, 20 | }; 21 | -------------------------------------------------------------------------------- /app/containers/FeaturePage/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { FormattedMessage } from 'react-intl'; 4 | 5 | import H1 from 'components/H1'; 6 | import messages from '../messages'; 7 | import FeaturePage from '../index'; 8 | 9 | describe('', () => { 10 | it('should render its heading', () => { 11 | const renderedComponent = shallow( 12 | 13 | ); 14 | expect(renderedComponent.contains( 15 |

16 | 17 |

18 | )).toBe(true); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /internals/generators/component/stateless.js.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * {{ properCase name }} 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | // import styled from 'styled-components'; 9 | 10 | {{#if wantMessages}} 11 | import { FormattedMessage } from 'react-intl'; 12 | import messages from './messages'; 13 | {{/if}} 14 | 15 | function {{ properCase name }}() { 16 | return ( 17 |
18 | {{#if wantMessages}} 19 | 20 | {{/if}} 21 |
22 | ); 23 | } 24 | 25 | {{ properCase name }}.propTypes = { 26 | 27 | }; 28 | 29 | export default {{ properCase name }}; 30 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/tests/reducer.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import languageProviderReducer from '../reducer'; 4 | import { 5 | CHANGE_LOCALE, 6 | } from '../constants'; 7 | 8 | describe('languageProviderReducer', () => { 9 | it('returns the initial state', () => { 10 | expect(languageProviderReducer(undefined, {})).toEqual(fromJS({ 11 | locale: 'en', 12 | })); 13 | }); 14 | 15 | it('changes the locale', () => { 16 | expect(languageProviderReducer(undefined, { type: CHANGE_LOCALE, locale: 'de' }).toJS()).toEqual({ 17 | locale: 'de', 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /internals/generators/utils/componentExists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * componentExists 3 | * 4 | * Check whether the given component exist in either the components or containers directory 5 | */ 6 | 7 | const fs = require('fs'); 8 | const path = require('path'); 9 | const pageComponents = fs.readdirSync(path.join(__dirname, '../../../app/components')); 10 | const pageContainers = fs.readdirSync(path.join(__dirname, '../../../app/containers')); 11 | const components = pageComponents.concat(pageContainers); 12 | 13 | function componentExists(comp) { 14 | return components.indexOf(comp) >= 0; 15 | } 16 | 17 | module.exports = componentExists; 18 | -------------------------------------------------------------------------------- /docs/css/sanitize.md: -------------------------------------------------------------------------------- 1 | # `sanitize.css` 2 | 3 | Sanitize.css makes browsers render elements more in 4 | line with developer expectations (e.g. having the box model set to a cascading 5 | `box-sizing: border-box`) and preferences (its defaults can be individually 6 | overridden). 7 | 8 | It was selected over older projects like `normalize.css` and `reset.css` due 9 | to its greater flexibility and better alignment with CSSNext features like CSS 10 | variables. 11 | 12 | See the [official documentation](https://github.com/10up/sanitize.css) for more 13 | information. 14 | 15 | --- 16 | 17 | _Don't like this feature? [Click here](remove.md)_ 18 | -------------------------------------------------------------------------------- /app/components/H1/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import H1 from '../index'; 5 | 6 | describe('

', () => { 7 | it('should render a prop', () => { 8 | const id = 'testId'; 9 | const renderedComponent = shallow( 10 |

11 | ); 12 | expect(renderedComponent.prop('id')).toEqual(id); 13 | }); 14 | 15 | it('should render its text', () => { 16 | const children = 'Text'; 17 | const renderedComponent = shallow( 18 |

{children}

19 | ); 20 | expect(renderedComponent.contains(children)).toBe(true); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /app/components/H2/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import H2 from '../index'; 5 | 6 | describe('

', () => { 7 | it('should render a prop', () => { 8 | const id = 'testId'; 9 | const renderedComponent = shallow( 10 |

11 | ); 12 | expect(renderedComponent.prop('id')).toEqual(id); 13 | }); 14 | 15 | it('should render its text', () => { 16 | const children = 'Text'; 17 | const renderedComponent = shallow( 18 |

{children}

19 | ); 20 | expect(renderedComponent.contains(children)).toBe(true); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /app/components/H3/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import H3 from '../index'; 5 | 6 | describe('

', () => { 7 | it('should render a prop', () => { 8 | const id = 'testId'; 9 | const renderedComponent = shallow( 10 |

11 | ); 12 | expect(renderedComponent.prop('id')).toEqual(id); 13 | }); 14 | 15 | it('should render its text', () => { 16 | const children = 'Text'; 17 | const renderedComponent = shallow( 18 |

{children}

19 | ); 20 | expect(renderedComponent.contains(children)).toBe(true); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /app/components/Img/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Img.react.js 4 | * 5 | * Renders an image, enforcing the usage of the alt="" tag 6 | */ 7 | 8 | import React, { PropTypes } from 'react'; 9 | 10 | function Img(props) { 11 | return ( 12 | {props.alt} 13 | ); 14 | } 15 | 16 | // We require the use of src and alt, only enforced by react in dev mode 17 | Img.propTypes = { 18 | src: PropTypes.oneOfType([ 19 | PropTypes.string, 20 | PropTypes.object, 21 | ]).isRequired, 22 | alt: PropTypes.string.isRequired, 23 | className: PropTypes.string, 24 | }; 25 | 26 | export default Img; 27 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider reducer 4 | * 5 | */ 6 | 7 | import { fromJS } from 'immutable'; 8 | 9 | import { 10 | CHANGE_LOCALE, 11 | } from './constants'; 12 | import { 13 | DEFAULT_LOCALE, 14 | } from '../App/constants'; 15 | 16 | const initialState = fromJS({ 17 | locale: DEFAULT_LOCALE, 18 | }); 19 | 20 | function languageProviderReducer(state = initialState, action) { 21 | switch (action.type) { 22 | case CHANGE_LOCALE: 23 | return state 24 | .set('locale', action.locale); 25 | default: 26 | return state; 27 | } 28 | } 29 | 30 | export default languageProviderReducer; 31 | -------------------------------------------------------------------------------- /app/components/ListItem/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { mount } from 'enzyme'; 3 | 4 | import ListItem from '../index'; 5 | 6 | describe('', () => { 7 | it('should have a className', () => { 8 | const renderedComponent = mount(); 9 | expect(renderedComponent.find('li').prop('className')).toBeDefined(); 10 | }); 11 | 12 | it('should render the content passed to it', () => { 13 | const content = (
Hello world!
); 14 | const renderedComponent = mount( 15 | 16 | ); 17 | expect(renderedComponent.contains(content)).toBe(true); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /app/global-styles.js: -------------------------------------------------------------------------------- 1 | import { injectGlobal } from 'styled-components'; 2 | 3 | /* eslint no-unused-expressions: 0 */ 4 | injectGlobal` 5 | html, 6 | body { 7 | height: 100%; 8 | width: 100%; 9 | } 10 | 11 | body { 12 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 13 | } 14 | 15 | body.fontLoaded { 16 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 17 | } 18 | 19 | #app { 20 | background-color: #fafafa; 21 | min-height: 100%; 22 | min-width: 100%; 23 | } 24 | 25 | p, 26 | label { 27 | font-family: Georgia, Times, 'Times New Roman', serif; 28 | line-height: 1.5em; 29 | } 30 | `; 31 | -------------------------------------------------------------------------------- /internals/generators/container/selectors.js.hbs: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | /** 4 | * Direct selector to the {{ camelCase name }} state domain 5 | */ 6 | const select{{ properCase name }}Domain = () => (state) => state.get('{{ camelCase name }}'); 7 | 8 | /** 9 | * Other specific selectors 10 | */ 11 | 12 | 13 | /** 14 | * Default selector used by {{ properCase name }} 15 | */ 16 | 17 | const makeSelect{{ properCase name }} = () => createSelector( 18 | select{{ properCase name }}Domain(), 19 | (substate) => substate.toJS() 20 | ); 21 | 22 | export default makeSelect{{ properCase name }}; 23 | export { 24 | select{{ properCase name }}Domain, 25 | }; 26 | -------------------------------------------------------------------------------- /app/components/Header/HeaderLink.js: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router'; 2 | import styled from 'styled-components'; 3 | 4 | export default styled(Link)` 5 | display: inline-flex; 6 | padding: 0.25em 2em; 7 | margin: 1em; 8 | text-decoration: none; 9 | border-radius: 4px; 10 | -webkit-font-smoothing: antialiased; 11 | -webkit-touch-callout: none; 12 | user-select: none; 13 | cursor: pointer; 14 | outline: 0; 15 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 16 | font-weight: bold; 17 | font-size: 16px; 18 | border: 2px solid #41ADDD; 19 | color: #41ADDD; 20 | 21 | &:active { 22 | background: #41ADDD; 23 | color: #FFF; 24 | } 25 | `; 26 | -------------------------------------------------------------------------------- /internals/templates/global-styles.js: -------------------------------------------------------------------------------- 1 | import { injectGlobal } from 'styled-components'; 2 | 3 | /* eslint no-unused-expressions: 0 */ 4 | injectGlobal` 5 | html, 6 | body { 7 | height: 100%; 8 | width: 100%; 9 | } 10 | 11 | body { 12 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 13 | } 14 | 15 | body.fontLoaded { 16 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 17 | } 18 | 19 | #app { 20 | background-color: #fafafa; 21 | min-height: 100%; 22 | min-width: 100%; 23 | } 24 | 25 | p, 26 | label { 27 | font-family: Georgia, Times, 'Times New Roman', serif; 28 | line-height: 1.5em; 29 | } 30 | `; 31 | -------------------------------------------------------------------------------- /app/components/Button/buttonStyles.js: -------------------------------------------------------------------------------- 1 | import { css } from 'styled-components'; 2 | 3 | const buttonStyles = css` 4 | display: inline-block; 5 | box-sizing: border-box; 6 | padding: 0.25em 2em; 7 | text-decoration: none; 8 | border-radius: 4px; 9 | -webkit-font-smoothing: antialiased; 10 | -webkit-touch-callout: none; 11 | user-select: none; 12 | cursor: pointer; 13 | outline: 0; 14 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 15 | font-weight: bold; 16 | font-size: 16px; 17 | border: 2px solid #41addd; 18 | color: #41addd; 19 | 20 | &:active { 21 | background: #41addd; 22 | color: #fff; 23 | } 24 | `; 25 | 26 | export default buttonStyles; 27 | -------------------------------------------------------------------------------- /internals/templates/containers/LanguageProvider/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider reducer 4 | * 5 | */ 6 | 7 | import { fromJS } from 'immutable'; 8 | 9 | import { 10 | CHANGE_LOCALE, 11 | } from './constants'; 12 | import { 13 | DEFAULT_LOCALE, 14 | } from '../App/constants'; // eslint-disable-line 15 | 16 | const initialState = fromJS({ 17 | locale: DEFAULT_LOCALE, 18 | }); 19 | 20 | function languageProviderReducer(state = initialState, action) { 21 | switch (action.type) { 22 | case CHANGE_LOCALE: 23 | return state 24 | .set('locale', action.locale); 25 | default: 26 | return state; 27 | } 28 | } 29 | 30 | export default languageProviderReducer; 31 | -------------------------------------------------------------------------------- /app/tests/store.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test store addons 3 | */ 4 | 5 | import { browserHistory } from 'react-router'; 6 | import configureStore from '../store'; 7 | 8 | describe('configureStore', () => { 9 | let store; 10 | 11 | beforeAll(() => { 12 | store = configureStore({}, browserHistory); 13 | }); 14 | 15 | describe('asyncReducers', () => { 16 | it('should contain an object for async reducers', () => { 17 | expect(typeof store.asyncReducers).toBe('object'); 18 | }); 19 | }); 20 | 21 | describe('runSaga', () => { 22 | it('should contain a hook for `sagaMiddleware.run`', () => { 23 | expect(typeof store.runSaga).toBe('function'); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /internals/templates/tests/store.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test store addons 3 | */ 4 | 5 | import { browserHistory } from 'react-router'; 6 | import configureStore from '../store'; 7 | 8 | describe('configureStore', () => { 9 | let store; 10 | 11 | beforeAll(() => { 12 | store = configureStore({}, browserHistory); 13 | }); 14 | 15 | describe('asyncReducers', () => { 16 | it('should contain an object for async reducers', () => { 17 | expect(typeof store.asyncReducers).toEqual('object'); 18 | }); 19 | }); 20 | 21 | describe('runSaga', () => { 22 | it('should contain a hook for `sagaMiddleware.run`', () => { 23 | expect(typeof store.runSaga).toEqual('function'); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /internals/generators/component/es6.js.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * {{ properCase name }} 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | // import styled from 'styled-components'; 9 | 10 | {{#if wantMessages}} 11 | import { FormattedMessage } from 'react-intl'; 12 | import messages from './messages'; 13 | {{/if}} 14 | 15 | class {{ properCase name }} extends React.Component { // eslint-disable-line react/prefer-stateless-function 16 | render() { 17 | return ( 18 |
19 | {{#if wantMessages}} 20 | 21 | {{/if}} 22 |
23 | ); 24 | } 25 | } 26 | 27 | {{ properCase name }}.propTypes = { 28 | 29 | }; 30 | 31 | export default {{ properCase name }}; 32 | -------------------------------------------------------------------------------- /app/containers/NotFoundPage/tests/index.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Testing the NotFoundPage 3 | */ 4 | 5 | import React from 'react'; 6 | import { shallow } from 'enzyme'; 7 | import { FormattedMessage } from 'react-intl'; 8 | 9 | import H1 from 'components/H1'; 10 | import NotFound from '../index'; 11 | 12 | describe('', () => { 13 | it('should render the Page Not Found text', () => { 14 | const renderedComponent = shallow( 15 | 16 | ); 17 | expect(renderedComponent.contains( 18 |

19 | 23 |

)).toEqual(true); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /internals/generators/component/es6.pure.js.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * {{ properCase name }} 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | // import styled from 'styled-components'; 9 | 10 | {{#if wantMessages}} 11 | import { FormattedMessage } from 'react-intl'; 12 | import messages from './messages'; 13 | {{/if}} 14 | 15 | class {{ properCase name }} extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function 16 | render() { 17 | return ( 18 |
19 | {{#if wantMessages}} 20 | 21 | {{/if}} 22 |
23 | ); 24 | } 25 | } 26 | 27 | {{ properCase name }}.propTypes = { 28 | 29 | }; 30 | 31 | export default {{ properCase name }}; 32 | -------------------------------------------------------------------------------- /app/containers/App/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * AppConstants 3 | * Each action has a corresponding type, which the reducer knows and picks up on. 4 | * To avoid weird typos between the reducer and the actions, we save them as 5 | * constants here. We prefix them with 'yourproject/YourComponent' so we avoid 6 | * reducers accidentally picking up actions they shouldn't. 7 | * 8 | * Follow this format: 9 | * export const YOUR_ACTION_CONSTANT = 'yourproject/YourContainer/YOUR_ACTION_CONSTANT'; 10 | */ 11 | 12 | export const LOAD_REPOS = 'boilerplate/App/LOAD_REPOS'; 13 | export const LOAD_REPOS_SUCCESS = 'boilerplate/App/LOAD_REPOS_SUCCESS'; 14 | export const LOAD_REPOS_ERROR = 'boilerplate/App/LOAD_REPOS_ERROR'; 15 | export const DEFAULT_LOCALE = 'en'; 16 | -------------------------------------------------------------------------------- /internals/scripts/helpers/progress.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const readline = require('readline'); 4 | 5 | /** 6 | * Adds an animated progress indicator 7 | * 8 | * @param {string} message The message to write next to the indicator 9 | * @param {number} amountOfDots The amount of dots you want to animate 10 | */ 11 | function animateProgress(message, amountOfDots) { 12 | if (typeof amountOfDots !== 'number') { 13 | amountOfDots = 3; 14 | } 15 | 16 | let i = 0; 17 | return setInterval(function() { 18 | readline.cursorTo(process.stdout, 0); 19 | i = (i + 1) % (amountOfDots + 1); 20 | const dots = new Array(i + 1).join('.'); 21 | process.stdout.write(message + dots); 22 | }, 500); 23 | } 24 | 25 | module.exports = animateProgress; 26 | -------------------------------------------------------------------------------- /app/components/List/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'enzyme'; 3 | 4 | import ListItem from 'components/ListItem'; 5 | import List from '../index'; 6 | 7 | describe('', () => { 8 | it('should render the component if no items are passed', () => { 9 | const renderedComponent = render( 10 | 11 | ); 12 | expect(renderedComponent.find(ListItem)).toBeDefined(); 13 | }); 14 | 15 | it('should render the items', () => { 16 | const items = [ 17 | 'Hello', 18 | 'World', 19 | ]; 20 | const renderedComponent = render( 21 | 22 | ); 23 | expect(renderedComponent.find(items)).toBeDefined(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /app/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "React Boilerplate", 3 | "icons": [ 4 | { 5 | "src": "favicon.png", 6 | "sizes": "48x48", 7 | "type": "image/png", 8 | "density": 1.0 9 | }, 10 | { 11 | "src": "favicon.png", 12 | "sizes": "96x96", 13 | "type": "image/png", 14 | "density": 2.0 15 | }, 16 | { 17 | "src": "favicon.png", 18 | "sizes": "144x144", 19 | "type": "image/png", 20 | "density": 3.0 21 | }, 22 | { 23 | "src": "favicon.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "density": 4.0 27 | } 28 | ], 29 | "start_url": "index.html", 30 | "display": "standalone", 31 | "orientation": "portrait", 32 | "background_color": "#FFFFFF" 33 | } -------------------------------------------------------------------------------- /app/components/LoadingIndicator/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Circle from './Circle'; 4 | import Wrapper from './Wrapper'; 5 | 6 | const LoadingIndicator = () => ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | 23 | export default LoadingIndicator; 24 | -------------------------------------------------------------------------------- /app/containers/HomePage/tests/reducer.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import homeReducer from '../reducer'; 4 | import { 5 | changeUsername, 6 | } from '../actions'; 7 | 8 | describe('homeReducer', () => { 9 | let state; 10 | beforeEach(() => { 11 | state = fromJS({ 12 | username: '', 13 | }); 14 | }); 15 | 16 | it('should return the initial state', () => { 17 | const expectedResult = state; 18 | expect(homeReducer(undefined, {})).toEqual(expectedResult); 19 | }); 20 | 21 | it('should handle the changeUsername action correctly', () => { 22 | const fixture = 'mxstbr'; 23 | const expectedResult = state.set('username', fixture); 24 | 25 | expect(homeReducer(state, changeUsername(fixture))).toEqual(expectedResult); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /app/components/LoadingIndicator/tests/Circle.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { mount } from 'enzyme'; 3 | 4 | import Circle from '../Circle'; 5 | 6 | describe('', () => { 7 | it('should render an
tag', () => { 8 | const renderedComponent = mount(); 9 | expect(renderedComponent.find('div').length).toEqual(1); 10 | }); 11 | 12 | it('should have a className attribute', () => { 13 | const renderedComponent = mount(); 14 | expect(renderedComponent.find('div').prop('className')).toBeDefined(); 15 | }); 16 | 17 | it('should not adopt attributes', () => { 18 | const id = 'test'; 19 | const renderedComponent = mount(); 20 | expect(renderedComponent.find('div').prop('id')).toBeUndefined(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /internals/templates/containers/HomePage/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * HomePage 3 | * 4 | * This is the first thing users see of our App, at the '/' route 5 | * 6 | * NOTE: while this component should technically be a stateless functional 7 | * component (SFC), hot reloading does not currently support SFCs. If hot 8 | * reloading is not a necessity for you then you can refactor it and remove 9 | * the linting exception. 10 | */ 11 | 12 | import React from 'react'; 13 | import { FormattedMessage } from 'react-intl'; 14 | import messages from './messages'; 15 | 16 | export default class HomePage extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function 17 | render() { 18 | return ( 19 |

20 | 21 |

22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/components/Footer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormattedMessage } from 'react-intl'; 3 | 4 | import A from 'components/A'; 5 | import LocaleToggle from 'containers/LocaleToggle'; 6 | import Wrapper from './Wrapper'; 7 | import messages from './messages'; 8 | 9 | function Footer() { 10 | return ( 11 | 12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 | Max Stoiber, 23 | }} 24 | /> 25 |
26 |
27 | ); 28 | } 29 | 30 | export default Footer; 31 | -------------------------------------------------------------------------------- /app/components/List/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Ul from './Ul'; 4 | import Wrapper from './Wrapper'; 5 | 6 | function List(props) { 7 | const ComponentToRender = props.component; 8 | let content = (
); 9 | 10 | // If we have items, render them 11 | if (props.items) { 12 | content = props.items.map((item, index) => ( 13 | 14 | )); 15 | } else { 16 | // Otherwise render a single component 17 | content = (); 18 | } 19 | 20 | return ( 21 | 22 |
    23 | {content} 24 |
25 |
26 | ); 27 | } 28 | 29 | List.propTypes = { 30 | component: React.PropTypes.func.isRequired, 31 | items: React.PropTypes.array, 32 | }; 33 | 34 | export default List; 35 | -------------------------------------------------------------------------------- /internals/templates/containers/NotFoundPage/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NotFoundPage 3 | * 4 | * This is the page we show when the user visits a url that doesn't have a route 5 | * 6 | * NOTE: while this component should technically be a stateless functional 7 | * component (SFC), hot reloading does not currently support SFCs. If hot 8 | * reloading is not a necessity for you then you can refactor it and remove 9 | * the linting exception. 10 | */ 11 | 12 | import React from 'react'; 13 | import { FormattedMessage } from 'react-intl'; 14 | 15 | import messages from './messages'; 16 | 17 | export default class NotFound extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function 18 | render() { 19 | return ( 20 |

21 | 22 |

23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/containers/HomePage/tests/selectors.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import { 4 | selectHome, 5 | makeSelectUsername, 6 | } from '../selectors'; 7 | 8 | describe('selectHome', () => { 9 | it('should select the home state', () => { 10 | const homeState = fromJS({ 11 | userData: {}, 12 | }); 13 | const mockedState = fromJS({ 14 | home: homeState, 15 | }); 16 | expect(selectHome(mockedState)).toEqual(homeState); 17 | }); 18 | }); 19 | 20 | describe('makeSelectUsername', () => { 21 | const usernameSelector = makeSelectUsername(); 22 | it('should select the username', () => { 23 | const username = 'mxstbr'; 24 | const mockedState = fromJS({ 25 | home: { 26 | username, 27 | }, 28 | }); 29 | expect(usernameSelector(mockedState)).toEqual(username); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /docs/general/gotchas.md: -------------------------------------------------------------------------------- 1 | # Gotchas 2 | 3 | These are some things to be aware of when using this boilerplate. 4 | 5 | ## Special images in HTML files 6 | 7 | If you specify your images in the `.html` files using the `` tag, everything 8 | will work fine. The problem comes up if you try to include images using anything 9 | except that tag, like meta tags: 10 | 11 | ```HTML 12 | 13 | ``` 14 | 15 | The webpack `html-loader` does not recognise this as an image file and will not 16 | transfer the image to the build folder. To get webpack to transfer them, you 17 | have to import them with the file loader in your JavaScript somewhere, e.g.: 18 | 19 | ```JavaScript 20 | import 'file?name=[name].[ext]!../img/yourimg.png'; 21 | ``` 22 | 23 | Then webpack will correctly transfer the image to the build folder. 24 | -------------------------------------------------------------------------------- /app/components/Toggle/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * LocaleToggle 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import Select from './Select'; 10 | import ToggleOption from '../ToggleOption'; 11 | 12 | function Toggle(props) { 13 | let content = (); 14 | 15 | // If we have items, render them 16 | if (props.values) { 17 | content = props.values.map((value) => ( 18 | 19 | )); 20 | } 21 | 22 | return ( 23 | 26 | ); 27 | } 28 | 29 | Toggle.propTypes = { 30 | onToggle: React.PropTypes.func, 31 | values: React.PropTypes.array, 32 | value: React.PropTypes.string, 33 | messages: React.PropTypes.object, 34 | }; 35 | 36 | export default Toggle; 37 | -------------------------------------------------------------------------------- /internals/templates/containers/App/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * App.react.js 4 | * 5 | * This component is the skeleton around the actual pages, and should only 6 | * contain code that should be seen on all pages. (e.g. navigation bar) 7 | * 8 | * NOTE: while this component should technically be a stateless functional 9 | * component (SFC), hot reloading does not currently support SFCs. If hot 10 | * reloading is not a necessity for you then you can refactor it and remove 11 | * the linting exception. 12 | */ 13 | 14 | import React from 'react'; 15 | 16 | export default class App extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function 17 | 18 | static propTypes = { 19 | children: React.PropTypes.node, 20 | }; 21 | 22 | render() { 23 | return ( 24 |
25 | {React.Children.toArray(this.props.children)} 26 |
27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /internals/generators/route/routeWithReducer.hbs: -------------------------------------------------------------------------------- 1 | { 2 | path: '{{ path }}', 3 | name: '{{ camelCase component }}', 4 | getComponent(nextState, cb) { 5 | const importModules = Promise.all([ 6 | import('containers/{{ properCase component }}/reducer'), 7 | {{#if useSagas}} 8 | import('containers/{{ properCase component }}/sagas'), 9 | {{/if}} 10 | import('containers/{{ properCase component }}'), 11 | ]); 12 | 13 | const renderRoute = loadModule(cb); 14 | 15 | importModules.then(([reducer,{{#if useSagas}} sagas,{{/if}} component]) => { 16 | injectReducer('{{ camelCase component }}', reducer.default); 17 | {{#if useSagas}} 18 | injectSagas(sagas.default); 19 | {{/if}} 20 | renderRoute(component); 21 | }); 22 | 23 | importModules.catch(errorLoading); 24 | }, 25 | },$1 26 | -------------------------------------------------------------------------------- /app/components/Button/tests/A.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import A from '../A'; 5 | 6 | describe('', () => { 7 | it('should render an tag', () => { 8 | const renderedComponent = shallow(); 9 | expect(renderedComponent.type()).toEqual('a'); 10 | }); 11 | 12 | it('should have a className attribute', () => { 13 | const renderedComponent = shallow(); 14 | expect(renderedComponent.prop('className')).toBeDefined(); 15 | }); 16 | 17 | it('should adopt a valid attribute', () => { 18 | const id = 'test'; 19 | const renderedComponent = shallow(); 20 | expect(renderedComponent.prop('id')).toEqual(id); 21 | }); 22 | 23 | it('should not adopt an invalid attribute', () => { 24 | const renderedComponent = shallow(); 25 | expect(renderedComponent.prop('attribute')).toBeUndefined(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /app/containers/App/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import Header from 'components/Header'; 5 | import Footer from 'components/Footer'; 6 | import { App } from '../index'; 7 | 8 | describe('', () => { 9 | it('should render the header', () => { 10 | const renderedComponent = shallow( 11 | 12 | ); 13 | expect(renderedComponent.find(Header).length).toBe(1); 14 | }); 15 | 16 | it('should render its children', () => { 17 | const children = (

Test

); 18 | const renderedComponent = shallow( 19 | 20 | {children} 21 | 22 | ); 23 | expect(renderedComponent.contains(children)).toBe(true); 24 | }); 25 | 26 | it('should render the footer', () => { 27 | const renderedComponent = shallow( 28 | 29 | ); 30 | expect(renderedComponent.find(Footer).length).toBe(1); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /app/containers/HomePage/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * HomeReducer 3 | * 4 | * The reducer takes care of our data. Using actions, we can change our 5 | * application state. 6 | * To add a new action, add it to the switch statement in the reducer function 7 | * 8 | * Example: 9 | * case YOUR_ACTION_CONSTANT: 10 | * return state.set('yourStateVariable', true); 11 | */ 12 | import { fromJS } from 'immutable'; 13 | 14 | import { 15 | CHANGE_USERNAME, 16 | } from './constants'; 17 | 18 | // The initial state of the App 19 | const initialState = fromJS({ 20 | username: '', 21 | }); 22 | 23 | function homeReducer(state = initialState, action) { 24 | switch (action.type) { 25 | case CHANGE_USERNAME: 26 | 27 | // Delete prefixed '@' from the github username 28 | return state 29 | .set('username', action.name.replace(/@/gi, '')); 30 | default: 31 | return state; 32 | } 33 | } 34 | 35 | export default homeReducer; 36 | -------------------------------------------------------------------------------- /docs/css/sass.md: -------------------------------------------------------------------------------- 1 | # Can I use Sass with this boilerplate? 2 | 3 | Yes, although we advise against it and **do not support this**. We selected 4 | [`styled-components`](https://github.com/styled-components/styled-components) 5 | over Sass because its approach is more powerful: instead of trying to 6 | give a styling language programmatic abilities, it pulls logic and configuration 7 | out into JS where we believe those features belong. 8 | 9 | If you _really_ still want (or need) to use Sass then... 10 | 11 | 1. You will need to add a [sass-loader](https://github.com/jtangelder/sass-loader) 12 | to the loaders section in `internals/webpack/webpack.base.babel.js` so it reads something like 13 | ```javascript 14 | { 15 | test: /\.scss$/, 16 | exclude: /node_modules/, 17 | loaders: ['style', 'css', 'sass'] 18 | } 19 | ``` 20 | 21 | Then run `npm i -D sass-loader node-sass` 22 | 23 | ...and you should be good to go! 24 | -------------------------------------------------------------------------------- /internals/scripts/analyze.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const shelljs = require('shelljs'); 4 | const animateProgress = require('./helpers/progress'); 5 | const chalk = require('chalk'); 6 | const addCheckMark = require('./helpers/checkmark'); 7 | 8 | const progress = animateProgress('Generating stats'); 9 | 10 | // Generate stats.json file with webpack 11 | shelljs.exec( 12 | 'webpack --config internals/webpack/webpack.prod.babel.js --profile --json > stats.json', 13 | addCheckMark.bind(null, callback) // Output a checkmark on completion 14 | ); 15 | 16 | // Called after webpack has finished generating the stats.json file 17 | function callback() { 18 | clearInterval(progress); 19 | process.stdout.write( 20 | '\n\nOpen ' + chalk.magenta('http://webpack.github.io/analyse/') + ' in your browser and upload the stats.json file!' + 21 | chalk.blue('\n(Tip: ' + chalk.italic('CMD + double-click') + ' the link!)\n\n') 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /app/components/List/tests/Ul.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import Ul from '../Ul'; 5 | 6 | describe('