├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── app
├── fonts
│ └── .gitkeep
├── images
│ └── .gitkeep
├── src
│ ├── actions
│ │ ├── actionCreators.js
│ │ └── amazingComponent.js
│ ├── components
│ │ ├── AmazingBox
│ │ │ ├── AmazingBox.js
│ │ │ └── AmazingBox.module.scss
│ │ ├── AmazingComponent
│ │ │ ├── AmazingComponent.js
│ │ │ └── AmazingComponent.module.scss
│ │ ├── AmazingInput
│ │ │ ├── AmazingInput.js
│ │ │ └── AmazingInput.module.scss
│ │ ├── App.jsx
│ │ ├── HacksmithsLogo
│ │ │ ├── HacksmithsLogo.js
│ │ │ ├── HacksmithsLogo.module.scss
│ │ │ └── hacksmiths.png
│ │ ├── Main.js
│ │ └── index.js
│ ├── constants
│ │ └── amazingComponent.js
│ ├── containers
│ │ ├── MyAmazingContainer
│ │ │ ├── MyAmazingContainer.js
│ │ │ └── MyAmazingContainer.module.scss
│ │ └── index.js
│ ├── index.js
│ ├── pages
│ │ ├── LandingPage
│ │ │ ├── LandingPage.js
│ │ │ └── LandingPage.module.scss
│ │ ├── NotFoundPage
│ │ │ ├── NotFoundPage.js
│ │ │ └── NotFoundPage.module.scss
│ │ └── index.js
│ ├── reducers
│ │ ├── amazingComponent.js
│ │ └── index.js
│ ├── store
│ │ ├── initialState.js
│ │ └── store.js
│ └── utils
│ │ ├── network.js
│ │ ├── request.js
│ │ └── router.js
├── styles
│ ├── settings.scss
│ └── styles.scss
└── tests
│ ├── actions
│ └── amazingComponent.js
│ ├── components
│ └── .gitkeep
│ ├── reducers
│ └── amazingComponent.js
│ └── test_helper.js
├── index.html
├── package.json
└── webpack.config.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | app/build/
2 | app/dist/
3 | webpack.config.js
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-airbnb",
3 | "parser": "babel-eslint",
4 | "env": {
5 | "browser": true,
6 | "node": true,
7 | "mocha": true
8 | },
9 | "rules": {
10 | "comma-dangle": [2, "never"],
11 | "func-names": 0,
12 | "eol-last": 0
13 | },
14 | "plugins": [
15 | "react",
16 | ]
17 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.css linguist-language=JavaScript
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | build/
3 | npm-debug.log
4 | .DS_Store
5 | =======
6 | # Logs
7 | logs
8 | *.log
9 | npm-debug.log*
10 |
11 | # Runtime data
12 | pids
13 | *.pid
14 | *.seed
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # nyc test coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # node-waf configuration
29 | .lock-wscript
30 |
31 | # Compiled binary addons (http://nodejs.org/api/addons.html)
32 | build/Release
33 |
34 | # Dependency directories
35 | node_modules
36 | jspm_packages
37 |
38 | # Optional npm cache directory
39 | .npm
40 |
41 | # Optional REPL history
42 | .node_repl_history
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Ryan Collins
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 | # Notice
2 | This project will no longer be updated, in favor of focusing my efforts on the [Scalable React Boilerplate](https://github.com/RyanCCollins/scalable-react-boilerplate) and [Generator Scalable React](https://github.com/RyanCCollins/slush-generator-scalable-react). Feel free to keep using it!
3 |
4 | 
5 | # Behind the boilerplate
6 | The hardest part about React is getting setup. This project aims to help anyone quickly get bootstrapped with the latest versions of React, Redux, Webpack, etc. It uses Hot Module Reloading and has a few optional add-ons like React Foundation, Redux Form, etc.
7 |
8 | It follows best practices, including the [AirBnb JS & JSX style guides](https://github.com/airbnb/javascript) and uses the FTF (file-type first) organizational pattern.
9 |
10 | ## Scalable / Feature First Boilerplate
11 | If you're looking for something with a bit more features, take a look at the [Scalable React Boilerplate](https://github.com/RyanCCollins/scalable-react-boilerplate) project!
12 |
13 | # Documentation
14 |
15 | ## Getting Started
16 | To try the example application out or to use the project, follow the instructions below.
17 |
18 | 1. **Clone repo**
19 |
20 | git clone https://github.com/RyanCCollins/react-redux-simple-starter.git
21 |
22 | 2. **Install dependencies**
23 |
24 | npm run setup
25 |
26 | 3. **Run development server**
27 |
28 | npm run start
29 |
30 | Development server should be running at http://localhost:8080/
31 |
32 | 4. **Make build**
33 |
34 | npm run build
35 |
36 | ### File Structure
37 | * Some files left out for brevity. Please reference the files in the [React Redux Simple Starter](https://github.com/RyanCCollins/react-redux-simple-starter) project for information about the file structure.
38 | ```
39 | .
40 | ├── README.md
41 | ├── LICENSE
42 | ├── index.html
43 | ├── package.json
44 | ├── webpack.config.js
45 | ├── app/
46 | | ├── fonts
47 | | ├── images
48 | | ├── src
49 | | | ├── actions
50 | | | ├── components
51 | | | | ├── MyComponent
52 | | | | ├── MyOtherComponent
53 | | | | ├── App.js
54 | | | | ├── Main.js
55 | | | | └── index.js
56 | | | ├── containers
57 | | | | ├── MyContainer
58 | | | | └── index.js
59 | | | ├── pages
60 | | | ├── reducers
61 | | | ├── store
62 | | | ├── utils
63 | | | └── index.js
64 | | ├── styles
65 | | └── tests
66 | | | ├── actions
67 | | | ├── components
68 | | | ├── reducers
69 | | | └── test_helper.js
70 | ├── .eslintignore
71 | ├── .eslintrc
72 | ├── .gitattributes
73 | └── .gitignore
74 | ```
75 |
76 | ## Scripts
77 | - **npm run setup**
78 |
79 | Installs the application's dependencies
80 |
81 | - **npm run test**
82 |
83 | Runs unit tests
84 |
85 | - **npm run test:watch**
86 |
87 | Watches for changes to run unit tests
88 |
89 | - **npm run build**
90 |
91 | Bundles the application
92 |
93 | - **npm run dev**
94 |
95 | Starts webpack development server
96 |
97 | - **npm run lint**
98 |
99 | Runs the linter
100 |
101 | - **npm run deploy**
102 |
103 | Creates the production ready files
104 |
105 | - **npm run clean**
106 |
107 | Removes the bundled code and the production ready files
108 |
109 |
110 |
111 | ## Technologies / Libraries
112 |
113 | - [Node](https://nodejs.org/en/) - JS runtime environment
114 | - [npm](https://www.npmjs.com/) - package manager
115 | - [Babel](https://babeljs.io/) - ES6 transpiler
116 | - [Webpack](https://webpack.github.io/) - module bundler & task runner
117 | - [React](https://facebook.github.io/react/) - interfaces
118 | - [react-hot-loader](https://github.com/gaearon/react-hot-loader) - hot reloading for react
119 | - [react-router](https://github.com/rackt/react-router) - react application router
120 | - [react-redux](https://github.com/rackt/react-redux) - react bindings for redux
121 | - [react-css-modules](https://github.com/gajus/react-css-modules) - React CSS Modules implement automatic mapping of CSS modules.
122 | - [react-foundation](https://github.com/nordsoftware/react-foundation) - Foundation React components, use or don't use.
123 | - [Immutable](https://github.com/facebook/immutable-js) - data structures
124 | - [Redux](https://github.com/rackt/redux) - awesome flux architecture
125 | - [Redux Form](https://github.com/erikras/redux-form)- works with React Redux to enable an html form in React to use Redux to store all of its state.
126 | - [redux-thunk](https://github.com/gaearon/redux-thunk) - thunk middleware for redux
127 | - [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch) - API fetch network requests
128 | - [redux-devtools](https://github.com/gaearon/redux-devtools) - redux development tool
129 | - [SASS](http://sass-lang.com/) - styles
130 | - [ESLint](http://eslint.org/) - linter
131 | - [Mocha](http://mochajs.org/) - unit tests
132 | - [jsdom](https://github.com/tmpvar/jsdom) - vdom to test React without browser
133 | - [Expect](https://github.com/mjackson/expect) - assertion library
134 | - [Chai / Immutable](http://chaijs.com/) - assertion library for Immutable JS
135 | - A bunch of useful scripts
136 |
137 |
138 | ## Timeline / TODOS
139 | * [x] Write README file
140 | * [x] Setup Filetype First organization
141 | * [x] Add better demonstration of included libraries (run the test application)
142 | * [x] Write unit tests and setup folder structure for testing (See tests directory for examples)
143 | * [x] Migrate to Feature First file organization as noted [in this article](http://engineering.kapost.com/2016/01/organizing-large-react-applications/) and in the [React Boilerplate](https://github.com/mxstbr/react-boilerplate) (See [here](https://github.com/RyanCCollins/scalable-react-boilerplate))
144 | * [ ] Write component tests using Jest and / or Enzyme
145 | * [ ] Write wiki / other documentation
146 | * [ ] Implement a Rails like component generator
147 |
148 |
149 | ### Acknowledgements
150 |
151 | This project is loosely based on: [This boilerplate](https://github.com/mezod/boilerplate-koa-redux-react).
152 |
153 | Thank you to @mezod for their hard work and inspiration.
154 |
--------------------------------------------------------------------------------
/app/fonts/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RyanCCollins/react-redux-simple-starter/67fcb738b2edb5ef88d7ac577c1cd266f0e852d3/app/fonts/.gitkeep
--------------------------------------------------------------------------------
/app/images/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RyanCCollins/react-redux-simple-starter/67fcb738b2edb5ef88d7ac577c1cd266f0e852d3/app/images/.gitkeep
--------------------------------------------------------------------------------
/app/src/actions/actionCreators.js:
--------------------------------------------------------------------------------
1 | export const GLOBAL_ACTION_CREATOR = 'GLOBAL_ACTION_CREATOR';
2 |
--------------------------------------------------------------------------------
/app/src/actions/amazingComponent.js:
--------------------------------------------------------------------------------
1 | import * as types from '../constants/amazingComponent';
2 |
3 | export const addBox = (content) => ({
4 | type: types.ADD_BOX,
5 | content
6 | });
7 |
8 | export const removeBox = (index) => ({
9 | type: types.REMOVE_BOX,
10 | index
11 | });
--------------------------------------------------------------------------------
/app/src/components/AmazingBox/AmazingBox.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes, Component } from 'react';
2 | import cssModules from 'react-css-modules';
3 | import styles from './AmazingBox.module.scss';
4 |
5 | const randomColor = Math.floor(Math.random() * 16777215).toString(16);
6 | const BoxStyle = {
7 | colorStyle: {
8 | color: `#${randomColor}`
9 | }
10 | };
11 |
12 | class AmazingBox extends Component {
13 | constructor() {
14 | super();
15 | this.handleRemoveItem = this.handleRemoveItem.bind(this);
16 | }
17 | handleRemoveItem() {
18 | const {
19 | onRemove,
20 | id
21 | } = this.props;
22 | onRemove(id);
23 | }
24 | render() {
25 | const {
26 | id,
27 | content
28 | } = this.props;
29 | return (
30 |
35 |
#{id}
36 |
{content}
37 |
43 |
44 | );
45 | }
46 | }
47 |
48 | AmazingBox.propTypes = {
49 | content: PropTypes.string.isRequired,
50 | onRemove: PropTypes.func.isRequired,
51 | id: PropTypes.number.isRequired
52 | };
53 |
54 | export default cssModules(AmazingBox, styles);
55 |
--------------------------------------------------------------------------------
/app/src/components/AmazingBox/AmazingBox.module.scss:
--------------------------------------------------------------------------------
1 | .box {
2 | width: 200px;
3 | height: 200px;
4 | border: 1px solid black;
5 | position: relative;
6 | display: flex;
7 | justify-content: center;
8 | align-items: center;
9 | margin: 10px;
10 | }
11 |
12 | .btnClose {
13 | position: absolute;
14 | top: 10px;
15 | right: 10px;
16 | font-size: 25px;
17 | cursor: pointer;
18 | }
19 |
20 | .number {
21 | position: absolute;
22 | left: 5px;
23 | top: 5px;
24 | font-family: 'Open Sans';
25 | font-size: 1.8rem;
26 | }
27 |
28 | .text {
29 | text-shadow: #CCC 1px 0 10px;
30 | font-size: 1.5rem;
31 | font-family: 'Lato';
32 | letter-spacing: 5px;
33 | text-align: center;
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/components/AmazingComponent/AmazingComponent.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import cssModules from 'react-css-modules';
3 | import styles from './AmazingComponent.module.scss';
4 | import { AmazingInput, AmazingBox } from 'components';
5 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
6 |
7 | const AmazingComponent = ({
8 | boxes,
9 | onAddBox,
10 | onRemoveBox
11 | }) => (
12 |
13 |
Play with redux by adding a box to the wall
14 |
17 |
18 |
24 | {boxes.map((box, i) =>
25 |
31 | )}
32 | {!boxes.length &&
33 |
34 | No boxes here 😔. Why don't you add one!
35 |
36 | }
37 |
38 |
39 |
40 | );
41 |
42 | AmazingComponent.propTypes = {
43 | boxes: PropTypes.array.isRequired,
44 | onAddBox: PropTypes.func.isRequired,
45 | onRemoveBox: PropTypes.func.isRequired
46 | };
47 |
48 | export default cssModules(AmazingComponent, styles);
49 |
--------------------------------------------------------------------------------
/app/src/components/AmazingComponent/AmazingComponent.module.scss:
--------------------------------------------------------------------------------
1 | .fullScreen {
2 | min-height: 100vh;
3 | padding: 50px;
4 | }
5 |
6 | .sectionHeader {
7 | font-size: 2rem;
8 | text-align: center;
9 | text-transform: uppercase;
10 | color: white;
11 | font-family: 'Raleway';
12 | letter-spacing: 1px;
13 | padding: 40px;
14 | max-width: 60%;
15 | margin-left: auto;
16 | margin-right: auto;
17 | }
18 |
19 | .flex {
20 | display: flex;
21 | flex-wrap: wrap;
22 | justify-content: center;
23 | align-items: center;
24 | }
25 |
26 | .noBoxes {
27 | margin-top: 50px;
28 | font-family: 'Open Sans';
29 | font-size: 1.4rem;
30 | color: white;
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/components/AmazingInput/AmazingInput.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes, Component } from 'react';
2 | import { Button } from 'react-foundation';
3 | import cssModules from 'react-css-modules';
4 | import styles from './AmazingInput.module.scss';
5 |
6 | class AmazingInput extends Component {
7 | constructor() {
8 | super();
9 | this.handleSubmit = this.handleSubmit.bind(this);
10 | this.handleInput = this.handleInput.bind(this);
11 | this.state = {
12 | value: ''
13 | };
14 | }
15 | handleInput(e) {
16 | this.setState({
17 | value: e.target.value
18 | });
19 | }
20 | handleSubmit() {
21 | const {
22 | onSubmit
23 | } = this.props;
24 | const {
25 | value
26 | } = this.state;
27 | onSubmit(value);
28 | }
29 | render() {
30 | const {
31 | value
32 | } = this.state;
33 | return (
34 |
35 |
36 |
37 |
38 |
45 |
46 |
47 |
54 |
55 | );
56 | }
57 | }
58 |
59 | AmazingInput.propTypes = {
60 | onSubmit: PropTypes.func.isRequired
61 | };
62 |
63 | export default cssModules(AmazingInput, styles);
64 |
--------------------------------------------------------------------------------
/app/src/components/AmazingInput/AmazingInput.module.scss:
--------------------------------------------------------------------------------
1 | .formGroup {
2 | display: flex;
3 | margin: 20px;
4 | align-items: center;
5 | justify-content: center;
6 | }
7 |
8 | .button {
9 | margin-left: 40px !important;
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/components/App.jsx:
--------------------------------------------------------------------------------
1 | import { bindActionCreators } from 'redux';
2 | import { connect } from 'react-redux';
3 | import * as actionCreators from '../actions/actionCreators';
4 | import Main from './Main';
5 |
6 | // Map the global state to global props here.
7 | // See: https://egghead.io/lessons/javascript-redux-generating-containers-with-connect-from-react-redux-visibletodolist
8 | const mapStateToProps = (state) => ({
9 | messages: state.messages,
10 | errors: state.errors
11 | });
12 |
13 | // Map the dispatch and bind the action creators.
14 | // See: http://redux.js.org/docs/api/bindActionCreators.html
15 | function mapDispatchToProps(dispatch) {
16 | return bindActionCreators(
17 | actionCreators,
18 | dispatch
19 | );
20 | }
21 |
22 | // Use connect both here and in your components.
23 | // See: https://egghead.io/lessons/javascript-redux-generating-containers-with-connect-from-react-redux-visibletodolist
24 | const App = connect(
25 | mapStateToProps,
26 | mapDispatchToProps
27 | )(Main);
28 |
29 | export default App;
30 |
--------------------------------------------------------------------------------
/app/src/components/HacksmithsLogo/HacksmithsLogo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Hacksmiths from './hacksmiths.png';
3 | import styles from './HacksmithsLogo.module.scss';
4 | import cssModules from 'react-css-modules';
5 |
6 | const HacksmithsLogo = () => (
7 |
8 |

13 |
14 | );
15 |
16 | export default cssModules(HacksmithsLogo, styles);
17 |
--------------------------------------------------------------------------------
/app/src/components/HacksmithsLogo/HacksmithsLogo.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 100%;
3 | display: flex;
4 | align-items: center;
5 | justify-content: center;
6 | padding-top: 30px;
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/components/HacksmithsLogo/hacksmiths.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RyanCCollins/react-redux-simple-starter/67fcb738b2edb5ef88d7ac577c1cd266f0e852d3/app/src/components/HacksmithsLogo/hacksmiths.png
--------------------------------------------------------------------------------
/app/src/components/Main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Main = (props) => (
4 |
5 | {React.cloneElement(props.children, props)}
6 |
7 | );
8 |
9 | export default Main;
10 |
--------------------------------------------------------------------------------
/app/src/components/index.js:
--------------------------------------------------------------------------------
1 | // Import / export your componenents here.
2 | // Allows you to make modules that are easily imported as components
3 | // Like so:
4 | // import * as Components from '../components';
5 | // import { MyComponent } from '../components';
6 |
7 | import AmazingComponent from './AmazingComponent/AmazingComponent';
8 | import AmazingBox from './AmazingBox/AmazingBox';
9 | import AmazingInput from './AmazingInput/AmazingInput';
10 | import HacksmithsLogo from './HacksmithsLogo/HacksmithsLogo';
11 |
12 | export {
13 | AmazingComponent,
14 | AmazingBox,
15 | AmazingInput,
16 | HacksmithsLogo
17 | };
18 |
--------------------------------------------------------------------------------
/app/src/constants/amazingComponent.js:
--------------------------------------------------------------------------------
1 | export const ADD_BOX = 'ADD_BOX';
2 | export const REMOVE_BOX = 'REMOVE_BOX';
3 |
--------------------------------------------------------------------------------
/app/src/containers/MyAmazingContainer/MyAmazingContainer.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes, Component } from 'react';
2 | import { AmazingComponent } from 'components';
3 | import styles from './MyAmazingContainer.module.scss';
4 | import cssModules from 'react-css-modules';
5 | import * as AmazingActionCreators from '../../actions/amazingComponent';
6 | import { connect } from 'react-redux';
7 | import { bindActionCreators } from 'redux';
8 | import { HacksmithsLogo } from 'components';
9 |
10 | // Containers are used for managing state.
11 | // Whenever possible, write components as stateless functional
12 | // components and use classes for stateful containers.
13 | // There is also likely too much state here. The form
14 | // Input elements can be managed by redux form, for example.
15 | class MyAmazingContainer extends Component {
16 | constructor(props) {
17 | super(props);
18 | this.handleAddItem = this.handleAddItem.bind(this);
19 | this.handleRemoveItem = this.handleRemoveItem.bind(this);
20 | }
21 | handleAddItem(content) {
22 | const {
23 | actions
24 | } = this.props;
25 | actions.addBox(content);
26 | }
27 | handleRemoveItem(id) {
28 | const {
29 | actions
30 | } = this.props;
31 | actions.removeBox(id);
32 | }
33 | render() {
34 | const {
35 | boxes
36 | } = this.props;
37 | return (
38 |
39 |
40 |
React Redux Simple Starter
41 |
46 |
47 | );
48 | }
49 | }
50 |
51 | MyAmazingContainer.propTypes = {
52 | boxes: PropTypes.array.isRequired,
53 | actions: PropTypes.object.isRequired
54 | };
55 |
56 | // Standard React-Redux Magic to connect your state to local props
57 | // https://github.com/reactjs/react-redux/blob/master/docs/api.md
58 | const mapStateToProps = (state) => ({
59 | boxes: state.amazingComponent.boxes
60 | });
61 |
62 | // More connected component magic
63 | // https://egghead.io/lessons/javascript-redux-generating-containers-with-connect-from-react-redux-visibletodolist
64 | const mapDispatchToProps = (dispatch) => ({
65 | actions: bindActionCreators(AmazingActionCreators, dispatch)
66 | });
67 |
68 | // CSS Module magic to connect the hashed selectors
69 | // https://github.com/gajus/react-css-modules
70 | const StyledComponent = cssModules(MyAmazingContainer, styles);
71 |
72 | export default connect(
73 | mapStateToProps,
74 | mapDispatchToProps
75 | )(StyledComponent);
76 |
--------------------------------------------------------------------------------
/app/src/containers/MyAmazingContainer/MyAmazingContainer.module.scss:
--------------------------------------------------------------------------------
1 | .myAmazingContainer {
2 | background-image: linear-gradient(135deg, #ca2091 0, #a041fa 20%, #3f65fb 50%, #0fcbfa 80%, #0fe6fa 100%);
3 | min-height: 100vh;
4 | overflow: auto;
5 | }
6 |
7 | .bigTitle {
8 | text-align: center;
9 | margin-top: 20px;
10 | padding: 40px;
11 | text-transform: uppercase;
12 | color: white;
13 | max-width: 60%;
14 | margin-left: auto;
15 | margin-right: auto;
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/containers/index.js:
--------------------------------------------------------------------------------
1 | import MyAmazingContainer from './MyAmazingContainer/MyAmazingContainer';
2 |
3 | export {
4 | MyAmazingContainer
5 | };
6 |
--------------------------------------------------------------------------------
/app/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import router from './utils/router';
4 | import '../styles/styles.scss';
5 |
6 | render(router, document.getElementById('app'));
7 |
--------------------------------------------------------------------------------
/app/src/pages/LandingPage/LandingPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cssModules from 'react-css-modules';
3 | import styles from './LandingPage.module.scss';
4 | // Example to import a component using ES6 destructuring.
5 | import { MyAmazingContainer } from '../../containers';
6 |
7 | // Pages map directly to Routes, i.e. one page equals on Route
8 | // Handler that maps to a route in /utils/routes
9 | const LandingPage = (props) => (
10 |
11 |
14 |
15 | );
16 |
17 | export default cssModules(LandingPage, styles);
18 |
--------------------------------------------------------------------------------
/app/src/pages/LandingPage/LandingPage.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | height: 100vh;
3 | width: 100%
4 | };
5 |
6 | .header {
7 | font-size: 32px;
8 | font: 'Open Sans';
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/pages/NotFoundPage/NotFoundPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CSSModules from 'react-css-modules';
3 | import styles from './NotFoundPage.module.scss';
4 |
5 | const NotFound = () => (
6 |
7 |
Not Found
8 |
9 | );
10 |
11 | export default CSSModules(NotFound, styles);
12 |
--------------------------------------------------------------------------------
/app/src/pages/NotFoundPage/NotFoundPage.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | height: 100vh;
3 | width: 100%
4 | };
5 |
6 | .header {
7 | font-size: 32px;
8 | font: 'Open Sans';
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import NotFoundPage from './NotFoundPage/NotFoundPage';
2 | import LandingPage from './LandingPage/LandingPage'
3 |
4 | export {
5 | NotFoundPage,
6 | LandingPage
7 | };
8 |
--------------------------------------------------------------------------------
/app/src/reducers/amazingComponent.js:
--------------------------------------------------------------------------------
1 | import * as types from '../constants/amazingComponent';
2 |
3 | const amazingComponent = (state = {
4 | boxes: []
5 | }, action) => {
6 | switch (action.type) {
7 | case types.ADD_BOX:
8 | return Object.assign({}, state, {
9 | boxes: [
10 | ...state.boxes,
11 | action.content
12 | ]
13 | });
14 | case types.REMOVE_BOX:
15 | return Object.assign({}, state, {
16 | boxes: [
17 | ...state.boxes.slice(0, action.index),
18 | ...state.boxes.slice(action.index + 1)
19 | ]
20 | });
21 | default:
22 | return state;
23 | }
24 | };
25 |
26 | export default amazingComponent;
27 |
--------------------------------------------------------------------------------
/app/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { routerReducer } from 'react-router-redux';
3 | import { reducer as formReducer } from 'redux-form';
4 | import { reducer as toastrReducer } from 'react-redux-toastr';
5 |
6 | // Import the various reducers here:
7 | import amazingComponent from './amazingComponent';
8 |
9 | const rootReducer = combineReducers({
10 | // Apply all of the reducers here.
11 | amazingComponent,
12 | routing: routerReducer,
13 | form: formReducer,
14 | toastr: toastrReducer
15 | });
16 |
17 | export default rootReducer;
18 |
--------------------------------------------------------------------------------
/app/src/store/initialState.js:
--------------------------------------------------------------------------------
1 | const initalState = {
2 | amazingComponent: {
3 | boxes: ['Amazing', 'Super Amazing', 'Absolutely Amazing', 'Stunningly Amazing', 'Amazingly Amazing']
4 | }
5 | };
6 |
7 | export default initalState;
8 |
--------------------------------------------------------------------------------
/app/src/store/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, compose, applyMiddleware } from 'redux';
2 | import { syncHistoryWithStore } from 'react-router-redux';
3 | import thunk from 'redux-thunk';
4 | import { browserHistory } from 'react-router';
5 | import createLogger from 'redux-logger';
6 | import promiseMiddleware from 'redux-promise-middleware';
7 | import rootReducer from '../reducers/index';
8 | import initialState from './initialState';
9 |
10 | /* Commonly used middlewares and enhancers */
11 | /* See: http://redux.js.org/docs/advanced/Middleware.html*/
12 | const loggerMiddleware = createLogger();
13 | const middlewares = [thunk, promiseMiddleware(), loggerMiddleware];
14 |
15 | /* Everyone should use redux dev tools */
16 | /* https://github.com/gaearon/redux-devtools */
17 | /* https://medium.com/@meagle/understanding-87566abcfb7a */
18 | const enhancers = [];
19 | const devToolsExtension = window.devToolsExtension;
20 | if (typeof devToolsExtension === 'function') {
21 | enhancers.push(devToolsExtension());
22 | }
23 |
24 | const composedEnhancers = compose(
25 | applyMiddleware(...middlewares),
26 | ...enhancers
27 | );
28 |
29 | /* Hopefully by now you understand what a store is and how redux uses them,
30 | * But if not, take a look at: https://github.com/reactjs/redux/blob/master/docs/api/createStore.md
31 | * And https://egghead.io/lessons/javascript-redux-implementing-store-from-scratch
32 | */
33 | const store = createStore(
34 | rootReducer,
35 | initialState,
36 | composedEnhancers,
37 | );
38 |
39 | /* See: https://github.com/reactjs/react-router-redux/issues/305 */
40 | export const history = syncHistoryWithStore(browserHistory, store);
41 |
42 | /* Hot reloading of reducers. How futuristic!! */
43 | if (module.hot) {
44 | module.hot.accept('../reducers/', () => {
45 | const nextRootReducer = require('../reducers/index').default;
46 | store.replaceReducer(nextRootReducer);
47 | });
48 | }
49 |
50 | export default store;
51 |
--------------------------------------------------------------------------------
/app/src/utils/network.js:
--------------------------------------------------------------------------------
1 | // Brilliant network wrapper courteousy of
2 | // https://github.com/pburtchaell/redux-promise-middleware
3 | // Works to create requests to a network resource and wraps the requests in
4 | // A promise. This works great with redux promise middlewares
5 | // See the actions for post to see it in action located in ../actions/post.js
6 | import request from './request';
7 |
8 | /**
9 | * @function Network
10 | * @description Factory function to create a object that can send
11 | * requests to a specific resource on the server.
12 | * @param {string} resource The resource used for config
13 | */
14 | const Network = resource => {
15 | const buildUrl = ({ id, resource } = {}) => {
16 | const parameters = [
17 | 'http://localhost:3000',
18 | 'api'
19 | ];
20 |
21 | if (resource) parameters.concat([resource]);
22 | if (id) parameters.concat([id]);
23 | return parameters.join('/');
24 | };
25 |
26 | // Default option for every request
27 | const defaultOptions = {
28 | mode: 'cors',
29 | headers: {
30 | 'Accept': 'application/json',
31 | 'Content-Type': 'application/json'
32 | }
33 | };
34 |
35 | return {
36 |
37 | /**
38 | * @function post
39 | * @description Make a POST request.
40 | * @param {string} path
41 | * @param {object} body
42 | * @param {object} options
43 | * @returns {promise}
44 | */
45 | post: (path, body, options = {}) => {
46 | return request(buildUrl(path), Object.assign(
47 | options,
48 | defaultOptions,
49 | {
50 | method: 'POST',
51 | body: JSON.stringify(body)
52 | }
53 | ));
54 | },
55 |
56 | /**
57 | * @function get
58 | * @description Make a GET request.
59 | * @param {string} path
60 | * @param {object} options
61 | * @returns {promise}
62 | */
63 | get: (path, options = {}) => {
64 | return request(buildUrl(path), Object.assign(
65 | options,
66 | defaultOptions,
67 | { method: 'GET' }
68 | ));
69 | },
70 |
71 | /**
72 | * @function edit
73 | * @description Make a PUT request.
74 | * @param {string} path
75 | * @param {object} body
76 | * @param {object} options
77 | * @returns {promise}
78 | */
79 | update: (path, body, options = {}) => {
80 | return request(buildUrl(path), Object.assign(
81 | options,
82 | defaultOptions,
83 | { method: 'PUT' }
84 | ));
85 | },
86 |
87 | /**
88 | * @function delete
89 | * @description Make a DELETE request.
90 | * @param {string} path
91 | * @param {object} options
92 | * @returns {promise}
93 | */
94 | delete: (path, options = {}) => {
95 | return request(buildUrl(path), Object.assign(
96 | options,
97 | defaultOptions,
98 | { method: 'DELETE' }
99 | ));
100 | },
101 |
102 | ping: () => request(buildUrl(), { method: 'GET' })
103 | };
104 | };
105 |
106 | export default Network;
107 |
--------------------------------------------------------------------------------
/app/src/utils/request.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @private
3 | * @function request
4 | * @description Make a request to the server and return a promise.
5 | * @param {string} url
6 | * @param {object} options
7 | * @returns {promise}
8 | */
9 | export default function request(url, options) {
10 | return new Promise((resolve, reject) => {
11 | if (!url) reject(new Error('URL parameter required'));
12 | if (!options) reject(new Error('Options parameter required'));
13 |
14 | fetch(url, options)
15 | .then(response => response.json())
16 | .then(response => {
17 | if (response.errors) reject(response.errors);
18 | else resolve(response);
19 | })
20 | .catch(reject);
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/utils/router.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Router, Route, IndexRoute } from 'react-router';
3 | import { Provider } from 'react-redux';
4 | import store, { history } from '../store/store';
5 | import App from '../components/App';
6 | import * as Pages from '../pages/';
7 | import ReduxToastr from 'react-redux-toastr';
8 |
9 | const router = (
10 |
11 |
12 |
17 |
18 |
19 |
20 | {/* Examples of routes here.
21 |
22 |
23 |
24 | */}
25 |
26 |
27 |
28 |
29 |
30 | );
31 |
32 | export default router;
33 |
--------------------------------------------------------------------------------
/app/styles/settings.scss:
--------------------------------------------------------------------------------
1 | // Foundation for Sites Settings
2 | // Note, this is totally optional. I like using foundation, but if you don't just ignore it.
3 | // -----------------------------
4 | //
5 | // Table of Contents:
6 | //
7 | // 1. Global
8 | // 2. Breakpoints
9 | // 3. The Grid
10 | // 4. Base Typography
11 | // 5. Typography Helpers
12 | // 6. Abide
13 | // 7. Accordion
14 | // 8. Accordion Menu
15 | // 9. Badge
16 | // 10. Breadcrumbs
17 | // 11. Button
18 | // 12. Button Group
19 | // 13. Callout
20 | // 14. Close Button
21 | // 15. Drilldown
22 | // 16. Dropdown
23 | // 17. Dropdown Menu
24 | // 18. Flex Video
25 | // 19. Forms
26 | // 20. Label
27 | // 21. Media Object
28 | // 22. Menu
29 | // 23. Meter
30 | // 24. Off-canvas
31 | // 25. Orbit
32 | // 26. Pagination
33 | // 27. Progress Bar
34 | // 28. Reveal
35 | // 29. Slider
36 | // 30. Switch
37 | // 31. Table
38 | // 32. Tabs
39 | // 33. Thumbnail
40 | // 34. Title Bar
41 | // 35. Tooltip
42 | // 36. Top Bar
43 |
44 | @import '../../node_modules/foundation-sites/scss/util/util';
45 |
46 | // 1. Global
47 | // ---------
48 |
49 | $global-font-size: 100%;
50 | $global-width: rem-calc(1200);
51 | $global-lineheight: 1.5;
52 | $foundation-palette: (
53 | primary: #2199e8,
54 | secondary: #777,
55 | success: #3adb76,
56 | warning: #ffae00,
57 | alert: #ec5840,
58 | );
59 | $light-gray: #e6e6e6;
60 | $medium-gray: #cacaca;
61 | $dark-gray: #8a8a8a;
62 | $black: #0a0a0a;
63 | $white: #fefefe;
64 | $body-background: $white;
65 | $body-font-color: $black;
66 | $body-font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif;
67 | $body-antialiased: true;
68 | $global-margin: 1rem;
69 | $global-padding: 1rem;
70 | $global-weight-normal: normal;
71 | $global-weight-bold: bold;
72 | $global-radius: 0;
73 | $global-text-direction: ltr;
74 | $global-flexbox: false;
75 | $print-transparent-backgrounds: true;
76 |
77 | @include add-foundation-colors;
78 |
79 | // 2. Breakpoints
80 | // --------------
81 |
82 | $breakpoints: (
83 | small: 0,
84 | medium: 640px,
85 | large: 1024px,
86 | xlarge: 1200px,
87 | xxlarge: 1440px,
88 | );
89 | $breakpoint-classes: (small medium large);
90 |
91 | // 3. The Grid
92 | // -----------
93 |
94 | $grid-row-width: $global-width;
95 | $grid-column-count: 12;
96 | $grid-column-gutter: (
97 | small: 20px,
98 | medium: 30px,
99 | );
100 | $grid-column-align-edge: true;
101 | $block-grid-max: 8;
102 |
103 | // 4. Base Typography
104 | // ------------------
105 |
106 | $header-font-family: $body-font-family;
107 | $header-font-weight: $global-weight-normal;
108 | $header-font-style: normal;
109 | $font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace;
110 | $header-sizes: (
111 | small: (
112 | 'h1': 24,
113 | 'h2': 20,
114 | 'h3': 19,
115 | 'h4': 18,
116 | 'h5': 17,
117 | 'h6': 16,
118 | ),
119 | medium: (
120 | 'h1': 48,
121 | 'h2': 40,
122 | 'h3': 31,
123 | 'h4': 25,
124 | 'h5': 20,
125 | 'h6': 16,
126 | ),
127 | );
128 | $header-color: inherit;
129 | $header-lineheight: 1.4;
130 | $header-margin-bottom: 0.5rem;
131 | $header-text-rendering: optimizeLegibility;
132 | $small-font-size: 80%;
133 | $header-small-font-color: $medium-gray;
134 | $paragraph-lineheight: 1.6;
135 | $paragraph-margin-bottom: 1rem;
136 | $paragraph-text-rendering: optimizeLegibility;
137 | $code-color: $black;
138 | $code-font-family: $font-family-monospace;
139 | $code-font-weight: $global-weight-normal;
140 | $code-background: $light-gray;
141 | $code-border: 1px solid $medium-gray;
142 | $code-padding: rem-calc(2 5 1);
143 | $anchor-color: $primary-color;
144 | $anchor-color-hover: scale-color($anchor-color, $lightness: -14%);
145 | $anchor-text-decoration: none;
146 | $anchor-text-decoration-hover: none;
147 | $hr-width: $global-width;
148 | $hr-border: 1px solid $medium-gray;
149 | $hr-margin: rem-calc(20) auto;
150 | $list-lineheight: $paragraph-lineheight;
151 | $list-margin-bottom: $paragraph-margin-bottom;
152 | $list-style-type: disc;
153 | $list-style-position: outside;
154 | $list-side-margin: 1.25rem;
155 | $list-nested-side-margin: 1.25rem;
156 | $defnlist-margin-bottom: 1rem;
157 | $defnlist-term-weight: $global-weight-bold;
158 | $defnlist-term-margin-bottom: 0.3rem;
159 | $blockquote-color: $dark-gray;
160 | $blockquote-padding: rem-calc(9 20 0 19);
161 | $blockquote-border: 1px solid $medium-gray;
162 | $cite-font-size: rem-calc(13);
163 | $cite-color: $dark-gray;
164 | $keystroke-font: $font-family-monospace;
165 | $keystroke-color: $black;
166 | $keystroke-background: $light-gray;
167 | $keystroke-padding: rem-calc(2 4 0);
168 | $keystroke-radius: $global-radius;
169 | $abbr-underline: 1px dotted $black;
170 |
171 | // 5. Typography Helpers
172 | // ---------------------
173 |
174 | $lead-font-size: $global-font-size * 1.25;
175 | $lead-lineheight: 1.6;
176 | $subheader-lineheight: 1.4;
177 | $subheader-color: $dark-gray;
178 | $subheader-font-weight: $global-weight-normal;
179 | $subheader-margin-top: 0.2rem;
180 | $subheader-margin-bottom: 0.5rem;
181 | $stat-font-size: 2.5rem;
182 |
183 | // 6. Abide
184 | // --------
185 |
186 | $abide-inputs: true;
187 | $abide-labels: true;
188 | $input-background-invalid: map-get($foundation-palette, alert);
189 | $form-label-color-invalid: map-get($foundation-palette, alert);
190 | $input-error-color: map-get($foundation-palette, alert);
191 | $input-error-font-size: rem-calc(12);
192 | $input-error-font-weight: $global-weight-bold;
193 |
194 | // 7. Accordion
195 | // ------------
196 |
197 | $accordion-background: $white;
198 | $accordion-plusminus: true;
199 | $accordion-item-color: foreground($accordion-background, $primary-color);
200 | $accordion-item-background-hover: $light-gray;
201 | $accordion-item-padding: 1.25rem 1rem;
202 | $accordion-content-background: $white;
203 | $accordion-content-border: 1px solid $light-gray;
204 | $accordion-content-color: foreground($accordion-content-background, $body-font-color);
205 | $accordion-content-padding: 1rem;
206 |
207 | // 8. Accordion Menu
208 | // -----------------
209 |
210 | $accordionmenu-arrows: true;
211 | $accordionmenu-arrow-color: $primary-color;
212 |
213 | // 9. Badge
214 | // --------
215 |
216 | $badge-background: $primary-color;
217 | $badge-color: foreground($badge-background);
218 | $badge-padding: 0.3em;
219 | $badge-minwidth: 2.1em;
220 | $badge-font-size: 0.6rem;
221 |
222 | // 10. Breadcrumbs
223 | // ---------------
224 |
225 | $breadcrumbs-margin: 0 0 $global-margin 0;
226 | $breadcrumbs-item-font-size: rem-calc(11);
227 | $breadcrumbs-item-color: $primary-color;
228 | $breadcrumbs-item-color-current: $black;
229 | $breadcrumbs-item-color-disabled: $medium-gray;
230 | $breadcrumbs-item-margin: 0.75rem;
231 | $breadcrumbs-item-uppercase: true;
232 | $breadcrumbs-item-slash: true;
233 |
234 | // 11. Button
235 | // ----------
236 |
237 | $button-padding: 0.85em 1em;
238 | $button-margin: 0 0 $global-margin 0;
239 | $button-fill: solid;
240 | $button-background: $primary-color;
241 | $button-background-hover: scale-color($button-background, $lightness: -15%);
242 | $button-color: $white;
243 | $button-color-alt: $black;
244 | $button-radius: $global-radius;
245 | $button-sizes: (
246 | tiny: 0.6rem,
247 | small: 0.75rem,
248 | default: 0.9rem,
249 | large: 1.25rem,
250 | );
251 | $button-opacity-disabled: 0.25;
252 |
253 | // 12. Button Group
254 | // ----------------
255 |
256 | $buttongroup-margin: 1rem;
257 | $buttongroup-spacing: 1px;
258 | $buttongroup-child-selector: '.button';
259 | $buttongroup-expand-max: 6;
260 |
261 | // 13. Callout
262 | // -----------
263 |
264 | $callout-background: $white;
265 | $callout-background-fade: 85%;
266 | $callout-border: 1px solid rgba($black, 0.25);
267 | $callout-margin: 0 0 1rem 0;
268 | $callout-padding: 1rem;
269 | $callout-font-color: $body-font-color;
270 | $callout-font-color-alt: $body-background;
271 | $callout-radius: $global-radius;
272 | $callout-link-tint: 30%;
273 |
274 | // 14. Close Button
275 | // ----------------
276 |
277 | $closebutton-position: right top;
278 | $closebutton-offset-horizontal: 1rem;
279 | $closebutton-offset-vertical: 0.5rem;
280 | $closebutton-size: 2em;
281 | $closebutton-lineheight: 1;
282 | $closebutton-color: $dark-gray;
283 | $closebutton-color-hover: $black;
284 |
285 | // 15. Drilldown
286 | // -------------
287 |
288 | $drilldown-transition: transform 0.15s linear;
289 | $drilldown-arrows: true;
290 | $drilldown-arrow-color: $primary-color;
291 | $drilldown-background: $white;
292 |
293 | // 16. Dropdown
294 | // ------------
295 |
296 | $dropdown-padding: 1rem;
297 | $dropdown-border: 1px solid $medium-gray;
298 | $dropdown-font-size: 1rem;
299 | $dropdown-width: 300px;
300 | $dropdown-radius: $global-radius;
301 | $dropdown-sizes: (
302 | tiny: 100px,
303 | small: 200px,
304 | large: 400px,
305 | );
306 |
307 | // 17. Dropdown Menu
308 | // -----------------
309 |
310 | $dropdownmenu-arrows: true;
311 | $dropdownmenu-arrow-color: $anchor-color;
312 | $dropdownmenu-min-width: 200px;
313 | $dropdownmenu-background: $white;
314 | $dropdownmenu-border: 1px solid $medium-gray;
315 |
316 | // 18. Flex Video
317 | // --------------
318 |
319 | $flexvideo-margin-bottom: rem-calc(16);
320 | $flexvideo-ratio: 4 by 3;
321 | $flexvideo-ratio-widescreen: 16 by 9;
322 |
323 | // 19. Forms
324 | // ---------
325 |
326 | $fieldset-border: 1px solid $medium-gray;
327 | $fieldset-padding: rem-calc(20);
328 | $fieldset-margin: rem-calc(18 0);
329 | $legend-padding: rem-calc(0 3);
330 | $form-spacing: rem-calc(16);
331 | $helptext-color: $black;
332 | $helptext-font-size: rem-calc(13);
333 | $helptext-font-style: italic;
334 | $input-prefix-color: $black;
335 | $input-prefix-background: $light-gray;
336 | $input-prefix-border: 1px solid $medium-gray;
337 | $input-prefix-padding: 1rem;
338 | $form-label-color: $black;
339 | $form-label-font-size: rem-calc(14);
340 | $form-label-font-weight: $global-weight-normal;
341 | $form-label-line-height: 1.8;
342 | $select-background: $white;
343 | $select-triangle-color: $dark-gray;
344 | $select-radius: $global-radius;
345 | $input-color: $black;
346 | $input-placeholder-color: $medium-gray;
347 | $input-font-family: inherit;
348 | $input-font-size: rem-calc(16);
349 | $input-background: $white;
350 | $input-background-focus: $white;
351 | $input-background-disabled: $light-gray;
352 | $input-border: 1px solid $medium-gray;
353 | $input-border-focus: 1px solid $dark-gray;
354 | $input-shadow: inset 0 1px 2px rgba($black, 0.1);
355 | $input-shadow-focus: 0 0 5px $medium-gray;
356 | $input-cursor-disabled: not-allowed;
357 | $input-transition: box-shadow 0.5s, border-color 0.25s ease-in-out;
358 | $input-number-spinners: true;
359 | $input-radius: $global-radius;
360 |
361 | // 20. Label
362 | // ---------
363 |
364 | $label-background: $primary-color;
365 | $label-color: foreground($label-background);
366 | $label-font-size: 0.8rem;
367 | $label-padding: 0.33333rem 0.5rem;
368 | $label-radius: $global-radius;
369 |
370 | // 21. Media Object
371 | // ----------------
372 |
373 | $mediaobject-margin-bottom: $global-margin;
374 | $mediaobject-section-padding: $global-padding;
375 | $mediaobject-image-width-stacked: 100%;
376 |
377 | // 22. Menu
378 | // --------
379 |
380 | $menu-margin: 0;
381 | $menu-margin-nested: 1rem;
382 | $menu-item-padding: 0.7rem 1rem;
383 | $menu-item-color-active: $white;
384 | $menu-item-background-active: map-get($foundation-palette, primary);
385 | $menu-icon-spacing: 0.25rem;
386 |
387 | // 23. Meter
388 | // ---------
389 |
390 | $meter-height: 1rem;
391 | $meter-radius: $global-radius;
392 | $meter-background: $medium-gray;
393 | $meter-fill-good: $success-color;
394 | $meter-fill-medium: $warning-color;
395 | $meter-fill-bad: $alert-color;
396 |
397 | // 24. Off-canvas
398 | // --------------
399 |
400 | $offcanvas-size: 250px;
401 | $offcanvas-background: $light-gray;
402 | $offcanvas-zindex: -1;
403 | $offcanvas-transition-length: 0.5s;
404 | $offcanvas-transition-timing: ease;
405 | $offcanvas-fixed-reveal: true;
406 | $offcanvas-exit-background: rgba($white, 0.25);
407 | $maincontent-class: 'off-canvas-content';
408 | $maincontent-shadow: 0 0 10px rgba($black, 0.5);
409 |
410 | // 25. Orbit
411 | // ---------
412 |
413 | $orbit-bullet-background: $medium-gray;
414 | $orbit-bullet-background-active: $dark-gray;
415 | $orbit-bullet-diameter: 1.2rem;
416 | $orbit-bullet-margin: 0.1rem;
417 | $orbit-bullet-margin-top: 0.8rem;
418 | $orbit-bullet-margin-bottom: 0.8rem;
419 | $orbit-caption-background: rgba($black, 0.5);
420 | $orbit-caption-padding: 1rem;
421 | $orbit-control-background-hover: rgba($black, 0.5);
422 | $orbit-control-padding: 1rem;
423 | $orbit-control-zindex: 10;
424 |
425 | // 26. Pagination
426 | // --------------
427 |
428 | $pagination-font-size: rem-calc(14);
429 | $pagination-margin-bottom: $global-margin;
430 | $pagination-item-color: $black;
431 | $pagination-item-padding: rem-calc(3 10);
432 | $pagination-item-spacing: rem-calc(1);
433 | $pagination-radius: $global-radius;
434 | $pagination-item-background-hover: $light-gray;
435 | $pagination-item-background-current: $primary-color;
436 | $pagination-item-color-current: foreground($pagination-item-background-current);
437 | $pagination-item-color-disabled: $medium-gray;
438 | $pagination-ellipsis-color: $black;
439 | $pagination-mobile-items: false;
440 | $pagination-arrows: true;
441 |
442 | // 27. Progress Bar
443 | // ----------------
444 |
445 | $progress-height: 1rem;
446 | $progress-background: $medium-gray;
447 | $progress-margin-bottom: $global-margin;
448 | $progress-meter-background: $primary-color;
449 | $progress-radius: $global-radius;
450 |
451 | // 28. Reveal
452 | // ----------
453 |
454 | $reveal-background: $white;
455 | $reveal-width: 600px;
456 | $reveal-max-width: $global-width;
457 | $reveal-padding: $global-padding;
458 | $reveal-border: 1px solid $medium-gray;
459 | $reveal-radius: $global-radius;
460 | $reveal-zindex: 1005;
461 | $reveal-overlay-background: rgba($black, 0.45);
462 |
463 | // 29. Slider
464 | // ----------
465 |
466 | $slider-width-vertical: 0.5rem;
467 | $slider-transition: all 0.2s ease-in-out;
468 | $slider-height: 0.5rem;
469 | $slider-background: $light-gray;
470 | $slider-fill-background: $medium-gray;
471 | $slider-handle-height: 1.4rem;
472 | $slider-handle-width: 1.4rem;
473 | $slider-handle-background: $primary-color;
474 | $slider-opacity-disabled: 0.25;
475 | $slider-radius: $global-radius;
476 |
477 | // 30. Switch
478 | // ----------
479 |
480 | $switch-background: $medium-gray;
481 | $switch-background-active: $primary-color;
482 | $switch-height: 2rem;
483 | $switch-height-tiny: 1.5rem;
484 | $switch-height-small: 1.75rem;
485 | $switch-height-large: 2.5rem;
486 | $switch-radius: $global-radius;
487 | $switch-margin: $global-margin;
488 | $switch-paddle-background: $white;
489 | $switch-paddle-offset: 0.25rem;
490 | $switch-paddle-radius: $global-radius;
491 | $switch-paddle-transition: all 0.25s ease-out;
492 |
493 | // 31. Table
494 | // ---------
495 |
496 | $table-background: $white;
497 | $table-color-scale: 5%;
498 | $table-border: 1px solid smart-scale($table-background, $table-color-scale);
499 | $table-padding: rem-calc(8 10 10);
500 | $table-hover-scale: 2%;
501 | $table-row-hover: darken($table-background, $table-hover-scale);
502 | $table-row-stripe-hover: darken($table-background, $table-color-scale + $table-hover-scale);
503 | $table-striped-background: smart-scale($table-background, $table-color-scale);
504 | $table-stripe: even;
505 | $table-head-background: smart-scale($table-background, $table-color-scale / 2);
506 | $table-foot-background: smart-scale($table-background, $table-color-scale);
507 | $table-head-font-color: $body-font-color;
508 | $table-foot-font-color: $body-font-color;
509 | $show-header-for-stacked: false;
510 |
511 | // 32. Tabs
512 | // --------
513 |
514 | $tab-margin: 0;
515 | $tab-background: $white;
516 | $tab-background-active: $light-gray;
517 | $tab-item-font-size: rem-calc(12);
518 | $tab-item-background-hover: $white;
519 | $tab-item-padding: 1.25rem 1.5rem;
520 | $tab-expand-max: 6;
521 | $tab-content-background: $white;
522 | $tab-content-border: $light-gray;
523 | $tab-content-color: foreground($tab-background, $primary-color);
524 | $tab-content-padding: 1rem;
525 |
526 | // 33. Thumbnail
527 | // -------------
528 |
529 | $thumbnail-border: solid 4px $white;
530 | $thumbnail-margin-bottom: $global-margin;
531 | $thumbnail-shadow: 0 0 0 1px rgba($black, 0.2);
532 | $thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5);
533 | $thumbnail-transition: box-shadow 200ms ease-out;
534 | $thumbnail-radius: $global-radius;
535 |
536 | // 34. Title Bar
537 | // -------------
538 |
539 | $titlebar-background: $black;
540 | $titlebar-color: $white;
541 | $titlebar-padding: 0.5rem;
542 | $titlebar-text-font-weight: bold;
543 | $titlebar-icon-color: $white;
544 | $titlebar-icon-color-hover: $medium-gray;
545 | $titlebar-icon-spacing: 0.25rem;
546 |
547 | // 35. Tooltip
548 | // -----------
549 |
550 | $has-tip-font-weight: $global-weight-bold;
551 | $has-tip-border-bottom: dotted 1px $dark-gray;
552 | $tooltip-background-color: $black;
553 | $tooltip-color: $white;
554 | $tooltip-padding: 0.75rem;
555 | $tooltip-font-size: $small-font-size;
556 | $tooltip-pip-width: 0.75rem;
557 | $tooltip-pip-height: $tooltip-pip-width * 0.866;
558 | $tooltip-radius: $global-radius;
559 |
560 | // 36. Top Bar
561 | // -----------
562 |
563 | $topbar-padding: 0.5rem;
564 | $topbar-background: rgba(255, 255, 255, 0.9);
565 | $topbar-dropdown-bg: $topbar-background;
566 | $topbar-submenu-background: $topbar-background;
567 | $topbar-title-spacing: 1rem;
568 | $topbar-input-width: 200px;
569 | $topbar-unstack-breakpoint: medium;
570 |
--------------------------------------------------------------------------------
/app/styles/styles.scss:
--------------------------------------------------------------------------------
1 | // Delete if you don't want to use foundation and don't need any starter styles.
2 | @import "../../node_modules/foundation-sites/scss/foundation";
3 | @import "./settings.scss";
4 |
5 | // Global Styles defined and foundation classes
6 | // Just delete this if you don't want to use it!
7 | // NOTE: Stylesheets will be loaded by Sass loader, unless the name includes a .module
8 | // postfix, i.e.: MyModule.module.scss. This is to allow you to use the best of both worlds
9 | // i.e. css / sass modules and global styles with imports
10 | @include foundation-flex-grid;
11 | @include foundation-flex-classes;
12 | @include foundation-everything;
13 |
14 | $primary-color: rgba(0, 216, 255, .9);
15 | $off-white: #E9EAEC;
16 | $light-grey: #a1a1a1;
17 |
18 | body {
19 | font-family: Lato,sans-serif;
20 | font-size: 16px;
21 | line-height: 25px;
22 | overflow-x: hidden;
23 | }
24 |
25 | .centered {
26 | text-align: center;
27 | }
28 |
29 | .dark-grey {
30 | color: #454545;
31 | }
32 |
33 | .text-medium {
34 | font-size: 1.4rem;
35 | line-height: 1.42857143;
36 | }
37 |
38 | .section-header {
39 | font-size: 2rem;
40 | text-align: center;
41 | text-transform: uppercase;
42 | color: #454545;
43 | font-family: 'Raleway';
44 | letter-spacing: 1px;
45 | padding: 40px;
46 | padding-top: 100px;
47 | }
48 |
49 | .section-subheader {
50 | color: $light-grey;
51 | font-size: 18px;
52 | font-style: italic;
53 | text-align: center;
54 | }
55 |
56 | span.divider {
57 | background-color: $primary-color;
58 | width: 150px;
59 | height: 3px;
60 | display: block;
61 | margin: 35px 0;
62 | margin-right: auto;
63 | margin-left: auto;
64 | }
65 |
66 | .section__off-white {
67 | background: $off-white;
68 | }
69 |
70 | .gradient-green {
71 | background-image: linear-gradient(45deg, rgba(0, 216, 255, .5), rgba(0, 255, 127,.5));
72 | }
73 |
74 | .gradient-purple {
75 | background-image: linear-gradient(135deg, #ca2091 0, #a041fa 20%, #3f65fb 50%, #0fcbfa 80%, #0fe6fa 100%);
76 | }
77 |
78 | .gradient-blue {
79 | background: linear-gradient(to bottom, #FFE1FF 0%, #C9DAF8 100%);
80 | }
81 | .uppercase {
82 | text-transform: uppercase;
83 | }
84 |
85 | .text-white {
86 | color: white;
87 | }
88 |
89 | .paragraph-text {
90 | font-family: 'Open Sans';
91 | font-size: 1.3rem;
92 | padding: 10px;
93 | }
94 |
95 | .icon-medium {
96 | font-size: 2rem;
97 | }
98 |
99 | .icon-small {
100 | font-size: 1.5rem;
101 | }
102 |
103 | .paper__fit {
104 | position: relative;
105 | display: inline-block;
106 | vertical-align: top;
107 | background-color: #fff;
108 | box-shadow: 0 12px 15px 0 rgba(0, 0, 0, 0.24);
109 | padding: 60px;
110 | width: 100%;
111 | min-height: 200px;
112 | }
113 |
114 | .huge-space {
115 | margin-bottom: 200px;
116 | }
117 |
118 | .tilted-background:before {
119 | content: '';
120 | position: absolute;
121 | top: -9px;
122 | left: -44px;
123 | right: -44px;
124 | height: 44px;
125 | background: #f4f5be;
126 | transform: rotate(-2deg);
127 | }
128 |
129 | .paragraph-header-text {
130 | margin: 0;
131 | font-size: 29px;
132 | line-height: 1.35;
133 | color: #333c4e;
134 | font-weight: 300;
135 | }
136 |
137 | .description-text {
138 | font-family: 'Open Sans';
139 | text-align: justify;
140 | h4 {
141 | margin: 0;
142 | font-size: 29px;
143 | line-height: 1.35;
144 | color: #333c4e;
145 | font-weight: 300;
146 | }
147 |
148 | p {
149 | margin: 0;
150 | font-size: 23px;
151 | color: #7e8890;
152 | font-weight: 300;
153 | }
154 | }
155 |
156 | hr.section-divider {
157 | margin-top: 52px;
158 | margin-bottom: 42px;
159 | display: block;
160 | border: 0;
161 | text-align: center;
162 | }
163 |
164 | hr.section-divider:before {
165 | font-family: 'Open Sans';
166 | font-weight: 400;
167 | font-style: italic;
168 | font-size: 28px;
169 | letter-spacing: .6em;
170 | }
171 |
172 | hr.section-divider:before {
173 | content: '...';
174 | display: inline-block;
175 | margin-left: .6em;
176 | color: rgba(0,0,0,.6);
177 | position: relative;
178 | top: -30px;
179 | }
180 |
181 | .container {
182 | margin-right: auto;
183 | margin-left: auto;
184 | padding-left: 15px;
185 | padding-right: 15px;
186 | @media (min-width: 992px) {
187 | width: 1000px;
188 | }
189 | }
190 |
191 | .form-field {
192 | display: block;
193 | box-shadow: none;
194 | position: relative;
195 | padding: 6px 24px;
196 | border: 1px solid rgba(0,0,0,.15);
197 | margin-bottom: -1px;
198 | background-color: #fff;
199 | color: #333;
200 | opacity: 1;
201 | min-width: 300px;
202 | max-width: 400px;
203 | }
204 |
205 | label.form-field {
206 | display: block;
207 | font-size: 14px;
208 | font-size: .875rem;
209 | line-height: 24px;
210 | color: #666;
211 | cursor: pointer;
212 | }
213 |
214 | span.form-field__contents {
215 | display: block;
216 | margin-left: -24px;
217 | margin-right: -24px;
218 | }
219 |
220 | .form-field input[type=text] {
221 | display: block;
222 | width: 100%;
223 | border: none;
224 | padding: 0 24px;
225 | border-radius: 0;
226 | font-size: 16px;
227 | font-size: 1rem;
228 | line-height: 1.5;
229 | }
230 |
231 | .fadeIn-appear {
232 | opacity: 0.01;
233 | }
234 |
235 | .fadeIn-appear.fadeIn-appear-active {
236 | opacity: 1;
237 | transition: opacity .5s ease-in;
238 | }
239 |
240 | .fadeIn-enter {
241 | opacity: 0.01;
242 | }
243 |
244 | .fadeIn-enter.fadeIn-enter-active {
245 | opacity: 1;
246 | transition: opacity 500ms ease-in;
247 | }
248 |
249 | .fadeIn-leave {
250 | opacity: 1;
251 | }
252 |
253 | .fadeIn-leave.fadeIn-leave-active {
254 | opacity: 0.01;
255 | transition: opacity 600ms ease-in;
256 | }
257 |
--------------------------------------------------------------------------------
/app/tests/actions/amazingComponent.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import * as actions from '../../src/actions/amazingComponent';
3 | import * as types from '../../src/constants/amazingComponent';
4 |
5 | describe('actions', () => {
6 | it('should create an action to add a box to the boxes list.', () => {
7 | const content = 'Amazing content!!';
8 | const expectedAction = {
9 | type: types.ADD_BOX,
10 | content
11 | };
12 | expect(
13 | actions.addBox(content)
14 | ).toEqual(expectedAction);
15 | });
16 | it('should create an action to remove a box from the specified index in the boxes list.', () => {
17 | const index = 1;
18 | const expectedAction = {
19 | type: types.REMOVE_BOX,
20 | index
21 | };
22 | expect(
23 | actions.removeBox(index)
24 | ).toEqual(expectedAction);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/app/tests/components/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RyanCCollins/react-redux-simple-starter/67fcb738b2edb5ef88d7ac577c1cd266f0e852d3/app/tests/components/.gitkeep
--------------------------------------------------------------------------------
/app/tests/reducers/amazingComponent.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import * as types from '../../src/constants/amazingComponent';
3 | import reducer from '../../src/reducers/amazingComponent';
4 |
5 | describe('amazingComponent reducer', () => {
6 | it('should return an object with an empty array under the key boxes (initial state)', () => {
7 | expect(
8 | reducer(undefined, {})
9 | ).toEqual({
10 | boxes: []
11 | });
12 | });
13 |
14 | it('should add an item to the box array', () => {
15 | expect(
16 | reducer(
17 | {
18 | boxes: []
19 | },
20 | {
21 | type: types.ADD_BOX,
22 | content: 'Super awesome content'
23 | }
24 | )
25 | ).toEqual(
26 | {
27 | boxes: [
28 | 'Super awesome content'
29 | ]
30 | }
31 | );
32 | });
33 | it('should remove the box with the specified index from the box array', () => {
34 | const stateBefore = {
35 | boxes: ['Amazing!', 'Awesome!', 'Super Cool!']
36 | };
37 | const stateAfter = {
38 | boxes: ['Amazing!', 'Super Cool!']
39 | };
40 | const index = 1;
41 | expect(
42 | reducer(stateBefore, {
43 | type: types.REMOVE_BOX,
44 | index
45 | })
46 | ).toEqual(stateAfter);
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/app/tests/test_helper.js:
--------------------------------------------------------------------------------
1 | import jsdom from 'jsdom';
2 |
3 | const doc = jsdom.jsdom('');
4 | const win = doc.defaultView;
5 |
6 | global.document = doc;
7 | global.window = win;
8 |
9 | Object.keys(window).forEach((key) => {
10 | if (!(key in global)) {
11 | global[key] = window[key];
12 | }
13 | });
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React Redux Simple Starter
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-simple-starter",
3 | "version": "1.0.0",
4 | "description": "A simple react redux starter project",
5 | "main": "index.js",
6 | "babel": {
7 | "presets": [
8 | "es2015",
9 | "react",
10 | "stage-0"
11 | ]
12 | },
13 | "scripts": {
14 | "test": "`npm bin`/mocha --compilers js:babel-core/register --require ./app/tests/test_helper.js '*/tests/**/*.@(js|jsx)'",
15 | "test:watch": "npm run test -- --watch",
16 | "build": "`npm bin`/webpack",
17 | "dev": "`npm bin`/webpack-dev-server",
18 | "lint": "`npm bin`/eslint . --ext .js --ext .jsx",
19 | "deploy": "NODE_ENV=production `npm bin`/webpack -p",
20 | "start": "npm run dev",
21 | "clean": "rm -rf app/dist app/build",
22 | "setup": "npm install"
23 | },
24 | "repository": {
25 | "type": "git",
26 | "url": "git+https://github.com/RyanCCollins/react-redux-simple-starter.git"
27 | },
28 | "keywords": [
29 | "boilerplate",
30 | "redux",
31 | "react",
32 | "webpack",
33 | "sass",
34 | "css modules"
35 | ],
36 | "engines": {
37 | "node": "4.2.4",
38 | "npm": "3.8.8"
39 | },
40 | "author": "Ryan Collins",
41 | "license": "MIT",
42 | "bugs": {
43 | "url": "https://github.com/RyanCCollins/react-redux-simple-starter/issues"
44 | },
45 | "homepage": "https://github.com/RyanCCollins/react-redux-simple-starter#readme",
46 | "dependencies": {
47 | "actions": "^1.3.0",
48 | "babel-core": "^6.3.15",
49 | "babel-loader": "^6.2.0",
50 | "babel-preset-es2015": "^6.3.13",
51 | "babel-preset-react": "^6.3.13",
52 | "babel-preset-stage-0": "^6.3.13",
53 | "components": "^0.1.0",
54 | "css-loader": "^0.23.0",
55 | "expect": "^1.20.2",
56 | "file-loader": "^0.9.0",
57 | "foundation-sites": "^6.2.3",
58 | "history": "^1.14.0",
59 | "html-webpack-plugin": "^2.7.1",
60 | "immutable": "^3.7.5",
61 | "isomorphic-fetch": "^2.2.0",
62 | "node-sass": "^3.4.2",
63 | "react": "^15.1.0",
64 | "react-addons-css-transition-group": "^15.2.1",
65 | "react-dom": "^15.0.1",
66 | "react-foundation": "^0.6.8",
67 | "react-redux": "^4.4.5",
68 | "react-redux-toastr": "^3.1.4",
69 | "react-router": "^2.3.0",
70 | "react-router-redux": "^4.0.4",
71 | "redux": "^3.5.2",
72 | "redux-form": "^5.2.5",
73 | "redux-logger": "^2.6.1",
74 | "redux-promise-middleware": "^3.2.0",
75 | "redux-thunk": "^1.0.0",
76 | "resolve-url-loader": "^1.4.4",
77 | "sass-loader": "^3.1.2",
78 | "style-loader": "^0.13.0",
79 | "svg-react-loader": "^0.3.6",
80 | "webpack": "^1.12.9"
81 | },
82 | "devDependencies": {
83 | "babel-eslint": "^5.0.0-beta4",
84 | "babel-preset-es2015": "^6.9.0",
85 | "chai": "^3.4.1",
86 | "chai-immutable": "^1.5.3",
87 | "eslint": "^1.10.3",
88 | "eslint-config-airbnb": "^4.0.0",
89 | "eslint-loader": "^1.1.1",
90 | "eslint-plugin-react": "^3.11.3",
91 | "jsdom": "^7.2.0",
92 | "mocha": "^2.3.4",
93 | "npm-install-webpack-plugin": "^4.0.3",
94 | "react-css-modules": "^3.7.6",
95 | "react-hot-loader": "^1.3.0",
96 | "redux-devtools": "^3.0.1",
97 | "webpack-dev-server": "^1.14.0"
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const HtmlwebpackPlugin = require('html-webpack-plugin');
4 | const NpmInstallPlugin = require('npm-install-webpack-plugin');
5 | const ROOT_PATH = path.resolve(__dirname);
6 |
7 | const env = process.env.NODE_ENV || 'development';
8 | const PORT = process.env.PORT || 8080;
9 | const HOST = '0.0.0.0'; // Set to localhost if need be.
10 | const URL = `http://${HOST}:${PORT}`
11 |
12 |
13 | module.exports = {
14 | devtool: process.env.NODE_ENV === 'production' ? '' : 'source-map',
15 | entry: [
16 | path.resolve(ROOT_PATH,'app/src/index')
17 | ],
18 | module: {
19 | preLoaders: [
20 | {
21 | test: /\.jsx?$/,
22 | loaders: process.env.NODE_ENV === 'production' ? [] : ['eslint'],
23 | include: path.resolve(ROOT_PATH, './app')
24 | }
25 | ],
26 | loaders: [{
27 | test: /\.jsx?$/,
28 | exclude: /node_modules/,
29 | loaders: ['react-hot', 'babel']
30 | },
31 | {
32 | test: /\.svg$/,
33 | loader: 'babel!svg-react'
34 | },
35 | {
36 | test: /\.json$/,
37 | loader: 'json'
38 | },
39 | {
40 | test: /\.module\.scss$/,
41 | loaders: [
42 | 'style',
43 | 'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]',
44 | 'resolve-url',
45 | 'sass'
46 | ]
47 | },
48 | {
49 | test: /\.scss$/,
50 | exclude: /\.module\.scss$/,
51 | loaders: ["style", "css", "sass"]
52 | },
53 | {
54 | test: /\.css$/,
55 | loader: "style-loader!css-loader"
56 | },
57 | {
58 | test: /\.woff(2)?(\?v=[0-9].[0-9].[0-9])?$/,
59 | loader: "url-loader?mimetype=application/font-woff"
60 | },
61 | {
62 | test: /\.(ttf|eot|svg)(\?v=[0-9].[0-9].[0-9])?$/,
63 | loader: "file-loader?name=[name].[ext]"
64 | },
65 | {
66 | test: /\.(jpg|png)$/,
67 | loader: 'file?name=[path][name].[hash].[ext]'
68 | }
69 | ]
70 | },
71 | resolve: {
72 | extensions: ['', '.js', '.jsx'],
73 | alias: {
74 | actions: path.resolve(ROOT_PATH, 'app/src/actions'),
75 | utils: path.resolve(ROOT_PATH, 'app/src/utils'),
76 | reducers: path.resolve(ROOT_PATH, 'app/src/reducers'),
77 | store: path.resolve(ROOT_PATH, 'app/src/stores'),
78 | components: path.resolve(ROOT_PATH, 'app/src/components'),
79 | containers: path.resolve(ROOT_PATH, 'app/src/containers'),
80 | constants: path.resolve(ROOT_PATH, 'app/src/constants'),
81 | pages: path.resolve(ROOT_PATH, 'app/src/pages')
82 | },
83 | },
84 | output: {
85 | path: process.env.NODE_ENV === 'production' ? path.resolve(ROOT_PATH, 'app/dist') : path.resolve(ROOT_PATH, 'app/build'),
86 | publicPath: '/',
87 | filename: 'bundle.js',
88 | },
89 | devServer: {
90 | contentBase: path.resolve(ROOT_PATH, 'app/build'),
91 | historyApiFallback: true,
92 | hot: true,
93 | inline: true,
94 | progress: true,
95 | // Constants defined above take care of logic
96 | // For setting host and port
97 | host: HOST,
98 | port: PORT
99 | },
100 | plugins: [
101 | new webpack.HotModuleReplacementPlugin(),
102 | new NpmInstallPlugin(),
103 | new HtmlwebpackPlugin({
104 | title: 'React Redux Simple Starter',
105 | template: 'index.html'
106 | })
107 | ]
108 | };
109 |
--------------------------------------------------------------------------------