Commit f9c22c5a authored by Matija Obreza's avatar Matija Obreza
Browse files

ApiCall in state and 'API' saga

parent 3c66c30b
class ApiCall<T> {
public loading: boolean;
public error: string;
public timestamp: Date | number; // last successful execution (helps with invalidating cached data)
public data: T;
public progress?: number;
public static start = () => ({
loading: true,
error: null,
})
public static progress = (progress) => ({
loading: true,
error: null,
progress,
})
public static success = (data) => ({
loading: false,
data,
timestamp: Date.now(),
error: null,
})
public static error = (error) => {
console.log('ApiCall error', error);
return {
loading: false,
data: null,
timestamp: Date.now(),
error: `${error.message} ${error.response && error.response.data && error.response.data.error || ''}`,
};
}
}
export default ApiCall;
......@@ -5,3 +5,4 @@ export { default as IVersionedEntity } from '@gringlobal/client/model/common/IVe
export { default as VersionedModel } from '@gringlobal/client/model/common/VersionedModel';
export { default as AuditedVersionedModel } from '@gringlobal/client/model/common/AuditedVersionedModel';
export { default as UuidModel } from '@gringlobal/client/model/common/UuidModel';
export { default as ApiCall } from '@gringlobal/client/model/common/ApiCall';
import * as React from 'react';
import { WithTranslation, withTranslation } from 'react-i18next';
// import CircularProgress from '@material-ui/core/CircularProgress';
import CircularProgress from '@material-ui/core/CircularProgress';
interface ILoadingProps extends React.ClassAttributes<any>, WithTranslation {
id?: string;
......@@ -30,6 +30,7 @@ class Loading extends React.Component<ILoadingProps, any> {
return noShow && !immediatelyShow ? null : (
<div className={ className } id={ id } style={ { textAlign: 'center', padding: '5rem' } }>
<CircularProgress />
<div className="font-bold uppercase">{ message || t('common:label.loadingData') }</div>
</div>
);
......
......@@ -4,7 +4,6 @@ import { put, takeEvery } from 'redux-saga/effects';
import {
RECEIVE_COOPERATOR, RECEIVE_COOPERATORS,
SAGA_RECEIVE_COOPERATOR, SAGA_RECEIVE_COOPERATORS,
API_RECEIVE_COOPERATOR, API_RECEIVE_COOPERATORS,
} from 'cooperator/constants';
// Model
import Cooperator from '@gringlobal/client/model/gringlobal/Cooperator';
......@@ -30,18 +29,14 @@ export const getCooperatorAction = (id) => ({
function* getCooperatorSaga(action) {
const { id } = action.payload;
yield put({
type: API_RECEIVE_COOPERATOR,
type: 'API',
target: RECEIVE_COOPERATOR,
method: CooperatorService.getCooperator,
params: [id],
onSuccess: (cooperator) => receiveCooperatorSuccess(cooperator),
// onSuccess: (cooperator) => receiveCooperatorSuccess(cooperator),
});
}
const receiveCooperatorSuccess = (cooperator: Cooperator) => ({
type: RECEIVE_COOPERATOR,
payload: { cooperator },
});
// #listUsers
export const listCooperatorsAction = (filter: string = '', pageR: IPageRequest = { page: 0 }) => ({
......@@ -54,19 +49,14 @@ export const listCooperatorsAction = (filter: string = '', pageR: IPageRequest =
function * listCooperatorsSaga(action) {
yield put({
type: API_RECEIVE_COOPERATORS,
type: 'API',
target: RECEIVE_COOPERATORS,
method: CooperatorService.filter_1,
params: [action.payload.filter, action.payload.pageR],
onSuccess: (cooperators) => {
onSuccess: (cooperators: FilteredPage<Cooperator>) => {
dereferenceReferences(cooperators.content, { _self: 'cooperator', id: [ 'cooperator', 'ownedBy' ] });
// console.log(cooperators.content);
return receiveCooperatorsSuccess(cooperators);
return cooperators;
},
});
}
const receiveCooperatorsSuccess = (cooperators: FilteredPage<Cooperator>) => ({
type: RECEIVE_COOPERATORS,
payload: { cooperators },
});
......@@ -4,10 +4,11 @@ import { RECEIVE_COOPERATOR, RECEIVE_COOPERATORS } from 'cooperator/constants';
// Model
import Cooperator from '@gringlobal/client/model/gringlobal/Cooperator';
import { FilteredPage } from '@gringlobal/client/model/page';
import { ApiCall } from '@gringlobal/client/model/common';
const initialState: {
cooperators: FilteredPage<Cooperator>,
cooperator: Cooperator,
cooperators: ApiCall<FilteredPage<Cooperator>>,
cooperator: ApiCall<Cooperator>,
} = {
cooperators: null,
cooperator: null,
......@@ -16,27 +17,29 @@ const initialState: {
const userReducer = (state = initialState, action) => {
switch (action.type) {
case RECEIVE_COOPERATOR: {
const { cooperator } = action.payload;
const { cooperators } = state;
const updatedIndex = cooperators && cooperators.content && cooperators.content.findIndex((userDetails) => userDetails.id === cooperator.id) || -1;
if (updatedIndex !== -1) {
return update(state, {
cooperator: { $set: cooperator },
cooperators: {
[updatedIndex]: { $set: cooperator },
},
});
} else {
return update(state, {
cooperator: { $set: cooperator },
});
const { apiCall } = action.payload;
if (apiCall.data) {
const { data: cooperators } = state.cooperators;
const cooperator = apiCall.data;
const updatedIndex = cooperators && cooperators.content && cooperators.content.findIndex((cooperator) => cooperator.id === cooperator.id) || -1;
if (updatedIndex !== -1) {
return update(state, {
cooperator: { $set: cooperator },
cooperators: {
[updatedIndex]: { $set: cooperator },
},
});
}
}
return update(state, {
cooperator: { $set: apiCall },
});
}
case RECEIVE_COOPERATORS: {
const { cooperators } = action.payload;
const { apiCall } = action.payload;
return update(state, {
cooperators: { $set: cooperators },
cooperators: { $set: apiCall },
});
}
default:
......
......@@ -4,16 +4,18 @@ import { bindActionCreators, compose } from 'redux';
import { WithTranslation, withTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
// Action
import { ApiCall } from '@gringlobal/client/model/common';
import { listCooperatorsAction } from 'cooperator/action/public';
// Model
import Cooperator from '@gringlobal/client/model/gringlobal/Cooperator';
import { FilteredPage } from '@gringlobal/client/model/page';
// Ui
import BackButton from '@gringlobal/client/ui/common/button/BackButton';
import Loading from '@gringlobal/client/ui/common/Loading';
interface IBrowsePageProps extends React.ClassAttributes<any>, WithTranslation {
listCooperatorsAction: () => void;
cooperators: FilteredPage<Cooperator>;
cooperators: ApiCall<FilteredPage<Cooperator>>;
}
class CooperatorBrowsePage extends React.Component<IBrowsePageProps> {
......@@ -25,7 +27,7 @@ class CooperatorBrowsePage extends React.Component<IBrowsePageProps> {
public componentWillMount(): void {
const { cooperators, listCooperatorsAction } = this.props;
if (!cooperators || !cooperators.content || cooperators.content.length === 0) {
if (!cooperators || !cooperators.loading && !cooperators.data.content) {
listCooperatorsAction();
}
}
......@@ -36,8 +38,9 @@ class CooperatorBrowsePage extends React.Component<IBrowsePageProps> {
return (
<>
<h3>{ t('cooperator.public.p.browse.title') }</h3>
{ (! cooperators || cooperators.loading) && <Loading /> }
<ul>
{ cooperators && cooperators.content && cooperators.content.length > 0 && cooperators.content.map((cooperator) => (
{ cooperators && cooperators.data && cooperators.data.content.length > 0 && cooperators.data.content.map((cooperator) => (
<li key={ cooperator.id }>
<Link to={ `/cooperator/${ cooperator.id }` }>{ cooperator.id } { cooperator.firstName } {cooperator.lastName} {cooperator.job}</Link>
</li>
......
import { all, takeEvery, select, call, put } from 'redux-saga/effects';
import { ApiCall } from '@gringlobal/client/model/common';
import { coreSagas } from '@gringlobal/client/action/saga';
......@@ -12,11 +13,15 @@ export default function*() {
...cooperatorPublicSagas,
...userPublicSagas,
takeEvery((action) => /^api\//.test(action.type), appendAxiosConfig),
// if action has method
takeEvery((action) => action.type === 'API' && !! action.method, appendAxiosConfig),
]);
}
function *appendAxiosConfig(action) {
console.log(`Appeding axios config for ${action.type}`);
yield put({ type: action.target, payload: { apiCall: ApiCall.start() } }); // Loading
const accessToken = yield select((state) => state.login.access_token);
const xhrConfig: any = {
......@@ -26,7 +31,9 @@ function *appendAxiosConfig(action) {
},
},
};
const resp = yield call(action.method, ...action.params, xhrConfig);
yield put(action.onSuccess(resp));
let resp = yield call(action.method, ...action.params, xhrConfig);
if (action.onSuccess) {
resp = action.onSuccess(resp); // postprocess
}
yield put({ type: action.target, payload: { apiCall: ApiCall.success(resp) } });
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment