offline remove, toggle, edit todos

This commit is contained in:
2018-06-16 14:30:28 +03:00
parent cf618f7651
commit 258eed6ee6
7 changed files with 141 additions and 69 deletions

28
package-lock.json generated
View File

@@ -3084,12 +3084,14 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -3104,17 +3106,20 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@@ -3231,7 +3236,8 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@@ -3243,6 +3249,7 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@@ -3257,6 +3264,7 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@@ -3264,12 +3272,14 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.2.4", "version": "2.2.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.1", "safe-buffer": "^5.1.1",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@@ -3288,6 +3298,7 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@@ -3368,7 +3379,8 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@@ -3380,6 +3392,7 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@@ -3501,6 +3514,7 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",

View File

@@ -4143,11 +4143,13 @@
}, },
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true "bundled": true,
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -4160,15 +4162,18 @@
}, },
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true "bundled": true,
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true "bundled": true,
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true "bundled": true,
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@@ -4271,7 +4276,8 @@
}, },
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true "bundled": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@@ -4281,6 +4287,7 @@
"is-fullwidth-code-point": { "is-fullwidth-code-point": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@@ -4293,17 +4300,20 @@
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
}, },
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true "bundled": true,
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.2.4", "version": "2.2.4",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.1", "safe-buffer": "^5.1.1",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@@ -4320,6 +4330,7 @@
"mkdirp": { "mkdirp": {
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@@ -4392,7 +4403,8 @@
}, },
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true "bundled": true,
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@@ -4402,6 +4414,7 @@
"once": { "once": {
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@@ -4507,6 +4520,7 @@
"string-width": { "string-width": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",

View File

@@ -1,5 +1,5 @@
import { API_ROOT, getToken } from './util'; import { API_ROOT, getToken } from './util';
import { fetchLists } from './lists'; import { fetchLists, INVALIDATE_LISTS } from './lists';
export const ADD_TODO = 'ADD_TODO'; export const ADD_TODO = 'ADD_TODO';
export const REMOVE_TODO = 'REMOVE_TODO'; export const REMOVE_TODO = 'REMOVE_TODO';
@@ -70,66 +70,78 @@ export function addTodo(text) {
export function removeTodo(id) { export function removeTodo(id) {
return async dispatch => { return async dispatch => {
dispatch(invalidateTodos()); dispatch({
const response = await fetch(`${API_ROOT}/todos/${id}`, { type: REMOVE_TODO,
id,
meta: {
offline: {
effect: {
url: `${API_ROOT}/todos/${id}`,
headers: { headers: {
Authorization: `Bearer ${getToken()}`, Authorization: `Bearer ${getToken()}`,
'content-type': 'application/json', 'content-type': 'application/json',
}, },
method: 'DELETE', method: 'DELETE',
},
rollback: {
type: INVALIDATE_LISTS,
},
},
},
}); });
const json = await response.json();
if (json.success) {
dispatch({ type: REMOVE_TODO, id });
} else {
dispatch(fetchLists());
}
dispatch(validateTodos());
}; };
} }
export function toggleTodo(id) { export function toggleTodo(id) {
return async (dispatch, getState) => { return async (dispatch, getState) => {
dispatch(invalidateTodos());
const state = getState(); const state = getState();
const todoObj = state.todos.todos[id]; const todoObj = state.todos.todos[id];
const completed = !todoObj.completed; const completed = !todoObj.completed;
const response = await fetch(`${API_ROOT}/todos/${id}`, { dispatch({
type: TOGGLE_TODO,
id,
meta: {
offline: {
effect: {
url: `${API_ROOT}/todos/${id}`,
body: JSON.stringify({ completed }), body: JSON.stringify({ completed }),
headers: { headers: {
Authorization: `Bearer ${getToken()}`, Authorization: `Bearer ${getToken()}`,
'content-type': 'appl ication/json', 'content-type': 'appl ication/json',
}, },
method: 'PATCH', method: 'PATCH',
},
rollback: {
type: INVALIDATE_LISTS,
},
},
},
}); });
const json = await response.json();
if (json.success) {
dispatch({ type: TOGGLE_TODO, id });
} else {
dispatch(fetchLists());
}
dispatch(validateTodos());
}; };
} }
export function editTodo(id, text) { export function editTodo(id, text) {
return async dispatch => { return async dispatch => {
dispatch(invalidateTodos()); dispatch({
const response = await fetch(`${API_ROOT}/todos/${id}`, { type: EDIT_TODO,
id,
text,
meta: {
offline: {
effect: {
url: `${API_ROOT}/todos/${id}`,
body: JSON.stringify({ text }), body: JSON.stringify({ text }),
headers: { headers: {
Authorization: `Bearer ${getToken()}`, Authorization: `Bearer ${getToken()}`,
'content-type': 'application/json', 'content-type': 'application/json',
}, },
method: 'PATCH', method: 'PATCH',
},
rollback: {
type: INVALIDATE_LISTS,
},
},
},
}); });
const json = await response.json();
if (json.success) {
const todo = json.data;
dispatch({ type: EDIT_TODO, id, todo });
} else {
dispatch(fetchLists());
}
dispatch(validateTodos());
}; };
} }

View File

@@ -1,10 +1,21 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import LogoutLink from '../containers/LogoutLink'; import LogoutLink from '../containers/LogoutLink';
import FetchButton from '../containers/FetchButton'; import FetchButton from '../containers/FetchButton';
import Status from '../containers/Status'; import Status from '../containers/Status';
export default function UserHeader() { export default class UserHeader extends React.Component {
componentDidUpdate() {
if (
(this.props.dirtyLists || this.props.dirtyTodos) &&
!this.props.fetchingLists
) {
this.props.fetchLists();
}
}
render() {
return ( return (
<div id="user-header"> <div id="user-header">
<FetchButton>sync</FetchButton> <FetchButton>sync</FetchButton>
@@ -13,3 +24,11 @@ export default function UserHeader() {
</div> </div>
); );
} }
}
UserHeader.propTypes = {
dirtyLists: PropTypes.bool.isRequired,
dirtyTodos: PropTypes.bool.isRequired,
fetchingLists: PropTypes.bool.isRequired,
fetchLists: PropTypes.func.isRequired,
};

View File

@@ -13,8 +13,8 @@ function Status({ userFetching, listsFetching }) {
marginLeft: '1rem', marginLeft: '1rem',
}} }}
> >
{userFetching ? 'user' : null} {userFetching ? 'loading user' : null}
{listsFetching ? 'lists' : null} {listsFetching ? 'loading lists' : null}
</ButtonBase> </ButtonBase>
); );
} }

View File

@@ -1,10 +1,23 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import UserHeader from '../components/UserHeader'; import UserHeader from '../components/UserHeader';
import { fetchLists } from '../actions/lists';
function mapStateToProps(state) { function mapStateToProps(state) {
return { return {
user: state.user, user: state.user,
dirtyLists: state.lists.dirty,
dirtyTodos: state.todos.dirty,
fetchingLists: state.lists.fetching,
}; };
} }
export default connect(mapStateToProps)(UserHeader); function mapDispatchToProps(dispatch) {
return {
fetchLists: () => dispatch(fetchLists()),
};
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(UserHeader);

View File

@@ -53,7 +53,7 @@ export default function todos(
...state, ...state,
todos: { todos: {
...state.todos, ...state.todos,
[action.id]: action.todo, [action.id]: { ...state.todos[action.id], text: action.text },
}, },
}; };
case REQUEST_TODOS: case REQUEST_TODOS: