mirror of
https://github.com/usatiuk/ustk-todolist.git
synced 2025-10-28 23:57:49 +01:00
use redux-offline
This commit is contained in:
19
react/package-lock.json
generated
19
react/package-lock.json
generated
@@ -61,6 +61,15 @@
|
|||||||
"recompose": "^0.26.0 || ^0.27.0"
|
"recompose": "^0.26.0 || ^0.27.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@redux-offline/redux-offline": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redux-offline/redux-offline/-/redux-offline-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-uv0DW9ZAFzL+lc7WzjQoDoWydReJiZe+Rpz6suGCS5ux+ZJWkr3jpRzeWlTW149uo+OrEk1BchNAE7FCTakXjQ==",
|
||||||
|
"requires": {
|
||||||
|
"babel-runtime": "^6.26.0",
|
||||||
|
"redux-persist": "^4.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/jss": {
|
"@types/jss": {
|
||||||
"version": "9.5.3",
|
"version": "9.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/jss/-/jss-9.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/jss/-/jss-9.5.3.tgz",
|
||||||
@@ -9752,6 +9761,16 @@
|
|||||||
"prop-types": "^15.6.1"
|
"prop-types": "^15.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"redux-persist": {
|
||||||
|
"version": "4.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-4.10.2.tgz",
|
||||||
|
"integrity": "sha512-U+e0ieMGC69Zr72929iJW40dEld7Mflh6mu0eJtVMLGfMq/aJqjxUM1hzyUWMR1VUyAEEdPHuQmeq5ti9krIgg==",
|
||||||
|
"requires": {
|
||||||
|
"json-stringify-safe": "^5.0.1",
|
||||||
|
"lodash": "^4.17.4",
|
||||||
|
"lodash-es": "^4.17.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"redux-thunk": {
|
"redux-thunk": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material-ui/core": "^1.1.0",
|
"@material-ui/core": "^1.1.0",
|
||||||
"@material-ui/icons": "^1.1.0",
|
"@material-ui/icons": "^1.1.0",
|
||||||
|
"@redux-offline/redux-offline": "^2.3.3",
|
||||||
"localforage": "^1.7.1",
|
"localforage": "^1.7.1",
|
||||||
"prop-types": "^15.6.1",
|
"prop-types": "^15.6.1",
|
||||||
"react": "^16.4.0",
|
"react": "^16.4.0",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import localforage from 'localforage';
|
|
||||||
import { API_ROOT, getToken } from './util';
|
import { API_ROOT, getToken } from './util';
|
||||||
import { RECIEVE_TODOS } from './todos';
|
import { RECIEVE_TODOS } from './todos';
|
||||||
|
|
||||||
@@ -51,7 +50,7 @@ export function addList(name) {
|
|||||||
const response = await fetch(`${API_ROOT}/lists`, {
|
const response = await fetch(`${API_ROOT}/lists`, {
|
||||||
body: JSON.stringify({ name }),
|
body: JSON.stringify({ name }),
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${await getToken()}`,
|
Authorization: `Bearer ${getToken()}`,
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
},
|
},
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -71,7 +70,7 @@ export function removeList() {
|
|||||||
dispatch(invalidateLists());
|
dispatch(invalidateLists());
|
||||||
const response = await fetch(`${API_ROOT}/lists/${list}`, {
|
const response = await fetch(`${API_ROOT}/lists/${list}`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${await getToken()}`,
|
Authorization: `Bearer ${getToken()}`,
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
},
|
},
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
@@ -96,7 +95,7 @@ export function editList(name) {
|
|||||||
const response = await fetch(`${API_ROOT}/lists/${list}`, {
|
const response = await fetch(`${API_ROOT}/lists/${list}`, {
|
||||||
body: JSON.stringify({ name }),
|
body: JSON.stringify({ name }),
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${await getToken()}`,
|
Authorization: `Bearer ${getToken()}`,
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
},
|
},
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
@@ -127,7 +126,7 @@ export function fetchLists() {
|
|||||||
dispatch(requestLists());
|
dispatch(requestLists());
|
||||||
const response = await fetch(`${API_ROOT}/lists`, {
|
const response = await fetch(`${API_ROOT}/lists`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${await getToken()}`,
|
Authorization: `Bearer ${getToken()}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
@@ -149,22 +148,12 @@ export function fetchLists() {
|
|||||||
if (lists.length !== 0) {
|
if (lists.length !== 0) {
|
||||||
dispatch(changeList(listsObj[Object.keys(listsObj)[0]].id));
|
dispatch(changeList(listsObj[Object.keys(listsObj)[0]].id));
|
||||||
}
|
}
|
||||||
await localforage.setItem('lists', listsObj);
|
|
||||||
await localforage.setItem('todos', normalizeTodos(lists));
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadLists() {
|
export function loadLists() {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
dispatch(requestLists());
|
dispatch(requestLists());
|
||||||
|
|
||||||
const lists = await localforage.getItem('lists');
|
|
||||||
const todos = await localforage.getItem('todos');
|
|
||||||
dispatch(recieveLists(lists));
|
|
||||||
dispatch({ type: RECIEVE_TODOS, todos });
|
|
||||||
if (lists && Object.keys(lists).length) {
|
|
||||||
dispatch(changeList(lists[Object.keys(lists)[0]].id));
|
|
||||||
}
|
|
||||||
dispatch(fetchLists());
|
dispatch(fetchLists());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function fetchTodos() {
|
|||||||
dispatch({ type: REQUEST_TODOS });
|
dispatch({ type: REQUEST_TODOS });
|
||||||
const response = await fetch(`${API_ROOT}/todos`, {
|
const response = await fetch(`${API_ROOT}/todos`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${await getToken()}`,
|
Authorization: `Bearer ${getToken()}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
@@ -51,7 +51,7 @@ export function addTodo(text) {
|
|||||||
const response = await fetch(`${API_ROOT}/lists/${list}/todos`, {
|
const response = await fetch(`${API_ROOT}/lists/${list}/todos`, {
|
||||||
body: JSON.stringify({ text }),
|
body: JSON.stringify({ text }),
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${await getToken()}`,
|
Authorization: `Bearer ${getToken()}`,
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
},
|
},
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -73,7 +73,7 @@ export function removeTodo(id) {
|
|||||||
dispatch(invalidateTodos());
|
dispatch(invalidateTodos());
|
||||||
const response = await fetch(`${API_ROOT}/todos/${id}`, {
|
const response = await fetch(`${API_ROOT}/todos/${id}`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${await getToken()}`,
|
Authorization: `Bearer ${getToken()}`,
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
},
|
},
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
@@ -97,7 +97,7 @@ export function toggleTodo(id) {
|
|||||||
const response = await fetch(`${API_ROOT}/todos/${id}`, {
|
const response = await fetch(`${API_ROOT}/todos/${id}`, {
|
||||||
body: JSON.stringify({ completed }),
|
body: JSON.stringify({ completed }),
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${await getToken()}`,
|
Authorization: `Bearer ${getToken()}`,
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
},
|
},
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
@@ -118,7 +118,7 @@ export function editTodo(id, text) {
|
|||||||
const response = await fetch(`${API_ROOT}/todos/${id}`, {
|
const response = await fetch(`${API_ROOT}/todos/${id}`, {
|
||||||
body: JSON.stringify({ text }),
|
body: JSON.stringify({ text }),
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${await getToken()}`,
|
Authorization: `Bearer ${getToken()}`,
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
},
|
},
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import localforage from 'localforage';
|
import { API_ROOT, getToken, setToken } from './util';
|
||||||
import { API_ROOT, getToken } from './util';
|
|
||||||
import { loadLists } from './lists';
|
import { loadLists } from './lists';
|
||||||
|
|
||||||
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
|
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
|
||||||
@@ -30,21 +29,16 @@ function validateUser() {
|
|||||||
|
|
||||||
export function loadUser() {
|
export function loadUser() {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
if (await getToken()) {
|
if (getToken()) {
|
||||||
const user = await localforage.getItem('user');
|
|
||||||
dispatch(loginSuccess(user));
|
|
||||||
dispatch(loadLists());
|
|
||||||
|
|
||||||
const response = await fetch(`${API_ROOT}/users/user`, {
|
const response = await fetch(`${API_ROOT}/users/user`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${await getToken()}`,
|
Authorization: `Bearer ${getToken()}`,
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
},
|
},
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
if (json.success) {
|
if (json.success) {
|
||||||
await localforage.setItem('user', json.data);
|
|
||||||
dispatch(loginSuccess(json.data));
|
dispatch(loginSuccess(json.data));
|
||||||
dispatch(loadLists());
|
dispatch(loadLists());
|
||||||
} else {
|
} else {
|
||||||
@@ -68,7 +62,7 @@ export function login(user) {
|
|||||||
});
|
});
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
if (json.success) {
|
if (json.success) {
|
||||||
await localforage.setItem('user', json.data);
|
setToken(json.data.jwt);
|
||||||
dispatch(loginSuccess(json.data));
|
dispatch(loginSuccess(json.data));
|
||||||
dispatch(loadLists());
|
dispatch(loadLists());
|
||||||
} else {
|
} else {
|
||||||
@@ -97,7 +91,7 @@ export function signup(user) {
|
|||||||
});
|
});
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
if (json.success) {
|
if (json.success) {
|
||||||
await await localforage.setItem('user', json.data);
|
setToken(json.data.jwt);
|
||||||
dispatch(signupSuccess(json.data));
|
dispatch(signupSuccess(json.data));
|
||||||
dispatch(loadLists());
|
dispatch(loadLists());
|
||||||
} else {
|
} else {
|
||||||
@@ -112,9 +106,6 @@ export function reset() {
|
|||||||
|
|
||||||
export function logout() {
|
export function logout() {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
await localforage.removeItem('user');
|
|
||||||
await localforage.removeItem('lists');
|
|
||||||
await localforage.removeItem('items');
|
|
||||||
dispatch({ type: LOGOUT });
|
dispatch({ type: LOGOUT });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import localforage from 'localforage';
|
|
||||||
|
|
||||||
export const API_ROOT = '/api';
|
export const API_ROOT = '/api';
|
||||||
|
|
||||||
export async function getToken() {
|
let token = null;
|
||||||
const user = await localforage.getItem('user');
|
|
||||||
return user ? user.jwt : null;
|
export function setToken(_token) {
|
||||||
|
token = _token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getToken() {
|
||||||
|
return token;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import FilterLink from '../containers/FilterLink';
|
import FilterLink from '../containers/FilterLink';
|
||||||
import LogoutLink from '../containers/LogoutLink';
|
import UserHeaderContainer from '../containers/UserHeaderContainer';
|
||||||
import { VisibilityFilters } from '../actions/todos';
|
import { VisibilityFilters } from '../actions/todos';
|
||||||
import ListsContainer from '../containers/ListsContainer';
|
import ListsContainer from '../containers/ListsContainer';
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
return (
|
return (
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<div id="user-header">
|
<UserHeaderContainer />
|
||||||
<LogoutLink>logout</LogoutLink>
|
|
||||||
</div>
|
|
||||||
<div id="lists-header">
|
<div id="lists-header">
|
||||||
<ListsContainer />
|
<ListsContainer />
|
||||||
<div id="filters">
|
<div id="filters">
|
||||||
|
|||||||
15
react/src/components/UserHeader.js
Normal file
15
react/src/components/UserHeader.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import LogoutLink from '../containers/LogoutLink';
|
||||||
|
import FetchButton from '../containers/FetchButton';
|
||||||
|
import Status from '../containers/Status';
|
||||||
|
|
||||||
|
export default function UserHeader() {
|
||||||
|
return (
|
||||||
|
<div id="user-header">
|
||||||
|
<FetchButton>sync</FetchButton>
|
||||||
|
<Status />
|
||||||
|
<LogoutLink>logout</LogoutLink>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
function UserErrors({ user }) {
|
function UserErrors({ user }) {
|
||||||
let errors = [];
|
const errors = [];
|
||||||
if (user.errors) {
|
if (user.errors) {
|
||||||
if (user.errors.name === 'AuthenticationError') {
|
if (user.errors.name === 'AuthenticationError') {
|
||||||
errors.push(
|
errors.push(
|
||||||
|
|||||||
41
react/src/containers/FetchButton.js
Normal file
41
react/src/containers/FetchButton.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { ButtonBase } from '@material-ui/core';
|
||||||
|
|
||||||
|
import { fetchLists } from '../actions/lists';
|
||||||
|
|
||||||
|
function FetchButton({ onClick, children }) {
|
||||||
|
return (
|
||||||
|
<ButtonBase
|
||||||
|
style={{
|
||||||
|
marginRight: 'auto',
|
||||||
|
padding: '0 0.5rem',
|
||||||
|
borderRadius: '7px',
|
||||||
|
marginLeft: '1rem',
|
||||||
|
}}
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
onClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ButtonBase>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FetchButton.propTypes = {
|
||||||
|
onClick: PropTypes.func.isRequired,
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return {
|
||||||
|
onClick: () => dispatch(fetchLists()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
null,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(FetchButton);
|
||||||
@@ -6,7 +6,6 @@ import { ButtonBase } from '@material-ui/core';
|
|||||||
import { logout } from '../actions/user';
|
import { logout } from '../actions/user';
|
||||||
|
|
||||||
function Link({ onClick, children }) {
|
function Link({ onClick, children }) {
|
||||||
const classes = ['logout'];
|
|
||||||
return (
|
return (
|
||||||
<ButtonBase
|
<ButtonBase
|
||||||
style={{
|
style={{
|
||||||
@@ -14,7 +13,6 @@ function Link({ onClick, children }) {
|
|||||||
padding: '0 0.5rem',
|
padding: '0 0.5rem',
|
||||||
borderRadius: '7px',
|
borderRadius: '7px',
|
||||||
}}
|
}}
|
||||||
className={classes.join(' ')}
|
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onClick();
|
onClick();
|
||||||
|
|||||||
34
react/src/containers/Status.js
Normal file
34
react/src/containers/Status.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { ButtonBase } from '@material-ui/core';
|
||||||
|
|
||||||
|
function Status({ userFetching, listsFetching }) {
|
||||||
|
return (
|
||||||
|
<ButtonBase
|
||||||
|
style={{
|
||||||
|
marginRight: 'auto',
|
||||||
|
padding: '0 0.5rem',
|
||||||
|
borderRadius: '7px',
|
||||||
|
marginLeft: '1rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{userFetching ? 'user' : null}
|
||||||
|
{listsFetching ? 'lists' : null}
|
||||||
|
</ButtonBase>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Status.propTypes = {
|
||||||
|
userFetching: PropTypes.bool.isRequired,
|
||||||
|
listsFetching: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
userFetching: state.user.fetching,
|
||||||
|
listsFetching: state.lists.fetching,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(Status);
|
||||||
10
react/src/containers/UserHeaderContainer.js
Normal file
10
react/src/containers/UserHeaderContainer.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import UserHeader from '../components/UserHeader';
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
user: state.user,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(UserHeader);
|
||||||
@@ -2,18 +2,36 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { createStore, applyMiddleware } from 'redux';
|
import { applyMiddleware, createStore, compose } from 'redux';
|
||||||
|
import { offline } from '@redux-offline/redux-offline';
|
||||||
|
import offlineConfig from '@redux-offline/redux-offline/lib/defaults';
|
||||||
|
|
||||||
import AppContainer from './containers/AppContainer';
|
import AppContainer from './containers/AppContainer';
|
||||||
import registerServiceWorker from './registerServiceWorker';
|
import registerServiceWorker from './registerServiceWorker';
|
||||||
import todoApp from './reducers';
|
import todoApp from './reducers';
|
||||||
|
import { setToken } from './actions/util';
|
||||||
|
|
||||||
const store = createStore(todoApp, applyMiddleware(thunk));
|
let store;
|
||||||
|
|
||||||
ReactDOM.render(
|
const persistCallback = () => {
|
||||||
|
const state = store.getState();
|
||||||
|
if (state.user.user) {
|
||||||
|
setToken(state.user.user.jwt);
|
||||||
|
}
|
||||||
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<AppContainer />
|
<AppContainer />
|
||||||
</Provider>,
|
</Provider>,
|
||||||
document.getElementById('root'),
|
document.getElementById('root'),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
store = createStore(
|
||||||
|
todoApp,
|
||||||
|
compose(
|
||||||
|
applyMiddleware(thunk),
|
||||||
|
offline({ ...offlineConfig, persistCallback }),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
registerServiceWorker();
|
registerServiceWorker();
|
||||||
|
|||||||
Reference in New Issue
Block a user