├── .babelrc ├── .dockerignore ├── .eslintrc ├── .gitignore ├── .storybook ├── config.js └── webpack.config.js ├── .stylelintrc ├── .travis.yml ├── Dockerfile ├── LICENSE.md ├── README.md ├── app.json ├── app ├── client │ ├── index.js │ └── root.js ├── common │ ├── WithStylesContext.js │ ├── components │ │ ├── .gitkeep │ │ ├── Button │ │ │ ├── index.js │ │ │ ├── index.story.js │ │ │ └── styles.scss │ │ ├── ErrorMessages │ │ │ └── index.js │ │ ├── Form │ │ │ ├── FormRow │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ ├── index.js │ │ │ ├── index.story.js │ │ │ └── styles.scss │ │ ├── FormField │ │ │ ├── index.js │ │ │ └── styles.scss │ │ ├── Icon │ │ │ ├── icons.font.js │ │ │ ├── icons │ │ │ │ ├── add.svg │ │ │ │ ├── arrow-down.svg │ │ │ │ ├── arrow-left-large.svg │ │ │ │ ├── arrow-left.svg │ │ │ │ ├── arrow-right.svg │ │ │ │ ├── arrows-expand.svg │ │ │ │ ├── arrows-reduce.svg │ │ │ │ ├── check-right.svg │ │ │ │ ├── doc.svg │ │ │ │ ├── eye.svg │ │ │ │ └── trash.svg │ │ │ ├── index.js │ │ │ ├── index.story.js │ │ │ └── templates │ │ │ │ └── css.hbs │ │ ├── Poster │ │ │ ├── index.js │ │ │ ├── index.story.js │ │ │ └── styles.scss │ │ ├── TextInput │ │ │ ├── index.js │ │ │ ├── index.story.js │ │ │ └── styles.scss │ │ ├── TextareaInput │ │ │ ├── index.js │ │ │ ├── index.story.js │ │ │ └── styles.scss │ │ └── _Component_ │ │ │ ├── index.js │ │ │ ├── index.story.js │ │ │ └── styles.scss │ ├── config.js │ ├── containers │ │ ├── blocks │ │ │ ├── .gitkeep │ │ │ └── MovieCard │ │ │ │ ├── index.js │ │ │ │ ├── index.story.js │ │ │ │ └── styles.scss │ │ ├── forms │ │ │ ├── .gitkeep │ │ │ └── MovieForm │ │ │ │ └── index.js │ │ ├── layouts │ │ │ ├── .gitkeep │ │ │ ├── App │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ │ └── Main │ │ │ │ ├── index.js │ │ │ │ └── styles.scss │ │ └── pages │ │ │ ├── .gitkeep │ │ │ ├── MoviesCreatePage │ │ │ ├── index.js │ │ │ └── styles.scss │ │ │ ├── MoviesDetailsPage │ │ │ ├── index.js │ │ │ └── styles.scss │ │ │ ├── MoviesListPage │ │ │ ├── index.js │ │ │ └── styles.scss │ │ │ └── NotFoundPage │ │ │ └── index.js │ ├── helpers │ │ ├── .gitkeep │ │ ├── tests │ │ │ └── mount.js │ │ ├── url.js │ │ ├── url.test.js │ │ └── validate.js │ ├── locales │ │ ├── .gitkeep │ │ ├── en.po │ │ ├── ru.po │ │ ├── source.pot │ │ └── uk.po │ ├── redux │ │ ├── .gitkeep │ │ ├── api.js │ │ ├── data │ │ │ ├── index.js │ │ │ └── movies.js │ │ ├── index.js │ │ ├── language.js │ │ └── ui │ │ │ └── loading.js │ ├── routes │ │ ├── .gitkeep │ │ └── index.js │ ├── schemas │ │ ├── .gitkeep │ │ └── index.js │ ├── services │ │ ├── .gitkeep │ │ ├── i18next.js │ │ └── validations.js │ ├── store │ │ ├── .gitkeep │ │ └── index.js │ └── styles │ │ └── variables.scss └── server │ ├── __dev.js │ ├── api │ └── index.js │ ├── page.js │ ├── server.js │ ├── sitemap.js │ └── views │ └── index.ejs ├── bin ├── build.sh ├── ci │ └── push.sh ├── release.sh ├── start.sh └── version-increment.sh ├── cypress.json ├── cypress ├── fixtures │ └── example.json ├── integration │ ├── creating_new_post.js │ └── movies_list.js ├── plugins │ └── index.js └── support │ ├── commands.js │ └── index.js ├── docs ├── .gitkeep └── images │ └── atom-eslint-autofix.png ├── package.json ├── postcss.config.js ├── public └── .gitkeep ├── webpack.config.js ├── webpack.server.js ├── webpack └── parts.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", { "modules": false }], 4 | "react", 5 | "stage-0", 6 | ], 7 | "plugins": [ 8 | "transform-runtime", 9 | "transform-decorators-legacy", 10 | [ 11 | "module-resolver", 12 | { 13 | "alias": { 14 | "tests": "./tests", 15 | "withStyles": "isomorphic-style-loader/lib/withStyles", 16 | "public": "./public", 17 | "@": "./app/common" 18 | } 19 | } 20 | ], 21 | ], 22 | "env": { 23 | "development": { 24 | "plugins": [ 25 | "react-hot-loader/babel" 26 | ] 27 | }, 28 | "test": { 29 | "presets": [ 30 | "es2015", 31 | "react", 32 | "stage-0", 33 | ] 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | confs 3 | bin 4 | scripts 5 | tests 6 | .md 7 | *.log 8 | .git 9 | .dockerignore 10 | Dockerfile 11 | docs/ 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "cypress/globals": true 8 | }, 9 | "globals": { 10 | "__DEV__": true, 11 | "__CLIENT__": true, 12 | "__REDUX_STATE__": true 13 | }, 14 | "plugins": [ 15 | "chai-expect", 16 | "cypress" 17 | ], 18 | "rules": { 19 | "no-nested-ternary": 0, 20 | "react/no-unused-prop-types": 0, 21 | "react/forbid-prop-types": 0, 22 | "new-cap": 0, 23 | "camelcase": 0, 24 | "no-bitwise": 0, 25 | "no-shadow": 0, 26 | "no-inner-declarations": 0, 27 | "no-unused-expressions": 0, 28 | "no-underscore-dangle": 0, 29 | "no-use-before-define": 0, 30 | "class-methods-use-this": 0, 31 | "import/prefer-default-export": 0, 32 | "import/no-extraneous-dependencies": 0, 33 | "react/jsx-filename-extension": 0, 34 | "react/prop-types": 0, 35 | "react/prefer-stateless-function": 0, 36 | "chai-expect/missing-assertion": 2, 37 | "chai-expect/terminating-properties": 1, 38 | "jsx-a11y/label-has-for": 0, 39 | "jsx-a11y/no-static-element-interactions": 0 40 | }, 41 | "parserOptions":{ 42 | "ecmaFeatures": { 43 | "classes": true, 44 | "experimentalObjectRestSpread": true 45 | } 46 | }, 47 | "settings": { 48 | "import/resolver": { 49 | "babel-module": {} 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .tmp 3 | .idea 4 | .DS_Store 5 | *.log 6 | static 7 | .env 8 | coverage 9 | lcov.info 10 | tests_output 11 | !Icon 12 | !icon 13 | browserstack.err 14 | *.mo 15 | public/sitemap*.xml 16 | logs 17 | stats.json 18 | cypress/screenshots 19 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { configure, addDecorator } from '@storybook/react' 3 | import { Provider } from 'react-redux' 4 | import { noop } from 'lodash' 5 | import { Router } from 'react-router' 6 | import createMemoryHistory from 'history/lib/createMemoryHistory'; 7 | import { I18nextProvider } from 'react-i18next'; 8 | import CookieDough from 'cookie-dough' 9 | 10 | import AppLayout from '@/containers/layouts/App' 11 | import { configureStore } from '@/store' 12 | import i18n from '@/services/i18next'; 13 | import WithStylesContext from '@/WithStylesContext'; 14 | 15 | const requireContext = require.context('../app', true, /\.story\.js$/) 16 | const loadStories = () => { 17 | // https://webpack.github.io/docs/context.html 18 | requireContext.keys().forEach(requireContext) 19 | } 20 | 21 | const store = configureStore({ history: createMemoryHistory(), cookies: CookieDough(), i18n }) 22 | addDecorator(story => ( 23 | 24 | 25 | 26 | 27 | {story()} 28 | 29 | 30 | 31 | 32 | )) 33 | 34 | configure(loadStories, module) 35 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const webpackMerge = require('webpack-merge') 4 | const parts = require('../webpack/parts') 5 | 6 | module.exports = webpackMerge( 7 | { 8 | plugins: [ 9 | new webpack.DefinePlugin({ 10 | 'process.env': { 11 | NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development'), 12 | }, 13 | __CLIENT__: true, 14 | }), 15 | ], 16 | }, 17 | parts.setupJs(), 18 | parts.setupFont(), 19 | parts.setupImages(), 20 | parts.setupJson(), 21 | parts.setupI18n(), 22 | parts.setupCss(), 23 | parts.setupFontGen(), 24 | ) 25 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | "selector-pseudo-class-no-unknown": [true, { 5 | ignorePseudoClasses: [ 6 | "global", 7 | "local" 8 | ], 9 | }] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | services: 3 | - docker 4 | cache: 5 | directories: 6 | - "~/.nvm" 7 | - node_modules 8 | - ~/.npm 9 | - ~/.cache 10 | env: 11 | global: 12 | - RELEASE_BRANCH="master" 13 | - DOCKER_HUB_ACCOUNT="frontband" 14 | - MAIN_BRANCHES="master develop" 15 | - secure: X3VGjTmC+XMv1cockfzaYLcVPBX9jgbFCwXSvzlxjQ8M1d9dYHgQO3JHaprdWTiELSEK/rjf3Tw8LmT9l0EkX8nh/mU9c1aTEGqgr6w2lTYt3P03olDKA5spDivIFoy0KjtYJJcrBuYl7q9OdTmpJl3JPsm9PSsJTdaLmd9KU/rmdeJMXVL/S7QvXDNNFd82k12j2sO4EeeaSMOfid4TvmrfsNh/zDHk0TU4WZTTCBTawxgGymfw1Xnp6tl8UhCOFLMLatb9gaeI0cAWiyPM03FZQbEbQjMU79ifBeVZFtP/PCbF+w2m9GZc8/G1CzwSn39P1TpB7uEzCqMXYKfrJI2ROPK0LbaHmHRrIBPbZDtjzsAWEDCQqk3oTJjWSCXcaeRG90ByP2qOsuxzfQDGx0prTaToRAsjfL0frSBdHS8G0ebtxCD6x68DH0iMFkUH4sUAXpnl23RVblwR9/xSv5g8Qgea5xDIQEXDchLJtcHCwtVvH6xD6gSwIpXmdLZTWRlqGaaDoxO2nXCfZ7eWAeu5nM8Y8S/CfHcqYYPKRqIi2M95DmhUlofXY/7KTjK/tBW1dGUx1g3CYffZklrdTU9qlSWryeYWiZQj5tNPYlcDlPHcTGune8p4zcJg3dVOMKZVFI6CgO8v6jj/t2kb41iUFKyzmuYG1LT8eENQrJg= 16 | node_js: 17 | - 7.5.0 18 | install: 19 | - yarn install --no-progress --frozen-lockfile 20 | before_install: 21 | - chmod -R +x bin 22 | script: 23 | - yarn test 24 | - yarn lint 25 | - "./bin/version-increment.sh" 26 | - "./bin/build.sh" 27 | - "./bin/start.sh" 28 | - sleep 5 29 | - docker ps 30 | - RUNNING_CONTAINERS=`docker ps | wc -l`; if [ "${RUNNING_CONTAINERS//[[:space:]]/}" 31 | == "1" ]; then echo "[E] Container is not started\!"; docker logs react-boilerplate 32 | --details --since 5h; exit 1; fi; 33 | - yarn test:acceptance 34 | - ARGOS_COMMIT=$TRAVIS_COMMIT ARGOS_BRANCH=$TRAVIS_BRANCH yarn argos upload cypress/screenshots --token $ARGOS_TOKEN || true 35 | - sleep 5 36 | after_success: 37 | - "./bin/ci/push.sh" 38 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:9.3.0-alpine 2 | 3 | EXPOSE 8080 4 | 5 | ENV PORT 8080 6 | ENV NODE_ENV production 7 | 8 | RUN apk add --update \ 9 | python 10 | 11 | RUN npm i -g pm2 --quiet 12 | 13 | COPY package.json /tmp/package.json 14 | COPY yarn.lock /tmp/yarn.lock 15 | 16 | RUN yarn --version && node --version && npm --version 17 | 18 | RUN cd /tmp && ls -la && yarn install --no-progress --frozen-lockfile || { exit 1; } && mkdir -p /opt/app && cp -a /tmp/node_modules /opt/app/ 19 | 20 | WORKDIR /opt/app 21 | 22 | COPY . /opt/app 23 | 24 | RUN yarn build 25 | 26 | RUN rm -rf ./app/client \ 27 | rm -rf ./app/common \ 28 | rm -rf ./node_modules/webpack 29 | 30 | # Clear deps and caches 31 | RUN apk --purge del python && rm -rf /var/cache/apk/* 32 | 33 | CMD pm2 start --log-type json --no-daemon static/server.js 34 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Front.Band 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Boilerplate 2 | 3 | Example project. We are using it as a start point for out applications or as a base for an education of our developers. 4 | 5 | ## Commands 6 | 7 | | Command | Description | 8 | | - | - | 9 | | `yarn dev` | Run dev server | 10 | | `yarn production` | Run production server | 11 | | `yarn lint` | Check code with Eslint and Stylelint | 12 | | `yarn build` | Build production | 13 | | `yarn stats` | Run webpack statistic dashboard to analyze bundle size` | 14 | | `yarn locales:extract` | Extract locales from the app into `.pot` file | 15 | | `yarn storybook` | Run storybook | 16 | | `yarn test:acceptance` | Run acceptance tests | 17 | 18 | ## Frontend Architecture 19 | 20 | ### Project structure 21 | 22 | - `app` - our application files. React JS app is here 23 | - `.storybook` - storybook configuration files. 24 | - `bin` - CI automation scripts 25 | - `cypress` - acceptance tests 26 | - `docs` - docs related documents, assets 27 | - `public` - public static data, like favicon, robot.txt, that is not using in react js app and that doesn't have to go thought webpack process 28 | - `webpack` - webpack configuration parts 29 | 30 | #### Application structure 31 | 32 | Inside `app` folder: 33 | 34 | - `client` - client's entrypoint 35 | - `server` - server's entrypoint 36 | - `common` - code, that is shared between client and server 37 | - `common/components` - generic components. Core UI of our application. Elements, that can be publish and reused in other projects. 38 | - `common/containers/blocks` - not generic components. specific for this project. API driven and can't be re-used in other projects 39 | - `common/containers/forms` - application's forms 40 | - `common/containers/layouts` - layouts 41 | - `common/containers/pages` - pages 42 | - `common/helpers` - helpers. 43 | - `common/locales` - localization files and template 44 | - `common/redux` - redux modules. reducers and actions are here 45 | - `common/routes` - routes definitions. 46 | - `common/schemas` - normalizr schemas. 47 | - `common/services` - configuration of 3rd party modules and services 48 | - `common/store` - configuration of redux store 49 | - `common/styles` - shared styles, like variables 50 | 51 | ### Code style 52 | 53 | #### Eslint 54 | 55 | We're using eslint to keep js and jsx code style consistent. Check that your IDE is using Eslint file from this repo. Anyway, pre-commit hook is checking lint with our internal installed eslint version. So if your IDE is not showing you the errors, but you have them in pre-commit hook, re-check IDE :D 56 | 57 | ##### Atom 58 | Add plugin [linter-eslint](https://atom.io/packages/linter-eslint). Go to the plugin's configuration and enable option **Fix errors on save** 59 | 60 | ![Fix errors on save](./docs/images/atom-eslint-autofix.png) 61 | 62 | #### Stylelint 63 | 64 | Stylelint is using to control the codestyle in style files. Configure your IDE to use config from this repo. 65 | 66 | ### Git flow 67 | 68 | - **Stable branch** - `master` 69 | - Don't push directly to the stable branch. Use PRs instead 70 | 71 | **Workflow:** 72 | 73 | 1. Start a ticket with a new branch 74 | 2. Write code 75 | 3. Create Pull Request 76 | 4. Get an approve from one of your coworkers 77 | 5. Merge PR's branch with the stable branch 78 | 79 | #### Name of the branches 80 | 81 | We are not following some strict rule on this stage of branch naming. So we have a single rule for the branch names: 82 | 1. Make you branch names meaningful. 83 | 84 | Bad example 85 | ``` 86 | fix-1 87 | redesign 88 | ``` 89 | 90 | Good example 91 | ``` 92 | fix-signals-table 93 | new-user-profile-page 94 | ``` 95 | 96 | ##### JIRA tickets 97 | 98 | If you are using JIRA as a task manager, follow this naming convention 99 | 100 | ``` 101 | [type of ticket]/[number of branch]-[short-title] 102 | 103 | feature/FRB-123-change-titles 104 | fix/FRB-431-retina-images 105 | ``` 106 | 107 | ### Components 108 | 109 | We are creating React application with component-approach. This means, what we try to decompose our UI into re-usable parts. 110 | 111 | Try to make components PURE when it's possible. Avoid using redux or inner state for components in `app/common/components`. 112 | 113 | Base component contains next files: 114 | 115 | - `index.js` - base component 116 | - `styles.scss` - styles file 117 | - `index.story.js` - storybook file 118 | 119 | See the [example component](./app/common/components/_Component_). 120 | 121 | #### Recompose 122 | 123 | Use recompose to create logic layout of your components. recompose allow us to split logic into multiple re-usable parts. 124 | 125 | #### Storybook 126 | 127 | Storybook is using as a UI library. We are using it as documentation for our UI. The goals are: 128 | 129 | - help teammates to find the right component 130 | - help to understand how to use the component 131 | - avoid duplications 132 | 133 | **The rule is**: write a story for all generic pure components and blocks and show all the existing variations. 134 | 135 | Help your teammates understand from story how to use your component. Not just write the story of itself. Think about your colleagues. 136 | 137 | #### Styling 138 | 139 | Shortly about our styles: 140 | - CSS modules 141 | - PostCss with SCSS like style 142 | 143 | We're using scoped styles, so you don't need to use BEM or other methodology to avoid conflicts in the styles. 144 | In BEM terminology, you don't have to use elements. Use only block and modificators. 145 | 146 | **If you feel that you also need an element - think, probably you have to extract a new component from this template.** 147 | 148 | Bad example 149 | 150 | ```scss 151 | .page { 152 | &__title {} 153 | &__content { 154 | &_active {} 155 | } 156 | } 157 | ``` 158 | 159 | Good example 160 | 161 | ```scss 162 | .root {} // root element 163 | .title {} 164 | .content { 165 | &.isActive {} 166 | } 167 | ``` 168 | 169 | Use `is` prefix for the modificators. 170 | 171 | ##### Injecting styles to component 172 | 173 | We are using `isomorphic style loader` to calculate critical CSS path. Use withStyles HOC to connect component and style. Use `withStyles` alias for import. 174 | 175 | For example 176 | 177 | ```js 178 | import React from 'react'; 179 | import PropTypes from 'prop-types'; 180 | import withStyles from 'withStyles'; 181 | import { compose } from 'recompose'; 182 | import styles from './styles.scss'; 183 | 184 | const Poster = ({ src, title }) => ( 185 |
186 | ); 187 | 188 | Poster.propTypes = { 189 | children: PropTypes.node, 190 | }; 191 | 192 | export default compose( 193 | withStyles(styles) 194 | )(Poster); 195 | ``` 196 | 197 | #### Prop names 198 | 199 | **Make names meaningful.** 200 | 201 | ```html 202 | 9 | )) 10 | .add('Block', () => ( 11 | 12 | )); 13 | -------------------------------------------------------------------------------- /app/common/components/Button/styles.scss: -------------------------------------------------------------------------------- 1 | @import "variables.scss"; 2 | 3 | .root { 4 | padding: 8px 12px; 5 | border-radius: 6px; 6 | background-color: $blue; 7 | color: white; 8 | min-width: 120px; 9 | cursor: pointer; 10 | display: inline-block; 11 | text-decoration: none; 12 | } 13 | 14 | .isBlock { 15 | width: 100%; 16 | display: block; 17 | } 18 | -------------------------------------------------------------------------------- /app/common/components/ErrorMessages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { compose } from 'recompose'; 3 | import { translate } from 'react-i18next'; 4 | import { ErrorMessages, ErrorMessage } from 'react-nebo15-validate'; 5 | 6 | const ErrorMessagesTranslated = ({ children, t, ...rest }) => ( 7 | 8 | {children} 9 | {t('Required field')} 10 | {t('Min length is <%= params %>')} 11 | 12 | ); 13 | 14 | export default compose( 15 | translate() 16 | )(ErrorMessagesTranslated); 17 | -------------------------------------------------------------------------------- /app/common/components/Form/FormRow/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import withStyles from 'withStyles'; 4 | import { compose } from 'recompose'; 5 | import styles from './styles.scss'; 6 | 7 | const FormRow = ({ label, children, ...rest }) => ( 8 |
9 | { label &&
{ label }
} 10 |
11 | { children } 12 |
13 |
14 | ); 15 | 16 | FormRow.propTypes = { 17 | children: PropTypes.node, 18 | label: PropTypes.node, 19 | }; 20 | 21 | export default compose( 22 | withStyles(styles) 23 | )(FormRow); 24 | -------------------------------------------------------------------------------- /app/common/components/Form/FormRow/styles.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | margin-top: 20px; 3 | 4 | &:first-child { 5 | margin-top: 0; 6 | } 7 | } 8 | 9 | .label { 10 | margin-bottom: 10px; 11 | font-size: 14px; 12 | font-weight: 400; 13 | } 14 | -------------------------------------------------------------------------------- /app/common/components/Form/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import withStyles from 'withStyles'; 3 | import { compose } from 'recompose'; 4 | import styles from './styles.scss'; 5 | 6 | const Form = ({ ...rest }) => ( 7 |
8 | ); 9 | 10 | export FormRow from './FormRow'; 11 | 12 | export default compose( 13 | withStyles(styles) 14 | )(Form); 15 | -------------------------------------------------------------------------------- /app/common/components/Form/index.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | 4 | import Form, { FormRow } from './index'; 5 | 6 | storiesOf('components/Form', module) 7 | .add('General', () => ( 8 | 9 | row 1 10 | row 2 11 |
12 | )); 13 | -------------------------------------------------------------------------------- /app/common/components/Form/styles.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | display: block; 3 | width: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /app/common/components/FormField/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import withStyles from 'withStyles'; 4 | import { compose, withProps, pure } from 'recompose'; 5 | import ErrorMessages from '@/components/ErrorMessages'; 6 | import styles from './styles.scss'; 7 | 8 | const FormField = ({ input, meta, showError, inputComponent: InputComponent }) => ( 9 |
10 |
11 | 12 |
13 | { showError && ( 14 |
15 | 16 |
17 | )} 18 |
19 | ); 20 | 21 | FormField.propTypes = { 22 | input: PropTypes.object.isRequired, 23 | meta: PropTypes.object.isRequired, 24 | inputComponent: PropTypes.func.isRequired, 25 | showError: PropTypes.bool, 26 | }; 27 | 28 | export default compose( 29 | withStyles(styles), 30 | withProps(({ meta }) => ({ 31 | showError: !!((meta.submitFailed || (meta.touched && !meta.active)) && meta.error), 32 | })), 33 | pure 34 | )(FormField); 35 | -------------------------------------------------------------------------------- /app/common/components/FormField/styles.scss: -------------------------------------------------------------------------------- 1 | @import "variables.scss"; 2 | 3 | .error { 4 | margin-top: 8px; 5 | color: $red; 6 | font-size: 10px; 7 | } 8 | -------------------------------------------------------------------------------- /app/common/components/Icon/icons.font.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | files: [ 4 | 'icons/*.svg', // glob style 5 | ], 6 | fontName: 'fontIcons', 7 | classPrefix: 'icon-', 8 | baseSelector: '.icon', 9 | fixedWidth: true, 10 | types: ['eot', 'woff', 'ttf', 'svg'], // this is the default 11 | cssTemplate: 'templates/css.hbs', 12 | }; 13 | -------------------------------------------------------------------------------- /app/common/components/Icon/icons/add.svg: -------------------------------------------------------------------------------- 1 | Прямоугольник 41Created with Avocode. -------------------------------------------------------------------------------- /app/common/components/Icon/icons/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/common/components/Icon/icons/arrow-left-large.svg: -------------------------------------------------------------------------------- 1 | Прямоугольник 45Created with Avocode. -------------------------------------------------------------------------------- /app/common/components/Icon/icons/arrow-left.svg: -------------------------------------------------------------------------------- 1 | arrow_leftCreated with Avocode. -------------------------------------------------------------------------------- /app/common/components/Icon/icons/arrow-right.svg: -------------------------------------------------------------------------------- 1 | arrow_rightCreated with Avocode. -------------------------------------------------------------------------------- /app/common/components/Icon/icons/arrows-expand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/common/components/Icon/icons/arrows-reduce.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/common/components/Icon/icons/check-right.svg: -------------------------------------------------------------------------------- 1 | Прямоугольник 24Created with Avocode. -------------------------------------------------------------------------------- /app/common/components/Icon/icons/doc.svg: -------------------------------------------------------------------------------- 1 | 3 | Фигура 7 копия 2 4 | Created with Avocode. 5 | 6 | 7 | 9 | 11 | 13 | 15 | 16 | 17 | 20 | 23 | 26 | 29 | -------------------------------------------------------------------------------- /app/common/components/Icon/icons/eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | eye 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/common/components/Icon/icons/trash.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/common/components/Icon/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classnames from 'classnames'; 4 | import withStyles from 'withStyles'; 5 | import styles from './icons.font'; 6 | 7 | export const icons = [ 8 | 'arrow-left', 9 | 'arrow-left-large', 10 | 'arrow-right', 11 | 'arrow-down', 12 | 'check-right', 13 | 'add', 14 | 'doc', 15 | 'trash', 16 | 'eye', 17 | 'arrows-expand', 18 | 'arrows-reduce', 19 | ]; 20 | 21 | const Icon = ({ name }) => React.createElement('i', { 22 | className: classnames(styles.icon, styles[`icon-${name}`]), 23 | }); 24 | 25 | Icon.propTypes = { 26 | name: PropTypes.oneOf(icons).isRequired, 27 | }; 28 | 29 | export default withStyles(styles)(Icon); 30 | -------------------------------------------------------------------------------- /app/common/components/Icon/index.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | 4 | import Icon, { icons } from './index'; 5 | 6 | storiesOf('components/Icon', module) 7 | .add('General', () => ( 8 |
9 | { icons.map(iconName => ( 10 | 11 | ))} 12 |
13 | )) 14 | .add('Change color', () => ( 15 |
16 | { icons.map(iconName => ( 17 | 18 | ))} 19 |
20 | )) 21 | .add('Change size', () => ( 22 |
23 | { icons.map(iconName => ( 24 | 25 | ))} 26 |
27 | )); 28 | -------------------------------------------------------------------------------- /app/common/components/Icon/templates/css.hbs: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "{{fontName}}"; 3 | src: {{{src}}}; 4 | } 5 | 6 | {{baseSelector}} { 7 | line-height: 1; 8 | display: inline-block; 9 | } 10 | 11 | {{baseSelector}}:before { 12 | font-family: {{fontName}} !important; 13 | font-style: normal; 14 | font-weight: normal !important; 15 | vertical-align: top; 16 | text-rendering: auto; 17 | speak: none; 18 | line-height: 1; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | } 22 | 23 | {{#each codepoints}} 24 | .{{../classPrefix}}{{@key}}:before { 25 | content: "\\{{this}}"; 26 | } 27 | {{/each}} 28 | -------------------------------------------------------------------------------- /app/common/components/Poster/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import withStyles from 'withStyles'; 4 | import { compose } from 'recompose'; 5 | import styles from './styles.scss'; 6 | 7 | const Poster = ({ src, title }) => ( 8 |
9 | ); 10 | 11 | Poster.propTypes = { 12 | children: PropTypes.node, 13 | }; 14 | 15 | export default compose( 16 | withStyles(styles) 17 | )(Poster); 18 | -------------------------------------------------------------------------------- /app/common/components/Poster/index.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | 4 | import Poster from './index'; 5 | 6 | const posterUrl = 'https://ia.media-imdb.com/images/M/MV5BM2MyNjYxNmUtYTAwNi00MTYxLWJmNWYtYzZlODY3ZTk3OTFlXkEyXkFqcGdeQXVyNzkwMjQ5NzM@._V1_SY1000_CR0,0,704,1000_AL_.jpg'; 7 | 8 | storiesOf('components/Poster', module) 9 | .add('General', () => ( 10 | 11 | )); 12 | -------------------------------------------------------------------------------- /app/common/components/Poster/styles.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | background-size: cover; 3 | background-position: top center; 4 | width: 300px; 5 | height: 400px; 6 | } 7 | -------------------------------------------------------------------------------- /app/common/components/TextInput/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import withStyles from 'withStyles'; 4 | import classnames from 'classnames'; 5 | import { compose } from 'recompose'; 6 | 7 | import styles from './styles.scss'; 8 | 9 | const TextInput = ({ error, ...rest }) => ( 10 | 11 | ); 12 | 13 | TextInput.propTypes = { 14 | error: PropTypes.bool, 15 | }; 16 | 17 | export default compose( 18 | withStyles(styles) 19 | )(TextInput); 20 | -------------------------------------------------------------------------------- /app/common/components/TextInput/index.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | 5 | import TextInput from './index'; 6 | 7 | storiesOf('components/TextInput', module) 8 | .add('General', () => ( 9 | 10 | )); 11 | -------------------------------------------------------------------------------- /app/common/components/TextInput/styles.scss: -------------------------------------------------------------------------------- 1 | @import "variables.scss"; 2 | 3 | .input { 4 | padding: 8px 12px; 5 | border: 1px solid $blue-grey; 6 | border-radius: 3px; 7 | width: 100%; 8 | display: block; 9 | } 10 | 11 | .isError { 12 | border-color: $red; 13 | } 14 | -------------------------------------------------------------------------------- /app/common/components/TextareaInput/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import withStyles from 'withStyles'; 4 | import classnames from 'classnames'; 5 | import { compose } from 'recompose'; 6 | import styles from './styles.scss'; 7 | 8 | const TextareaInput = ({ error, ...rest }) => ( 9 |