diff --git a/package.json b/package.json index 7bf4a6b..09bff37 100644 --- a/package.json +++ b/package.json @@ -13,29 +13,33 @@ "coverage": "NODE_ENV=testing nyc yarn test" }, "dependencies": { + "antd": "^2.6.0", + "chai": "^3.5.0", + "enzyme": "^2.6.0", "ramda": "^0.22.1", "react": "^15.0.2", "react-addons-test-utils": "^15.4.1", "react-dom": "^15.0.2", - "react-redux": "^4.4.5", + "react-redux": "^5.0.2", "react-router": "^3.0.0", - "redux": "^3.5.2", - "redux-logger": "^2.6.1", + "redux": "^3.6.0", + "redux-logger": "^2.7.4", "redux-thunk": "^2.0.1", - "styled-components": "^1.2.1" + "styled-components": "^1.2.1", + "whatwg-fetch": "^2.0.1" }, "devDependencies": { - "sinon": "^1.17.7", - "chai": "^3.5.0", - "enzyme": "^2.6.0", "babel-cli": "^6.18.0", "babel-core": "^6.18.2", "babel-eslint": "^7.1.1", "babel-loader": "^6.2.8", + "babel-plugin-import": "^1.1.0", "babel-preset-latest": "^6.16.0", "babel-preset-react": "^6.16.0", "babel-preset-stage-0": "^6.16.0", + "chai": "^3.5.0", "css-loader": "^0.26.1", + "enzyme": "^2.6.0", "eslint": "^3.11.1", "eslint-config-airbnb": "^12.0.0", "eslint-plugin-import": "^2.2.0", @@ -44,6 +48,7 @@ "extract-text-webpack-plugin": "^2.0.0-beta", "mocha": "^3.2.0", "nyc": "^10.0.0", + "sinon": "^1.17.7", "style-loader": "^0.13.1", "webpack": "^2.1.0-beta.26", "webpack-bundle-analyzer": "^1.5.0", @@ -55,6 +60,15 @@ ] }, "babel": { + "plugins": [ + [ + "import", + { + "libraryName": "antd", + "style": "css" + } + ] + ], "presets": [ [ "latest", diff --git a/public/index.html b/public/index.html index 8b99d88..7be6672 100644 --- a/public/index.html +++ b/public/index.html @@ -2,10 +2,18 @@ RedTodo - + + -
+
Loading TODO app ...
diff --git a/src/client/actions/index.js b/src/client/actions/index.js new file mode 100644 index 0000000..b9f0800 --- /dev/null +++ b/src/client/actions/index.js @@ -0,0 +1,7 @@ +import * as todo from './todos'; +import * as task from './tasks'; + +module.exports = { + todo, + task, +}; diff --git a/src/client/actions/tasks.js b/src/client/actions/tasks.js new file mode 100644 index 0000000..519ae3e --- /dev/null +++ b/src/client/actions/tasks.js @@ -0,0 +1,39 @@ +import requestJSON from '../requestJSON'; +import apiURI from '../apiURI'; + +export const INITIAL_TASKS_LOADED = 'INITIAL_TASKS_LOADED'; +export const TASK_CREATED = 'TASK_CREATED'; +export const TASK_REMOVED = 'TASK_REMOVED'; +export const TASK_UPDATED = 'TASK_UPDATED'; + +export const taskCreated = payload => ({ + type: TASK_CREATED, + payload, +}); + +export const taskRemoved = payload => ({ + type: TASK_REMOVED, + payload, +}); + +export const taskUpdated = payload => ({ + type: TASK_UPDATED, + payload, +}); + +export const add = payload => (dispatch) => { + const body = { task: { ...payload } }; + requestJSON(`${apiURI}/todo/tasks`, body, 'POST') + .then(task => dispatch(taskCreated(task))); +}; + +export const remove = payload => (dispatch) => { + requestJSON(`${apiURI}/todo/task/${payload}`, {}, 'DELETE') + .then(task => dispatch(taskRemoved(task))); +}; + +export const update = payload => (dispatch) => { + const body = { task: { ...payload } }; + requestJSON(`${apiURI}/todo/tasks`, body, 'PUT') + .then(task => dispatch(taskUpdated(task))); +}; diff --git a/src/client/actions/todos.js b/src/client/actions/todos.js new file mode 100644 index 0000000..076a880 --- /dev/null +++ b/src/client/actions/todos.js @@ -0,0 +1,39 @@ +import requestJSON from '../requestJSON'; +import apiURI from '../apiURI'; + +export const INITIAL_TODOS_LOADED = 'INITIAL_TODOS_LOADED'; +export const TODO_CREATED = 'TODO_CREATED'; +export const TODO_REMOVED = 'TODO_REMOVED'; +export const TODO_UPDATED = 'TODO_UPDATED'; + +export const todoCreated = payload => ({ + type: TODO_CREATED, + payload, +}); + +export const todoUpdated = payload => ({ + type: TODO_UPDATED, + payload, +}); + +export const todoRemoved = payload => ({ + type: TODO_REMOVED, + payload, +}); + +export const create = payload => (dispatch) => { + const body = { todo: payload }; + requestJSON(`${apiURI}/todo/lists`, body, 'POST') + .then(todo => dispatch(todoCreated(todo))); +}; + +export const update = payload => (dispatch) => { + const body = { todo: payload }; + requestJSON(`${apiURI}/todo/lists`, body, 'PUT') + .then(todo => dispatch(todoUpdated(todo))); +}; + +export const remove = payload => (dispatch) => { + requestJSON(`${apiURI}/todo/list/${payload}`, {}, 'DELETE') + .then(todo => dispatch(todoRemoved(todo))); +}; diff --git a/src/client/apiURI.js b/src/client/apiURI.js new file mode 100644 index 0000000..d361101 --- /dev/null +++ b/src/client/apiURI.js @@ -0,0 +1 @@ +export default 'http://rp3.redpelicans.com:4007/api'; diff --git a/src/client/colors.json b/src/client/colors.json new file mode 100644 index 0000000..6731c71 --- /dev/null +++ b/src/client/colors.json @@ -0,0 +1,21 @@ +{ + "red": "#f44336", + "pink": "#e91e63", + "purple": "#9c27b0", + "deepPurple": "#673ab7", + "indigo": "#3f51b5", + "blue": "#2196f3", + "lightBlue": "#03a9f4", + "cyan": "#00bcd4", + "teal": "#009688", + "green": "#4caf50", + "lightGreen": "#8bc34a", + "lime": "#cddc39", + "yellow": "#ffeb3b", + "amber": "#ffc107", + "orange": "#ff9800", + "deepOrange": "#ff5722", + "brown": "#795548", + "grey": "#9e9e9e", + "blueGrey": "#607d8b" +} diff --git a/src/client/components/AddTask/index.js b/src/client/components/AddTask/index.js new file mode 100644 index 0000000..88b15ef --- /dev/null +++ b/src/client/components/AddTask/index.js @@ -0,0 +1,67 @@ +import React, { PropTypes } from 'react'; +import Modal from 'antd/lib/modal'; + +import Button from '../Button'; +import FullHeightContainer from '../FullHeightContainer'; +import ModalInput from '../ModalInput'; + +export default class AddTask extends React.Component { + state = { + visible: false, + value: '', + } + + handleKeyDown = (e) => { + if (e.keyCode === 13) { + e.preventDefault(); + this.saveTask(); + } + } + + close = () => this.setState({ visible: false }) + + showModal = () => this.setState({ visible: true }) + + handleChange = e => this.setState({ value: e.target.value }); + + saveTask = () => { + const { value } = this.state; + const { actions, listId } = this.props; + + actions.task.add({ description: value, isCompleted: false, listId }); + this.setState({ value: '', visible: false }); + } + + render() { + const { visible, value } = this.state; + return ( + + + Cancel, + , + ]} + > + + + + ); + } +} + +AddTask.propTypes = { + actions: PropTypes.object.isRequired, + listId: PropTypes.number.isRequired, +}; diff --git a/src/client/components/AddTodo/index.js b/src/client/components/AddTodo/index.js new file mode 100644 index 0000000..3b32f62 --- /dev/null +++ b/src/client/components/AddTodo/index.js @@ -0,0 +1,72 @@ +import React, { PropTypes } from 'react'; +import styled from 'styled-components'; +import colors from '../../colors.json'; + +import TodoInput from '../TodoInput'; + +const FormContainer = styled.form` + background-color: white; + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); + display: flex; + margin-top: 2em; + width: 50vw; + height: 50px; +`; + +const TodoSubmit = styled.input` + border: none; + background-color: ${colors.blueGrey}; + width: 50%; + height: 100%; + color: white; + font-size: 16px; + cursor: pointer; + transition: all .2s; + + &:hover { + background-color: white; + color: ${colors.blueGrey}; + } +`; + +export default class AddTodo extends React.Component { + state = { + value: '', + } + + handleChange = e => this.setState({ value: e.target.value }); + + addTodo = (e) => { + e.preventDefault(); + + const { actions } = this.props; + const { value } = this.state; + + if (!value) return false; + + actions.todo.create({ label: value }); + this.setState({ value: '' }); + return true; + } + + render() { + const { value } = this.state; + return ( + + + + + ); + } +} + +AddTodo.propTypes = { + actions: PropTypes.object.isRequired, +}; diff --git a/src/client/components/App/__test__/index.js b/src/client/components/App/__test__/index.js deleted file mode 100644 index 067d243..0000000 --- a/src/client/components/App/__test__/index.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import chai from 'chai'; -import { shallow } from 'enzyme'; -import App, { Title } from '..'; - -const { describe, it } = global; -const { expect } = chai; - -describe('[UT] ', () => { - it('should render a ', () => { - expect(shallow(<App />).find(Title)).to.have.length(1); - }); -}); diff --git a/src/client/components/App/index.js b/src/client/components/App/index.js deleted file mode 100644 index ff82195..0000000 --- a/src/client/components/App/index.js +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; - -export const Title = styled.h1` - font-size: 1.5em; - text-align: center; - color: palevioletred; -`; - -const Wrapper = styled.section` - padding: 4em; - background: papayawhip; -`; - -const App = () => ( - <Wrapper> - <Title>Hello World, this is my first react app! - -); - -export default App; diff --git a/src/client/components/Button/index.js b/src/client/components/Button/index.js index d17e537..ca0695f 100644 --- a/src/client/components/Button/index.js +++ b/src/client/components/Button/index.js @@ -1,9 +1,18 @@ -import React, { PropTypes } from 'react'; +import styled from 'styled-components'; +import colors from '../../colors.json'; -const Button = ({ onClick }) => + NO, + , + ]} + > + ARE YOU SURE ? + + + ); + } +} + +RemoveTodo.propTypes = { + actions: PropTypes.object.isRequired, + listId: PropTypes.number.isRequired, +}; diff --git a/src/client/components/TodoContainer/index.js b/src/client/components/TodoContainer/index.js new file mode 100644 index 0000000..621f280 --- /dev/null +++ b/src/client/components/TodoContainer/index.js @@ -0,0 +1,45 @@ +import React, { PropTypes } from 'react'; +import styled from 'styled-components'; + +import AddTodo from '../AddTodo'; +import MainContainer from '../Container'; +import TodoEl from '../TodoEl'; + +const TodoList = styled.ul` + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: flex-start; + margin: 0; + padding: 0; +`; + +const linkTasks = (id, tasks) => tasks.filter(task => task.listId === id); + +const drawTodos = (todos, tasks, actions) => todos.map(todo => + + {todo.label} + , +); + +const TodoContainer = ({ todos, tasks, actions }) => + + + + {drawTodos(todos, tasks, actions)} + + +; + +TodoContainer.propTypes = { + todos: PropTypes.array.isRequired, + tasks: PropTypes.array.isRequired, + actions: PropTypes.object.isRequired, +}; + +export default TodoContainer; diff --git a/src/client/components/TodoEl/index.js b/src/client/components/TodoEl/index.js new file mode 100644 index 0000000..fcafa63 --- /dev/null +++ b/src/client/components/TodoEl/index.js @@ -0,0 +1,101 @@ +import React, { PropTypes } from 'react'; +import styled from 'styled-components'; + +import AddTask from '../AddTask'; +import RemoveTodo from '../RemoveTodo'; +import UpdateTask from '../UpdateTask'; +import UpdateTodo from '../UpdateTodo'; +import Button from '../Button'; + +const TodoBlock = styled.li` + list-style: none; + font-family: Roboto, arial, verdana; + background-color: white; + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); + display: flex; + flex-direction: column; + margin: 2em; + border-radius: 2px; +`; + +const TaskContainer = styled.li` + list-style: none; + display: flex; + justify-content: space-between; + align-items: center; + transition: all .2s; +`; + +const TasksList = styled.ul` + display: flex; + flex-direction: column; + padding: 0; +`; + +const TodoTitle = styled.span` + margin: 1em 1em; +`; + +const TaskTitle = styled.span` + margin: 0 .5em; + cursor: pointer; + text-decoration: ${props => (props.checked ? 'line-through' : 'none')}; + color: ${props => (props.checked ? 'grey' : 'black')} +`; + +const TopTodo = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + height: 50px; + background-color: rgba(0,0,0,.1); + & button { + background-color: rgba(0,0,0,0); + } +`; + +const TaskControls = styled.div` + display: flex; +`; + +const drawTasks = (tasks, actions) => tasks.map(task => + + actions.task.update({ + ...task, + isCompleted: !task.isCompleted, + })} + > + {task.description} + + + + + + , +); + +const TodoEl = ({ children, tasks, actions, listId }) => + + + {children} + + + + + {drawTasks(tasks, actions)} + +; + +TodoEl.propTypes = { + children: PropTypes.string.isRequired, + tasks: PropTypes.array.isRequired, + actions: PropTypes.object.isRequired, + listId: PropTypes.number.isRequired, +}; + +export default TodoEl; diff --git a/src/client/components/TodoInput/index.js b/src/client/components/TodoInput/index.js new file mode 100644 index 0000000..014c7f2 --- /dev/null +++ b/src/client/components/TodoInput/index.js @@ -0,0 +1,12 @@ +import styled from 'styled-components'; + +const TodoInput = styled.input` + width: 80%; + height: 2em; + border: none; + padding-top: 1em; + padding-left: 1em; + font-size: 16px; +`; + +export default TodoInput; diff --git a/src/client/components/UpdateTask/index.js b/src/client/components/UpdateTask/index.js new file mode 100644 index 0000000..be226e3 --- /dev/null +++ b/src/client/components/UpdateTask/index.js @@ -0,0 +1,73 @@ +import React, { PropTypes } from 'react'; + +import Modal from 'antd/lib/modal'; +import Button from '../Button'; +import ModalInput from '../ModalInput'; + +export default class UpdateTask extends React.Component { + state = { + visible: false, + value: this.props.task.description, + } + + componentWillReceiveProps = (newProps) => { + this.setState({ value: newProps.task.description }); + } + + updateTask = () => { + const { value } = this.state; + const { actions, task } = this.props; + const { id, isCompleted, listId } = task; + + if (!value || value.length > 1000) return false; + actions.task.update({ listId, id, description: value, isCompleted }); + this.setState({ visible: false }); + return (true); + } + + close = () => this.setState({ visible: false }) + + showModal = () => this.setState({ visible: true }); + + handleChange = e => this.setState({ value: e.target.value }) + + handleKeyDown = (e) => { + if (e.keyCode === 13) { + e.preventDefault(); + this.updateTask(); + } + } + + render() { + const { value, visible } = this.state; + return ( +
+ + Cancel, + , + ]} + > + + +
+ ); + } +} + +UpdateTask.propTypes = { + actions: PropTypes.object.isRequired, + task: PropTypes.object.isRequired, +}; diff --git a/src/client/components/UpdateTodo/index.js b/src/client/components/UpdateTodo/index.js new file mode 100644 index 0000000..1b2ecf3 --- /dev/null +++ b/src/client/components/UpdateTodo/index.js @@ -0,0 +1,74 @@ +import React, { PropTypes } from 'react'; + +import Modal from 'antd/lib/modal'; +import Button from '../Button'; +import FullHeightContainer from '../FullHeightContainer'; +import ModalInput from '../ModalInput'; + +export default class UpdateTodo extends React.Component { + state = { + visible: false, + value: this.props.label, + } + + componentWillReceiveProps = (newProps) => { + this.setState({ value: newProps.label }); + } + + updateTodo = () => { + const { value } = this.state; + const { actions, listId } = this.props; + + if (!value || value.length > 1000) return false; + actions.todo.update({ id: listId, label: value }); + this.setState({ visible: false }); + return (true); + } + + close = () => this.setState({ visible: false }) + + showModal = () => this.setState({ visible: true }); + + handleChange = e => this.setState({ value: e.target.value }) + + handleKeyDown = (e) => { + if (e.keyCode === 13) { + e.preventDefault(); + this.updateTodo(); + } + } + + render() { + const { value, visible } = this.state; + return ( + + + Cancel, + , + ]} + > + + + + ); + } +} + +UpdateTodo.propTypes = { + label: PropTypes.string.isRequired, + actions: PropTypes.object.isRequired, + listId: PropTypes.number.isRequired, +}; diff --git a/src/client/containers/App/__test__/index.js b/src/client/containers/App/__test__/index.js new file mode 100644 index 0000000..9eaf40a --- /dev/null +++ b/src/client/containers/App/__test__/index.js @@ -0,0 +1,64 @@ +import React from 'react'; +import chai from 'chai'; +import { shallow } from 'enzyme'; + +import App from '..'; +import Header from '../../Header'; +import TodoContainer from '../../TodoContainer'; +import actions from '../../../actions'; + +const state = [ + { + title: 'my first todoList', + id: 0, + checked: true, + tasks: [ + { title: 'my first task', checked: false, id: 0 }, + { title: 'my second task', checked: true, id: 1 }, + { title: 'my third task', checked: false, id: 2 }, + { title: 'my fourth task', checked: false, id: 3 }, + { title: 'my fifth task', checked: true, id: 4 }, + { title: 'my sixth task', checked: false, id: 5 }, + ], + }, + { + title: 'my second todoList', + checked: false, + id: 1, + tasks: [ + { title: 'my first task', checked: false, id: 6 }, + { title: 'my second task', checked: false, id: 7 }, + { title: 'my third task', checked: true, id: 8 }, + ], + }, +]; + +const store = { + state, + listeners: [], + listen(cb) { this.listeners.push(cb); }, + callListeners() { this.listeners.forEach(cb => cb()); }, + dispatch(action) { + this.state = action(this.state); + this.callListeners(); + }, +}; + +const { describe, it } = global; +const { expect } = chai; + +describe('', () => { + const app = shallow(); + it('should render a
', () => { + expect(app.find(Header)).to.have.length(1); + }); + it('should render a ', () => { + expect(app.find(TodoContainer)).to.have.length(1); + }); + it('should have store in props', () => { + expect(app.props().store).to.equal(store); + }); + it('should have actions in props', () => { + expect(app.props().actions).to.equal(actions); + }); +}); diff --git a/src/client/containers/App/index.js b/src/client/containers/App/index.js new file mode 100644 index 0000000..a1d9047 --- /dev/null +++ b/src/client/containers/App/index.js @@ -0,0 +1,30 @@ +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import allTheActions from '../../actions'; +import Header from '../../components/Header'; +import TodoContainer from '../../components/TodoContainer'; +import MainContainer from '../../components/Container'; + +const App = ({ todos, tasks, actions }) => + +
+ + +; + +const mapStateToPros = ({ todos, tasks }) => ({ todos, tasks }); +const mapDispatchToProps = dispatch => ({ + actions: { + todo: bindActionCreators(allTheActions.todo, dispatch), + task: bindActionCreators(allTheActions.task, dispatch), + }, +}); + +App.propTypes = { + todos: PropTypes.array.isRequired, + tasks: PropTypes.array.isRequired, + actions: PropTypes.object.isRequired, +}; + +export default connect(mapStateToPros, mapDispatchToProps)(App); diff --git a/src/client/index.js b/src/client/index.js index da1841c..f39445f 100644 --- a/src/client/index.js +++ b/src/client/index.js @@ -1,7 +1,16 @@ import React from 'react'; import { render } from 'react-dom'; -import App from './components/App'; +import 'antd/dist/antd.css'; +import { Provider } from 'react-redux'; +import App from './containers/App'; +import store from './store'; -console.log('mounting react app ...'); // eslint-disable-line no-console -render(, document.getElementById('__TODO__')); +const Root = ( + + + +); +console.log('mounting react app ...'); // eslint-disable-line no-console + +render(Root, document.getElementById('__TODO__')); diff --git a/src/client/initialState.js b/src/client/initialState.js new file mode 100644 index 0000000..76ce512 --- /dev/null +++ b/src/client/initialState.js @@ -0,0 +1,26 @@ +import apiURI from './apiURI'; + +export const state = { + todos: [], + tasks: [], +}; + +const fetchInitial = (uri, cb) => { + fetch(uri) + .then((data) => { + if (data.status === 200) { + if (data.json) return data.json(); + } + return null; + }) + .then(json => cb(json)) + .catch(console.error); // eslint-disable-line no-console +}; + +export const getInitialTodos = (cb) => { + fetchInitial(`${apiURI}/todo/lists`, cb); +}; + +export const getInitialTasks = (cb) => { + fetchInitial(`${apiURI}/todo/tasks`, cb); +}; diff --git a/src/client/reducers/index.js b/src/client/reducers/index.js new file mode 100644 index 0000000..7802937 --- /dev/null +++ b/src/client/reducers/index.js @@ -0,0 +1,8 @@ +import { combineReducers } from 'redux'; +import todos from './todos'; +import tasks from './tasks'; + +export default combineReducers({ + todos, + tasks, +}); diff --git a/src/client/reducers/tasks.js b/src/client/reducers/tasks.js new file mode 100644 index 0000000..db76dee --- /dev/null +++ b/src/client/reducers/tasks.js @@ -0,0 +1,27 @@ +import { + TASK_CREATED, + TASK_UPDATED, + TASK_REMOVED, + INITIAL_TASKS_LOADED, +} from '../actions/tasks'; + +const updateTask = (state, payload) => state.map((task) => { + if (task.id === payload.id) { + return { ...payload }; + } + return task; +}); + +export default (state = [], action) => { + switch (action.type) { + case INITIAL_TASKS_LOADED: + return action.payload; + case TASK_CREATED: + return [...state, action.payload]; + case TASK_REMOVED: + return state.filter(task => task.id !== action.payload.id); + case TASK_UPDATED: + return updateTask(state, action.payload); + default: return state; + } +}; diff --git a/src/client/reducers/todos.js b/src/client/reducers/todos.js new file mode 100644 index 0000000..94455a7 --- /dev/null +++ b/src/client/reducers/todos.js @@ -0,0 +1,27 @@ +import { + TODO_CREATED, + TODO_REMOVED, + TODO_UPDATED, + INITIAL_TODOS_LOADED, +} from '../actions/todos'; + +const updateTodo = (state, payload) => state.map((todo) => { + if (todo.id === payload.id) { + return { ...payload }; + } + return todo; +}); + +export default (state = [], action) => { + switch (action.type) { + case INITIAL_TODOS_LOADED: + return action.payload; + case TODO_CREATED: + return [...state, action.payload]; + case TODO_REMOVED: + return state.filter(todo => todo.id !== action.payload.id); + case TODO_UPDATED: + return updateTodo(state, action.payload); + default: return state; + } +}; diff --git a/src/client/requestJSON.js b/src/client/requestJSON.js new file mode 100644 index 0000000..8295c5a --- /dev/null +++ b/src/client/requestJSON.js @@ -0,0 +1,18 @@ +const getJSON = response => (response.json ? response.json() : null); + +const checkResponse = (response) => { + if (response.status && response.status === 200) { + return getJSON(response); + } + console.error(response); // eslint-disable-line no-console + return null; +}; + +const requestJSON = (uri, body, method) => { + const headers = { 'Content-Type': 'application/json' }; + const options = { headers, body: JSON.stringify(body), method }; + return fetch(uri, options) + .then(checkResponse); +}; + +export default requestJSON; diff --git a/src/client/store.js b/src/client/store.js new file mode 100644 index 0000000..fa5b250 --- /dev/null +++ b/src/client/store.js @@ -0,0 +1,41 @@ +import { createStore, applyMiddleware } from 'redux'; +import createLogger from 'redux-logger'; +import thunkMiddleware from 'redux-thunk'; +import reducers from './reducers'; +import { initialState, getInitialTodos, getInitialTasks } from './initialState'; +import { INITIAL_TODOS_LOADED } from './actions/todos'; +import { INITIAL_TASKS_LOADED } from './actions/tasks'; + +const logger = createLogger(); + +const generateStore = (mode) => { + if (mode === 'development') { + return createStore( + reducers, + initialState, + applyMiddleware( + logger, + thunkMiddleware, + ), + ); + } + return createStore( + reducers, + initialState, + applyMiddleware( + thunkMiddleware, + ), + ); +}; + +const store = generateStore(process.env.NODE_ENV); + +getInitialTodos((data) => { + store.dispatch({ type: INITIAL_TODOS_LOADED, payload: data }); +}); + +getInitialTasks((data) => { + store.dispatch({ type: INITIAL_TASKS_LOADED, payload: data }); +}); + +export default store; diff --git a/webpack.config.js b/webpack.config.js index 10104f4..ac8b351 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -17,7 +17,7 @@ const webpackConfig = { output: { filename: 'bundle.js', path: path.join(__dirname, '/build'), - publicPath: `/assets/`, + publicPath: '/assets/', }, entry: { app: './src/client/index.js', @@ -26,7 +26,7 @@ const webpackConfig = { rules: [ { test: /\.(js|jsx)$/, - loader: 'babel-loader', + loaders: [ 'babel-loader?cacheDirectory' ], exclude: /node_modules/, }, { @@ -44,8 +44,8 @@ const webpackConfig = { }), ifDev(new webpack.HotModuleReplacementPlugin()), ]), - performance: { - hints: false + performance: { + hints: false, }, stats: { assets: true, @@ -60,4 +60,3 @@ const webpackConfig = { }; module.exports = webpackConfig; -