├── internals ├── mocks │ └── image.js ├── testing │ └── test-bundler.js ├── webpack │ ├── webpack.dll.babel.js │ ├── webpack.prod.babel.js │ ├── webpack.base.babel.js │ └── webpack.dev.babel.js ├── scripts │ └── dependencies.js └── config.js ├── .coveralls.yml ├── app ├── translations │ ├── en.js │ ├── de.js │ ├── en.json │ └── de.json ├── store │ ├── nav │ │ ├── constants.js │ │ ├── actions.js │ │ ├── selectors.js │ │ └── reducer.js │ ├── language │ │ ├── constants.js │ │ ├── actions.js │ │ ├── selectors.test.js │ │ ├── selectors.js │ │ ├── actions.test.js │ │ ├── reducer.test.js │ │ └── reducer.js │ └── auth │ │ ├── actions.js │ │ ├── selectors.js │ │ ├── reducer.js │ │ └── constants.js ├── pages │ ├── FeatureList │ │ ├── Feature │ │ │ ├── styles.scss │ │ │ ├── index.jsx │ │ │ ├── index.story.js │ │ │ └── index.test.js │ │ ├── styles.scss │ │ ├── index.story.js │ │ ├── index.spec.js │ │ ├── index.jsx │ │ ├── index.test.js │ │ ├── __snapshots__ │ │ │ └── index.spec.js.snap │ │ └── messages.js │ ├── SignIn │ │ ├── messages.js │ │ ├── index.jsx │ │ └── index.test.js │ ├── SignUp │ │ ├── messages.js │ │ ├── index.jsx │ │ └── index.test.js │ ├── Home │ │ ├── RepositoriesList │ │ │ ├── Repository │ │ │ │ ├── styles.scss │ │ │ │ ├── RepositoryLink │ │ │ │ │ └── index.jsx │ │ │ │ ├── IssuesLink │ │ │ │ │ └── index.jsx │ │ │ │ ├── IssueIcon │ │ │ │ │ ├── index.jsx │ │ │ │ │ └── index.test.js │ │ │ │ └── index.jsx │ │ │ ├── messages.js │ │ │ ├── styles.scss │ │ │ └── index.jsx │ │ ├── styles.scss │ │ ├── messages.js │ │ └── index.jsx │ ├── SignOut │ │ ├── messages.js │ │ ├── index.story.js │ │ ├── index.jsx │ │ └── index.test.js │ └── 404-NotFound │ │ ├── messages.js │ │ ├── index.jsx │ │ └── index.test.js ├── images │ ├── favicon.ico │ ├── icon-72x72.png │ ├── icon-96x96.png │ ├── icon-120x120.png │ ├── icon-128x128.png │ ├── icon-144x144.png │ ├── icon-152x152.png │ ├── icon-167x167.png │ ├── icon-180x180.png │ ├── icon-192x192.png │ ├── icon-384x384.png │ └── icon-512x512.png ├── atoms │ ├── H2 │ │ ├── index.jsx │ │ └── index.test.js │ ├── H1 │ │ ├── index.jsx │ │ └── index.test.js │ ├── Error │ │ ├── index.jsx │ │ └── index.test.js │ ├── A │ │ ├── index.jsx │ │ └── index.test.js │ ├── List │ │ ├── index.story.js │ │ ├── index.jsx │ │ └── index.test.js │ └── Img │ │ ├── index.jsx │ │ └── index.test.js ├── molecules │ ├── Helmet │ │ ├── index.js │ │ ├── TitleIntl │ │ │ ├── index.jsx │ │ │ ├── Title │ │ │ │ ├── index.jsx │ │ │ │ └── index.test.js │ │ │ └── index.test.js │ │ └── MetaIntl │ │ │ ├── Meta │ │ │ ├── index.jsx │ │ │ └── index.test.js │ │ │ ├── index.jsx │ │ │ └── index.test.js │ ├── Dropdown │ │ ├── Option │ │ │ ├── index.jsx │ │ │ └── index.test.js │ │ ├── index.jsx │ │ └── index.test.js │ └── Page │ │ └── index.jsx ├── utils │ ├── store │ │ └── index.js │ ├── authenticated │ │ └── index.js │ ├── yup │ │ └── index.js │ ├── test │ │ └── index.js │ ├── auth │ │ ├── index.js │ │ └── index.test.js │ ├── form │ │ └── index.js │ └── request │ │ ├── index.js │ │ ├── base │ │ ├── index.js │ │ └── index.test.js │ │ └── index.test.js ├── reducers.js ├── organisms │ ├── App │ │ ├── defaultTheme │ │ │ └── index.js │ │ ├── global-styles.js │ │ ├── AuthorizedRoute │ │ │ ├── index.jsx │ │ │ └── index.test.js │ │ ├── UnauthorizedRoute │ │ │ ├── index.jsx │ │ │ └── index.test.js │ │ ├── index.jsx │ │ └── index.test.js │ ├── Footer │ │ ├── messages.js │ │ ├── index.jsx │ │ └── index.test.js │ ├── LocaleSwitcher │ │ ├── messages.js │ │ ├── index.test.js │ │ └── index.jsx │ ├── Header │ │ ├── LinksBlock │ │ │ ├── messages.js │ │ │ └── index.jsx │ │ ├── index.story.js │ │ └── index.jsx │ ├── LanguageProvider │ │ ├── index.jsx │ │ └── index.test.js │ ├── SignInForm │ │ ├── index.story.js │ │ ├── messages.js │ │ └── index.jsx │ └── SignUpForm │ │ ├── index.story.js │ │ ├── messages.js │ │ └── index.jsx ├── i18n │ └── index.js ├── templates │ └── default │ │ └── index.jsx ├── manifest.json ├── index.html ├── .htaccess ├── .nginx.conf └── app.js ├── .env ├── server ├── argv.js ├── port.js ├── middlewares │ ├── frontendMiddleware.js │ ├── addProdMiddlewares.js │ └── addDevMiddlewares.js ├── logger.js └── index.js ├── postcss.config.js ├── .gitignore ├── .storybook ├── webpack.config.js └── config.js ├── .editorconfig ├── .travis.yml ├── .babelrc ├── LICENSE.md ├── jest.config.js ├── .gitattributes ├── .eslintrc ├── README.md └── package.json /internals/mocks/image.js: -------------------------------------------------------------------------------- 1 | module.exports = 'IMAGE_MOCK'; 2 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: rfFLCXbO6A1d8ndBOBjHFwySsLtsLRxNS 2 | -------------------------------------------------------------------------------- /app/translations/en.js: -------------------------------------------------------------------------------- 1 | export default from 'yup/lib/locale'; 2 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | APP_ENV_API_BASE_URL = "http://localhost:8000" 2 | APP_ENV_BASE_PATH = "/" 3 | -------------------------------------------------------------------------------- /server/argv.js: -------------------------------------------------------------------------------- 1 | module.exports = require('minimist')(process.argv.slice(2)); 2 | -------------------------------------------------------------------------------- /app/store/nav/constants.js: -------------------------------------------------------------------------------- 1 | export const SET_PAGE_TITLE = 'actions/nav/SET_PAGE_TITLE'; 2 | -------------------------------------------------------------------------------- /app/pages/FeatureList/Feature/styles.scss: -------------------------------------------------------------------------------- 1 | .feature-header { 2 | font-weight: bold; 3 | } 4 | -------------------------------------------------------------------------------- /app/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-elgin/atomic-react-redux/HEAD/app/images/favicon.ico -------------------------------------------------------------------------------- /app/images/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-elgin/atomic-react-redux/HEAD/app/images/icon-72x72.png -------------------------------------------------------------------------------- /app/images/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-elgin/atomic-react-redux/HEAD/app/images/icon-96x96.png -------------------------------------------------------------------------------- /app/images/icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-elgin/atomic-react-redux/HEAD/app/images/icon-120x120.png -------------------------------------------------------------------------------- /app/images/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-elgin/atomic-react-redux/HEAD/app/images/icon-128x128.png -------------------------------------------------------------------------------- /app/images/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-elgin/atomic-react-redux/HEAD/app/images/icon-144x144.png -------------------------------------------------------------------------------- /app/images/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-elgin/atomic-react-redux/HEAD/app/images/icon-152x152.png -------------------------------------------------------------------------------- /app/images/icon-167x167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-elgin/atomic-react-redux/HEAD/app/images/icon-167x167.png -------------------------------------------------------------------------------- /app/images/icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-elgin/atomic-react-redux/HEAD/app/images/icon-180x180.png -------------------------------------------------------------------------------- /app/images/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-elgin/atomic-react-redux/HEAD/app/images/icon-192x192.png -------------------------------------------------------------------------------- /app/images/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-elgin/atomic-react-redux/HEAD/app/images/icon-384x384.png -------------------------------------------------------------------------------- /app/images/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexander-elgin/atomic-react-redux/HEAD/app/images/icon-512x512.png -------------------------------------------------------------------------------- /internals/testing/test-bundler.js: -------------------------------------------------------------------------------- 1 | // needed for regenerator-runtime 2 | // (ES7 generator support) 3 | import 'babel-polyfill'; 4 | -------------------------------------------------------------------------------- /server/port.js: -------------------------------------------------------------------------------- 1 | const argv = require('./argv'); 2 | 3 | module.exports = parseInt(argv.port || process.env.PORT || '3000', 10); 4 | -------------------------------------------------------------------------------- /app/store/language/constants.js: -------------------------------------------------------------------------------- 1 | export const CHANGE_LOCALE = 'app/LanguageToggle/CHANGE_LOCALE'; 2 | export const DEFAULT_LOCALE = 'en'; 3 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | precss: {}, 4 | autoprefixer: {}, 5 | cssnano: {}, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /app/atoms/H2/index.jsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const H2 = styled.h2` 4 | font-size: 1.5em; 5 | `; 6 | 7 | export default H2; 8 | -------------------------------------------------------------------------------- /app/molecules/Helmet/index.js: -------------------------------------------------------------------------------- 1 | import TitleIntl from './TitleIntl'; 2 | import MetaIntl from './MetaIntl'; 3 | 4 | export { 5 | MetaIntl, 6 | TitleIntl, 7 | }; 8 | -------------------------------------------------------------------------------- /app/atoms/H1/index.jsx: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't check auto-generated stuff into git 2 | coverage 3 | build 4 | node_modules 5 | 6 | # Cruft 7 | .DS_Store 8 | npm-debug.log 9 | yarn-error.log 10 | .idea 11 | -------------------------------------------------------------------------------- /app/pages/FeatureList/styles.scss: -------------------------------------------------------------------------------- 1 | .list { 2 | font-family: Georgia, Times, 'Times New Roman', serif; 3 | padding-left: 1.75em; 4 | 5 | li { 6 | margin: 1em 0; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /app/atoms/Error/index.jsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Error = styled.div` 4 | color: rgb(244, 67, 54); 5 | font-size: 12px; 6 | `; 7 | 8 | export default Error; 9 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const config = require('../internals/webpack/webpack.base.babel')({plugins: []}); 2 | 3 | module.exports = { 4 | module: { 5 | rules: config.module.rules, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /app/atoms/A/index.jsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const A = styled.a` 4 | color: #41addd; 5 | 6 | &:hover { 7 | color: #6cc0e5; 8 | } 9 | `; 10 | 11 | export default A; 12 | -------------------------------------------------------------------------------- /app/store/nav/actions.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_PAGE_TITLE, 3 | } from './constants'; 4 | 5 | export function setPageTitle(payload) { 6 | return { 7 | type: SET_PAGE_TITLE, 8 | payload, 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /app/pages/SignIn/messages.js: -------------------------------------------------------------------------------- 1 | import { defineMessages } from 'react-intl'; 2 | 3 | export default defineMessages({ 4 | metaTitle: { 5 | id: 'boilerplate.shared.Auth.signIn', 6 | defaultMessage: 'Sign in', 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /app/pages/SignUp/messages.js: -------------------------------------------------------------------------------- 1 | import { defineMessages } from 'react-intl'; 2 | 3 | export default defineMessages({ 4 | metaTitle: { 5 | id: 'boilerplate.shared.Auth.signUp', 6 | defaultMessage: 'Sign up', 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /app/atoms/List/index.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | 4 | import List from './'; 5 | 6 | storiesOf('List', module) 7 | .add('default', () => ) 8 | ; 9 | -------------------------------------------------------------------------------- /app/utils/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | import { fromJS } from 'immutable'; 3 | import { combineReducers } from 'redux-immutable'; 4 | 5 | export default (reducers, initialState = {}) => createStore(combineReducers(reducers), fromJS(initialState)); 6 | -------------------------------------------------------------------------------- /.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/reducers.js: -------------------------------------------------------------------------------- 1 | const context = require.context('./store', true, /reducer\.js$/); 2 | 3 | export default context.keys().reduce((reducers, fileRelativePath) => Object.assign(reducers, { 4 | [fileRelativePath.replace('./', '').replace('/reducer.js', '')]: context(fileRelativePath).default, 5 | }), {}); 6 | -------------------------------------------------------------------------------- /app/store/language/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/pages/Home/RepositoriesList/Repository/styles.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100%; 3 | height: 100%; 4 | display: flex; 5 | justify-content: space-between; 6 | align-items: center; 7 | max-height: 30em; 8 | overflow-y: auto; 9 | } 10 | 11 | .icon { 12 | fill: #ccc; 13 | margin-right: 0.25em; 14 | } 15 | -------------------------------------------------------------------------------- /app/store/auth/actions.js: -------------------------------------------------------------------------------- 1 | import { 2 | SIGN_IN_SUCCESS, 3 | SIGN_OUT, 4 | } from './constants'; 5 | 6 | export function setSignInData(user) { 7 | return { 8 | type: SIGN_IN_SUCCESS, 9 | user, 10 | }; 11 | } 12 | 13 | export function signOut() { 14 | return { 15 | type: SIGN_OUT, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /app/store/nav/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | const selectNav = (state) => state.get('nav'); 4 | 5 | const makeSelectPageTitle = () => createSelector( 6 | selectNav, 7 | (globalState) => globalState.get('pageTitle') 8 | ); 9 | 10 | export { 11 | selectNav, 12 | makeSelectPageTitle, 13 | }; 14 | -------------------------------------------------------------------------------- /app/organisms/App/defaultTheme/index.js: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from '@material-ui/core/styles'; 2 | 3 | const defaultTheme = createMuiTheme({ 4 | typography: { 5 | useNextVariants: true, 6 | }, 7 | palette: { 8 | primary: { 9 | 500: '#41ADDD', 10 | }, 11 | }, 12 | }); 13 | 14 | export default defaultTheme; 15 | -------------------------------------------------------------------------------- /app/atoms/List/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const List = ({ items, ...rest }) => ( 5 | 8 | ); 9 | 10 | List.propTypes = { 11 | items: PropTypes.array.isRequired, 12 | }; 13 | 14 | export default List; 15 | -------------------------------------------------------------------------------- /app/molecules/Helmet/TitleIntl/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormattedMessage } from 'react-intl'; 3 | 4 | import Title from './Title'; 5 | 6 | const TitleIntl = (props) => ( 7 | 8 | {(title) => } 9 | </FormattedMessage> 10 | ); 11 | 12 | export default TitleIntl; 13 | -------------------------------------------------------------------------------- /app/pages/Home/RepositoriesList/Repository/RepositoryLink/index.jsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import NormalA from '../../../../../atoms/A'; 3 | 4 | const RepoLink = styled(NormalA)` 5 | height: 100%; 6 | color: black; 7 | display: flex; 8 | align-items: center; 9 | width: 100%; 10 | `; 11 | 12 | export default RepoLink; 13 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import { configure } from '@storybook/react'; 3 | 4 | function requireAll(requireContext) { 5 | return requireContext.keys().map(requireContext); 6 | } 7 | 8 | function loadStories() { 9 | requireAll(require.context('../app', true, /(story|stories).jsx?$/)); 10 | } 11 | 12 | configure(loadStories, module); 13 | -------------------------------------------------------------------------------- /app/pages/SignOut/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 | signOutMessage: { 10 | id: 'boilerplate.pages.SignOut.signOutMessage', 11 | defaultMessage: 'Signing out', 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /app/pages/404-NotFound/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.pages.NotFound.header', 11 | defaultMessage: 'Page not found.', 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /app/molecules/Helmet/TitleIntl/Title/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Helmet } from 'react-helmet'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const Title = ({ content }) => ( 6 | <Helmet> 7 | <title>{ content } 8 | 9 | ); 10 | 11 | Title.propTypes = { 12 | content: PropTypes.string, 13 | }; 14 | 15 | export default Title; 16 | -------------------------------------------------------------------------------- /app/pages/SignOut/index.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | 4 | import { IntlProvider } from 'react-intl'; 5 | 6 | import SignOut from './'; 7 | 8 | storiesOf('Sign Out', module) 9 | .add('default', () => ( 10 | 11 | {}} /> 12 | 13 | )) 14 | ; 15 | -------------------------------------------------------------------------------- /app/pages/Home/RepositoriesList/Repository/IssuesLink/index.jsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import NormalA from '../../../../../atoms/A'; 3 | 4 | const IssuesLink = styled(NormalA)` 5 | height: 100%; 6 | color: black; 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | white-space: nowrap; 11 | `; 12 | 13 | export default IssuesLink; 14 | -------------------------------------------------------------------------------- /app/molecules/Helmet/MetaIntl/Meta/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Helmet } from 'react-helmet'; 4 | 5 | const Meta = ({ content, name }) => ( 6 | 7 | 8 | 9 | ); 10 | 11 | Meta.propTypes = { 12 | content: PropTypes.string, 13 | name: PropTypes.string, 14 | }; 15 | 16 | export default Meta; 17 | -------------------------------------------------------------------------------- /app/pages/Home/RepositoriesList/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 | somethingWrong: { 10 | id: 'boilerplate.pages.Home.RepositoriesList.somethingWrong', 11 | defaultMessage: 'Something went wrong, please try again!', 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /app/pages/FeatureList/index.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | 4 | import { IntlProvider } from 'react-intl'; 5 | 6 | import FeatureListPage from './'; 7 | 8 | storiesOf('FeatureListPage', module) 9 | .addDecorator((story) => ( 10 | 11 | {story()} 12 | 13 | )) 14 | .add('default', () => ) 15 | ; 16 | -------------------------------------------------------------------------------- /app/pages/Home/styles.scss: -------------------------------------------------------------------------------- 1 | .form { 2 | margin-bottom: 1em; 3 | } 4 | 5 | .input { 6 | background: transparent; 7 | border: none; 8 | outline: none; 9 | border-bottom: 1px dotted #999; 10 | margin-left: 0.4em; 11 | } 12 | 13 | .section { 14 | margin: 3em auto; 15 | 16 | &:first-child { 17 | margin-top: 0; 18 | } 19 | } 20 | 21 | .centered-section { 22 | composes: section; 23 | text-align: center; 24 | } 25 | -------------------------------------------------------------------------------- /app/store/language/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/pages/SignIn/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Page from '../../molecules/Page'; 4 | import SignInForm from '../../organisms/SignInForm'; 5 | import messages from './messages'; 6 | 7 | const SignInPage = () => ( 8 | 9 |
10 | 11 |
12 |
13 | ); 14 | 15 | export default SignInPage; 16 | -------------------------------------------------------------------------------- /app/pages/SignUp/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Page from '../../molecules/Page'; 4 | import SignUpForm from '../../organisms/SignUpForm'; 5 | import messages from './messages'; 6 | 7 | const SignUpPage = () => ( 8 | 9 |
10 | 11 |
12 |
13 | ); 14 | 15 | export default SignUpPage; 16 | -------------------------------------------------------------------------------- /app/organisms/Footer/messages.js: -------------------------------------------------------------------------------- 1 | import { defineMessages } from 'react-intl'; 2 | 3 | export default defineMessages({ 4 | licenseMessage: { 5 | id: 'boilerplate.organisms.Footer.license.message', 6 | defaultMessage: 'This project is licensed under the MIT license.', 7 | }, 8 | authorMessage: { 9 | id: 'boilerplate.organisms.Footer.author.message', 10 | defaultMessage: ` 11 | Made with love by {author}. 12 | `, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /app/utils/authenticated/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { createStructuredSelector } from 'reselect'; 3 | 4 | import { makeSelectIsAuthenticated } from '../../store/auth/selectors'; 5 | 6 | const mapStateToProps = createStructuredSelector({ 7 | authenticated: makeSelectIsAuthenticated(), 8 | }); 9 | 10 | const setAuthenticatedProp = (Component) => connect(mapStateToProps, null)(Component); 11 | export default setAuthenticatedProp; 12 | -------------------------------------------------------------------------------- /app/organisms/LocaleSwitcher/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.organisms.LocaleSwitcher.en', 11 | defaultMessage: 'en', 12 | }, 13 | de: { 14 | id: 'boilerplate.organisms.LocaleSwitcher.de', 15 | defaultMessage: 'de', 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /app/molecules/Helmet/MetaIntl/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { FormattedMessage } from 'react-intl'; 4 | 5 | import Meta from './Meta'; 6 | 7 | const MetaIntl = ({ name, ...rest }) => ( 8 | 9 | {(content) => } 10 | 11 | ); 12 | 13 | MetaIntl.propTypes = { 14 | name: PropTypes.string, 15 | }; 16 | 17 | export default MetaIntl; 18 | -------------------------------------------------------------------------------- /app/store/language/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/i18n/index.js: -------------------------------------------------------------------------------- 1 | import { addLocaleData } from 'react-intl'; 2 | 3 | const context = require.context('../translations', true, /\.json$/); 4 | 5 | export default context.keys().reduce((acc, fileRelativePath) => { 6 | const langIso2Code = fileRelativePath.replace('./', '').replace('.json', ''); 7 | addLocaleData(require(`react-intl/locale-data/${langIso2Code}`)); // eslint-disable-line global-require 8 | acc[langIso2Code] = context(fileRelativePath); 9 | return acc; 10 | }, {}); 11 | -------------------------------------------------------------------------------- /app/store/auth/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | const selectAuth = (state) => state.get('auth'); 4 | 5 | const makeSelectUser = () => createSelector( 6 | selectAuth, 7 | (authState) => authState.get('user') 8 | ); 9 | 10 | const makeSelectIsAuthenticated = () => createSelector( 11 | makeSelectUser(), 12 | (userState) => userState !== null 13 | ); 14 | 15 | export { 16 | selectAuth, 17 | makeSelectUser, 18 | makeSelectIsAuthenticated, 19 | }; 20 | -------------------------------------------------------------------------------- /app/store/nav/reducer.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import { 4 | SET_PAGE_TITLE, 5 | } from './constants'; 6 | 7 | const initialState = fromJS({ 8 | pageTitle: '', 9 | }); 10 | 11 | function navReducer(state = initialState, action = {}) { 12 | switch (action.type) { 13 | case SET_PAGE_TITLE: 14 | return state 15 | .set('pageTitle', action.payload); 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export default navReducer; 22 | -------------------------------------------------------------------------------- /app/store/language/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 | -------------------------------------------------------------------------------- /app/molecules/Dropdown/Option/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { injectIntl, intlShape } from 'react-intl'; 4 | 5 | const Option = ({ value, message, intl }) => ( 6 | 9 | ); 10 | 11 | Option.propTypes = { 12 | value: PropTypes.string.isRequired, 13 | message: PropTypes.object, 14 | intl: intlShape.isRequired, 15 | }; 16 | 17 | export default injectIntl(Option); 18 | -------------------------------------------------------------------------------- /app/pages/FeatureList/index.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | 4 | import { IntlProvider } from 'react-intl'; 5 | 6 | import FeatureListPage from './'; 7 | 8 | describe('', () => { 9 | it('renders the Features List page', () => { 10 | expect( 11 | renderer.create( 12 | 13 | 14 | 15 | ).toJSON() 16 | ).toMatchSnapshot(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /app/store/auth/reducer.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import { 4 | SIGN_IN_SUCCESS, 5 | SIGN_OUT, 6 | } from './constants'; 7 | 8 | const initialState = fromJS({ 9 | user: null, 10 | }); 11 | 12 | function reducer(state = initialState, action = {}) { 13 | switch (action.type) { 14 | case SIGN_IN_SUCCESS: 15 | return state.set('user', action.user); 16 | case SIGN_OUT: 17 | return state.set('user', null); 18 | default: 19 | return state; 20 | } 21 | } 22 | 23 | export default reducer; 24 | -------------------------------------------------------------------------------- /app/atoms/Error/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Enzyme, { shallow } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | 5 | import Error from './'; 6 | 7 | Enzyme.configure({ adapter: new Adapter() }); 8 | 9 | const children = (

Test

); 10 | const renderComponent = (props = {}) => shallow( 11 | 12 | {children} 13 | 14 | ); 15 | 16 | describe('', () => { 17 | it('renders children', () => expect(renderComponent().contains(children)).toBe(true)); 18 | }); 19 | -------------------------------------------------------------------------------- /app/store/auth/constants.js: -------------------------------------------------------------------------------- 1 | export const SIGN_IN_SUCCESS = 'actions/auth/SIGN_IN_SUCCESS'; 2 | export const SIGN_OUT = 'actions/auth/SIGN_OUT'; 3 | 4 | export const ERRORS_PREFIX = 'boilerplate.shared.Auth.errors'; 5 | 6 | export const DUPLICATED_EMAIL = 'DUPLICATED_EMAIL'; 7 | export const EMPTY_PASSWORD = 'EMPTY_PASSWORD'; 8 | export const INCORRECT_CREDENTIALS = 'INCORRECT_CREDENTIALS'; 9 | export const INVALID_EMAIL = 'INVALID_EMAIL'; 10 | export const INVALID_NAME = 'INVALID_NAME'; 11 | export const INVALID_PASSWORD = 'INVALID_PASSWORD'; 12 | -------------------------------------------------------------------------------- /app/atoms/A/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Enzyme, { shallow } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | 5 | import A from './'; 6 | 7 | Enzyme.configure({ adapter: new Adapter() }); 8 | 9 | const children = (

Test

); 10 | const renderComponent = (props = {}) => shallow( 11 | 12 | {children} 13 | 14 | ); 15 | 16 | describe('', () => { 17 | it('renders children', () => expect(renderComponent().contains(children)).toEqual(true)); 18 | }); 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | dist: trusty 3 | node_js: 4 | - "8.9.1" 5 | sudo: true 6 | git: 7 | depth: 1 8 | 9 | before_install: 10 | - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.9.4 11 | - export PATH="$HOME/.yarn/bin:$PATH" 12 | - yarn cache clean 13 | - yarn 14 | branches: 15 | only: 16 | - develop 17 | jobs: 18 | include: 19 | - stage: test 20 | script: 21 | - yarn test 22 | after_success: 23 | - yarn coveralls 24 | - stage: build 25 | script: 26 | - yarn build 27 | -------------------------------------------------------------------------------- /app/pages/SignIn/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Enzyme, { shallow } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | 5 | import SignInForm from '../../organisms/SignInForm'; 6 | 7 | import SignInPage from './'; 8 | 9 | Enzyme.configure({ adapter: new Adapter() }); 10 | 11 | describe('', () => { 12 | let component; 13 | 14 | beforeEach(() => { 15 | component = shallow( 16 | 17 | ); 18 | }); 19 | 20 | it('renders the form', () => expect(component.type()).toEqual(SignInForm)); 21 | }); 22 | -------------------------------------------------------------------------------- /app/pages/SignUp/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Enzyme, { shallow } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | 5 | import SignUpForm from '../../organisms/SignUpForm'; 6 | 7 | import SignUpPage from './'; 8 | 9 | Enzyme.configure({ adapter: new Adapter() }); 10 | 11 | describe('', () => { 12 | let component; 13 | 14 | beforeEach(() => { 15 | component = shallow( 16 | 17 | ); 18 | }); 19 | 20 | it('renders the form', () => expect(component.type()).toEqual(SignUpForm)); 21 | }); 22 | -------------------------------------------------------------------------------- /app/pages/404-NotFound/index.jsx: -------------------------------------------------------------------------------- 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 '../../atoms/H1'; 11 | import Page from '../../molecules/Page'; 12 | import messages from './messages'; 13 | 14 | export default function NotFound() { 15 | return ( 16 | 17 |

18 | 19 |

20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/utils/yup/index.js: -------------------------------------------------------------------------------- 1 | import { setLocale } from 'yup'; 2 | 3 | const context = require.context('../../translations', true, /\.js$/); 4 | 5 | const serializedLocales = JSON.stringify(context.keys().reduce((locales, fileRelativePath) => Object.assign(locales, { 6 | [fileRelativePath.replace('./', '').replace('.js', '')]: context(fileRelativePath).default, 7 | }), {})); 8 | 9 | // setLocale modifies the argument. That's the only way I found to fix the issue 10 | const setYupLocale = (langIso2Code) => setLocale(JSON.parse(serializedLocales)[langIso2Code]); 11 | export default setYupLocale; 12 | -------------------------------------------------------------------------------- /app/store/language/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()).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 | -------------------------------------------------------------------------------- /app/utils/test/index.js: -------------------------------------------------------------------------------- 1 | import { FormattedMessage, IntlProvider } from 'react-intl'; 2 | 3 | export function checkTranslation(message, id, defaultText) { 4 | expect(message.type()).toEqual(FormattedMessage); 5 | expect(message.prop('id')).toBe(id); 6 | expect(message.prop('defaultMessage')).toBe(defaultText); 7 | } 8 | 9 | export function getIntl(translate = (messageData) => messageData.defaultMessage) { 10 | const intlProvider = new IntlProvider({ locale: 'en', messages: {} }, {}); 11 | const { intl } = intlProvider.getChildContext(); 12 | intl.formatMessage = translate; 13 | return intl; 14 | } 15 | -------------------------------------------------------------------------------- /app/store/language/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider reducer 4 | * 5 | */ 6 | 7 | import { fromJS } from 'immutable'; 8 | 9 | import { 10 | CHANGE_LOCALE, 11 | DEFAULT_LOCALE, 12 | } from './constants'; 13 | 14 | const initialState = fromJS({ 15 | locale: DEFAULT_LOCALE, 16 | }); 17 | 18 | function languageProviderReducer(state = initialState, action = {}) { 19 | switch (action.type) { 20 | case CHANGE_LOCALE: 21 | return state 22 | .set('locale', action.locale); 23 | default: 24 | return state; 25 | } 26 | } 27 | 28 | export default languageProviderReducer; 29 | -------------------------------------------------------------------------------- /server/middlewares/frontendMiddleware.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | 3 | /** 4 | * Front-end middleware 5 | */ 6 | module.exports = (app, options) => { 7 | const isProd = process.env.NODE_ENV === 'production'; 8 | 9 | if (isProd) { 10 | const addProdMiddlewares = require('./addProdMiddlewares'); 11 | addProdMiddlewares(app, options); 12 | } else { 13 | const webpackConfig = require('../../internals/webpack/webpack.dev.babel'); 14 | const addDevMiddlewares = require('./addDevMiddlewares'); 15 | addDevMiddlewares(app, webpackConfig); 16 | } 17 | 18 | return app; 19 | }; 20 | -------------------------------------------------------------------------------- /app/pages/Home/RepositoriesList/Repository/IssueIcon/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | function IssueIcon(props) { 5 | return ( 6 | 11 | 12 | 13 | ); 14 | } 15 | 16 | IssueIcon.propTypes = { 17 | className: PropTypes.string, 18 | }; 19 | 20 | export default IssueIcon; 21 | -------------------------------------------------------------------------------- /app/pages/FeatureList/Feature/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { FormattedMessage } from 'react-intl'; 4 | 5 | import styles from './styles.scss'; 6 | 7 | const Feature = ({ description, header }) => ( 8 |
9 |

10 |

11 |
12 | ); 13 | 14 | Feature.propTypes = { 15 | description: PropTypes.object.isRequired, 16 | header: PropTypes.object.isRequired, 17 | }; 18 | 19 | export default Feature; 20 | -------------------------------------------------------------------------------- /app/atoms/Img/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Img.js 4 | * 5 | * Renders an image, enforcing the usage of the alt="" tag 6 | */ 7 | 8 | import React from 'react'; 9 | import PropTypes from 'prop-types'; 10 | 11 | function Img(props) { 12 | return ( 13 | {props.alt} 14 | ); 15 | } 16 | 17 | // We require the use of src and alt, only enforced by react in dev mode 18 | Img.propTypes = { 19 | src: PropTypes.oneOfType([ 20 | PropTypes.string, 21 | PropTypes.object, 22 | ]).isRequired, 23 | alt: PropTypes.string.isRequired, 24 | className: PropTypes.string, 25 | }; 26 | 27 | export default Img; 28 | -------------------------------------------------------------------------------- /app/organisms/App/global-styles.js: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from 'styled-components'; 2 | 3 | export default createGlobalStyle` 4 | html, 5 | body { 6 | height: 100%; 7 | width: 100%; 8 | } 9 | 10 | body { 11 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 12 | } 13 | 14 | body.fontLoaded { 15 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 16 | } 17 | 18 | #app { 19 | background-color: #fafafa; 20 | min-height: 100%; 21 | min-width: 100%; 22 | } 23 | 24 | p, 25 | label { 26 | font-family: Georgia, Times, 'Times New Roman', serif; 27 | line-height: 1.5em; 28 | } 29 | `; 30 | -------------------------------------------------------------------------------- /app/organisms/Header/LinksBlock/messages.js: -------------------------------------------------------------------------------- 1 | import { defineMessages } from 'react-intl'; 2 | 3 | export default defineMessages({ 4 | features: { 5 | id: 'boilerplate.organisms.Header.features', 6 | defaultMessage: 'Features', 7 | }, 8 | home: { 9 | id: 'boilerplate.organisms.Header.home', 10 | defaultMessage: 'Home', 11 | }, 12 | signIn: { 13 | id: 'boilerplate.shared.Auth.signIn', 14 | defaultMessage: 'Sign in', 15 | }, 16 | signOut: { 17 | id: 'boilerplate.shared.Auth.signOut', 18 | defaultMessage: 'Sign out', 19 | }, 20 | signUp: { 21 | id: 'boilerplate.shared.Auth.signUp', 22 | defaultMessage: 'Sign up', 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /app/pages/Home/RepositoriesList/styles.scss: -------------------------------------------------------------------------------- 1 | .centered { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | } 6 | 7 | .list { 8 | list-style: none; 9 | margin: 0; 10 | width: 100%; 11 | padding: 0 1em; 12 | 13 | li { 14 | width: 100%; 15 | height: 3em; 16 | display: flex; 17 | align-items: center; 18 | position: relative; 19 | border-top: 1px solid #eee; 20 | 21 | &:first-child { 22 | border-top: none; 23 | } 24 | } 25 | } 26 | 27 | .wrapper { 28 | padding: 0; 29 | margin: 0; 30 | width: 100%; 31 | background-color: white; 32 | border: 1px solid #ccc; 33 | border-radius: 3px; 34 | overflow: hidden; 35 | } 36 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "styled-components", 4 | "transform-decorators-legacy" 5 | ], 6 | "presets": [ 7 | [ 8 | "env", 9 | { 10 | "modules": false 11 | } 12 | ], 13 | "react", 14 | "stage-0" 15 | ], 16 | "env": { 17 | "production": { 18 | "only": [ 19 | "app" 20 | ], 21 | "plugins": [ 22 | "transform-react-remove-prop-types", 23 | "transform-react-constant-elements", 24 | "transform-react-inline-elements" 25 | ] 26 | }, 27 | "test": { 28 | "plugins": [ 29 | "transform-es2015-modules-commonjs", 30 | "dynamic-import-node" 31 | ] 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/atoms/H1/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Enzyme, { shallow } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | 5 | import H1 from './'; 6 | 7 | Enzyme.configure({ adapter: new Adapter() }); 8 | 9 | describe('

', () => { 10 | it('should render a prop', () => { 11 | const id = 'testId'; 12 | const renderedComponent = shallow( 13 |

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

{children}

22 | ); 23 | expect(renderedComponent.contains(children)).toBe(true); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /app/atoms/H2/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Enzyme, { shallow } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | 5 | import H2 from './'; 6 | 7 | Enzyme.configure({ adapter: new Adapter() }); 8 | 9 | describe('

', () => { 10 | it('should render a prop', () => { 11 | const id = 'testId'; 12 | const renderedComponent = shallow( 13 |

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

{children}

22 | ); 23 | expect(renderedComponent.contains(children)).toBe(true); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /app/pages/SignOut/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useDispatch } from 'react-redux'; 3 | import { FormattedMessage } from 'react-intl'; 4 | import { useHistory } from 'react-router-dom'; 5 | 6 | import { signOut } from '../../store/auth/actions'; 7 | import { removeToken } from '../../utils/auth'; 8 | 9 | import messages from './messages'; 10 | 11 | const SignOut = () => { 12 | const dispatch = useDispatch(); 13 | const { push } = useHistory(); 14 | 15 | useEffect(() => { 16 | dispatch(signOut()); 17 | removeToken(); 18 | push('/'); 19 | }, []); 20 | 21 | return ( 22 |

...

23 | ); 24 | }; 25 | 26 | export default SignOut; 27 | -------------------------------------------------------------------------------- /app/atoms/List/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Enzyme, { shallow } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | 5 | import List from './'; 6 | 7 | Enzyme.configure({ adapter: new Adapter() }); 8 | 9 | describe('', () => { 10 | let component; 11 | const className = 'list'; 12 | const items = [ 13 | 'list item', 14 | ]; 15 | 16 | beforeEach(() => { 17 | component = shallow(); 18 | }); 19 | 20 | it('renders an
    tag', () => expect(component.type()).toBe('ul')); 21 | it('sets the className', () => expect(component.prop('className')).toBe(className)); 22 | it('renders the list item', () => expect(component.find('li').first().text()).toBe(items[0])); 23 | }); 24 | -------------------------------------------------------------------------------- /app/molecules/Dropdown/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import Option from './Option'; 5 | 6 | function Dropdown(props) { 7 | let content = (); 8 | 9 | // If we have items, render them 10 | if (props.values) { 11 | content = props.values.map((value) => ( 12 |