├── .babelrc ├── .gitignore ├── Readme.md ├── client ├── actions │ └── todos.js ├── components │ ├── Footer │ │ ├── index.js │ │ └── style.css │ ├── Header │ │ └── index.js │ ├── MainSection │ │ ├── index.js │ │ └── style.css │ ├── TodoItem │ │ ├── index.js │ │ └── style.css │ └── TodoTextInput │ │ ├── index.js │ │ └── style.css ├── constants │ └── filters.js ├── containers │ └── App │ │ ├── index.js │ │ └── style.css ├── index.html ├── index.js ├── middleware │ ├── index.js │ └── logger.js ├── reducers │ ├── index.js │ └── todos.js └── store │ └── index.js ├── package.json └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "plugins": ["transform-runtime"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | node_modules 3 | static 4 | .module-cache 5 | *.log* 6 | 7 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | __I don't use this anymore, it's unsupported, I'd recommend https://parceljs.org/ 3 | 4 | # Frontend Boilerplate 5 | 6 | A boilerplate of things that mostly shouldn't exist. 7 | 8 | ## Contains 9 | 10 | - [x] [Webpack](https://webpack.github.io) 11 | - [x] [React](https://facebook.github.io/react/) 12 | - [x] [Redux](https://github.com/reactjs/redux) 13 | - [x] [Babel](https://babeljs.io/) 14 | - [x] [Autoprefixer](https://github.com/postcss/autoprefixer) 15 | - [x] [PostCSS](https://github.com/postcss/postcss) 16 | - [x] [CSS modules](https://github.com/outpunk/postcss-modules) 17 | - [x] [Rucksack](http://simplaio.github.io/rucksack/docs) 18 | - [x] [React Router Redux](https://github.com/reactjs/react-router-redux) 19 | - [x] [Redux DevTools Extension](https://github.com/zalmoxisus/redux-devtools-extension) 20 | - [ ] Redux effects 21 | - [x] TodoMVC example 22 | 23 | ## Setup 24 | 25 | ``` 26 | $ npm install 27 | ``` 28 | 29 | ## Running 30 | 31 | ``` 32 | $ npm start 33 | ``` 34 | 35 | ## Build 36 | 37 | ``` 38 | $ npm run build 39 | ``` 40 | 41 | ## Note 42 | 43 | My personal projects have diverged from this quite a bit, I use browserify now instead etc, but feel free to use this if it fits your needs! I won't be updating it a ton for now unless I have time to update it to match my current workflow. 44 | 45 | # License 46 | 47 | MIT 48 | -------------------------------------------------------------------------------- /client/actions/todos.js: -------------------------------------------------------------------------------- 1 | 2 | import { createAction } from 'redux-actions' 3 | 4 | export const addTodo = createAction('add todo') 5 | export const deleteTodo = createAction('delete todo') 6 | export const editTodo = createAction('edit todo') 7 | export const completeTodo = createAction('complete todo') 8 | export const completeAll = createAction('complete all') 9 | export const clearCompleted = createAction('clear complete') 10 | -------------------------------------------------------------------------------- /client/components/Footer/index.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react' 3 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../../constants/filters' 4 | import classnames from 'classnames' 5 | import style from './style.css' 6 | 7 | const FILTER_TITLES = { 8 | [SHOW_ALL]: 'All', 9 | [SHOW_ACTIVE]: 'Active', 10 | [SHOW_COMPLETED]: 'Completed' 11 | } 12 | 13 | class Footer extends Component { 14 | renderTodoCount() { 15 | const { activeCount } = this.props 16 | const itemWord = activeCount === 1 ? 'item' : 'items' 17 | 18 | return ( 19 | 20 | {activeCount || 'No'} {itemWord} left 21 | 22 | ) 23 | } 24 | 25 | renderFilterLink(filter) { 26 | const title = FILTER_TITLES[filter] 27 | const { filter: selectedFilter, onShow } = this.props 28 | 29 | return ( 30 | onShow(filter)}> 33 | {title} 34 | 35 | ) 36 | } 37 | 38 | renderClearButton() { 39 | const { completedCount, onClearCompleted } = this.props 40 | if (completedCount > 0) { 41 | return ( 42 | 45 | ) 46 | } 47 | } 48 | 49 | render() { 50 | return ( 51 | 62 | ) 63 | } 64 | } 65 | 66 | export default Footer 67 | -------------------------------------------------------------------------------- /client/components/Footer/style.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | color: #777; 4 | padding: 10px 15px; 5 | height: 20px; 6 | text-align: center; 7 | border-top: 1px solid #e6e6e6; 8 | } 9 | 10 | .normal:before { 11 | content: ''; 12 | position: absolute; 13 | right: 0; 14 | bottom: 0; 15 | left: 0; 16 | height: 50px; 17 | overflow: hidden; 18 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 19 | 0 8px 0 -3px #f6f6f6, 20 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 21 | 0 16px 0 -6px #f6f6f6, 22 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 23 | } 24 | 25 | .filters { 26 | margin: 0; 27 | padding: 0; 28 | list-style: none; 29 | position: absolute; 30 | right: 0; 31 | left: 0; 32 | } 33 | 34 | .filters li { 35 | display: inline; 36 | } 37 | 38 | .filters li a { 39 | color: inherit; 40 | margin: 3px; 41 | padding: 3px 7px; 42 | text-decoration: none; 43 | border: 1px solid transparent; 44 | border-radius: 3px; 45 | } 46 | 47 | .filters li a.selected, 48 | .filters li a:hover { 49 | border-color: rgba(175, 47, 47, 0.1); 50 | } 51 | 52 | .filters li a.selected { 53 | border-color: rgba(175, 47, 47, 0.2); 54 | } 55 | 56 | .count { 57 | float: left; 58 | text-align: left; 59 | } 60 | 61 | .count strong { 62 | font-weight: 300; 63 | } 64 | 65 | .clearCompleted, 66 | html .clearCompleted:active { 67 | float: right; 68 | position: relative; 69 | line-height: 20px; 70 | text-decoration: none; 71 | cursor: pointer; 72 | visibility: hidden; 73 | position: relative; 74 | } 75 | 76 | .clearCompleted::after { 77 | visibility: visible; 78 | content: 'Clear completed'; 79 | position: absolute; 80 | right: 0; 81 | white-space: nowrap; 82 | } 83 | 84 | .clearCompleted:hover::after { 85 | text-decoration: underline; 86 | } 87 | 88 | @media (max-width: 430px) { 89 | .normal { 90 | height: 50px; 91 | } 92 | 93 | .filters { 94 | bottom: 10px; 95 | } 96 | } -------------------------------------------------------------------------------- /client/components/Header/index.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react' 3 | import TodoTextInput from '../TodoTextInput' 4 | 5 | class Header extends Component { 6 | handleSave(text) { 7 | if (text.length) { 8 | this.props.addTodo(text) 9 | } 10 | } 11 | 12 | render() { 13 | return ( 14 |
15 |

Todos

16 | 20 |
21 | ) 22 | } 23 | } 24 | 25 | export default Header 26 | -------------------------------------------------------------------------------- /client/components/MainSection/index.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react' 3 | import TodoItem from '../TodoItem' 4 | import Footer from '../Footer' 5 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../../constants/filters' 6 | import style from './style.css' 7 | 8 | const TODO_FILTERS = { 9 | [SHOW_ALL]: () => true, 10 | [SHOW_ACTIVE]: todo => !todo.completed, 11 | [SHOW_COMPLETED]: todo => todo.completed 12 | } 13 | 14 | class MainSection extends Component { 15 | constructor(props, context) { 16 | super(props, context) 17 | this.state = { filter: SHOW_ALL } 18 | } 19 | 20 | handleClearCompleted() { 21 | const atLeastOneCompleted = this.props.todos.some(todo => todo.completed) 22 | if (atLeastOneCompleted) { 23 | this.props.actions.clearCompleted() 24 | } 25 | } 26 | 27 | handleShow(filter) { 28 | this.setState({ filter }) 29 | } 30 | 31 | renderToggleAll(completedCount) { 32 | const { todos, actions } = this.props 33 | if (todos.length > 0) { 34 | return 39 | } 40 | } 41 | 42 | renderFooter(completedCount) { 43 | const { todos } = this.props 44 | const { filter } = this.state 45 | const activeCount = todos.length - completedCount 46 | 47 | if (todos.length) { 48 | return ( 49 |