Skip to content
This repository has been archived by the owner on May 11, 2021. It is now read-only.

Commit

Permalink
feat: replace profile by default currency
Browse files Browse the repository at this point in the history
  • Loading branch information
igorkamyshev committed May 23, 2020
1 parent ba2a868 commit 943205e
Show file tree
Hide file tree
Showing 33 changed files with 396 additions and 129 deletions.
2 changes: 0 additions & 2 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { Option } from 'tsoption';

import { AppContext } from '&front/domain/AppContext';
import { WithReduxProps, withReduxStore } from '&front/domain/store';
import { fetchUserProfile } from '&front/domain/user/actions/fetchUserProfile';
import { actions as dataActions } from '&front/domain/user/reducer/data';
import { getToken } from '&front/domain/user/selectors/getToken';
import { pushRoute, routeAnimations } from '&front/features/routing';
Expand All @@ -28,7 +27,6 @@ class CheckmoneyWeb extends App<WithReduxProps> {

if (token.nonEmpty()) {
ctx.reduxStore.dispatch(dataActions.setToken(token.get()));
await ctx.reduxStore.dispatch(fetchUserProfile() as any);
}

const isSecure = !!(appContext.Component as any).isSecure;
Expand Down
24 changes: 21 additions & 3 deletions src/app/api/api.sagas.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
import getConfig from 'next/config';
import { select } from 'redux-saga/effects';
import { select, put } from 'redux-saga/effects';

import { StatisticsApi } from '&front/app/api/parts/StatisticsApi';
import { getTokenValue } from '&front/domain/user/selectors/getToken';
import { actions as authAction } from '&front/app/auth/auth.actions';

import { ProfileApi } from './parts/ProfileApi';

const { publicRuntimeConfig } = getConfig();
const { statsUrl } = publicRuntimeConfig;
const { statsUrl, backUrl } = publicRuntimeConfig;

export function* createStatisticsApi() {
const token: ReturnType<typeof getTokenValue> = yield select(getTokenValue);

if (!token) {
// TODO: specicif
yield put(authAction.unauthorized());
throw new Error('Try to use private API for anon user');
}

const apiClient = new StatisticsApi(statsUrl, token);

return apiClient;
}

export function* createProfileApi() {
const token: ReturnType<typeof getTokenValue> = yield select(getTokenValue);

if (!token) {
yield put(authAction.unauthorized());
throw new Error('Try to use private API for anon user');
}

const profileUrl = `${backUrl}/det-bell`;

const apiClient = new ProfileApi(profileUrl, token);

return apiClient;
}
26 changes: 26 additions & 0 deletions src/app/api/parts/ProfileApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Axios, { AxiosInstance } from 'axios';

import { Currency } from '&shared/enum/Currency';

import { addTokenToHttpConfig } from '../api.utils';

export class ProfileApi {
private readonly http: AxiosInstance;

constructor(url: string, private readonly token: string) {
const serviceUrl = url;

this.http = Axios.create({
baseURL: serviceUrl,
});
}

getCurrency = async (): Promise<Currency> => {
const { data } = await this.http.get(
`v1/default-currency`,
addTokenToHttpConfig(this.token, {}),
);

return data;
};
}
38 changes: 23 additions & 15 deletions src/app/api/parts/StatisticsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { PeriodCategories } from '&front/app/statistics/categories.types';
import { Grow } from '&front/app/statistics/grow.types';
import { PeriodAmount } from '&front/app/statistics/periods.types';
import { GroupBy } from '&shared/enum/GroupBy';
import { Currency } from '&shared/enum/Currency';

import { Interval } from '../api.types';
import { addTokenToHttpConfig, intervalForQuery } from '../api.utils';
Expand All @@ -20,40 +21,47 @@ export class StatisticsApi {
});
}

findGrow = async (periodType: GroupBy): Promise<Grow> => {
const { data } = await this.http.get(
findGrow = async (periodType: GroupBy) => {
const { data, headers } = await this.http.get(
`v1/statistics/grow?periodType=${periodType}`,
addTokenToHttpConfig(this.token, {}),
);

return plainToClass(Grow, data);
return {
data: plainToClass(Grow, data),
meta: this.getMeta(headers),
};
};

fetchCategories = async (
periodType: GroupBy,
dateRange: Interval,
): Promise<PeriodCategories[]> => {
const { data } = await this.http.get(
fetchCategories = async (periodType: GroupBy, dateRange: Interval) => {
const { data, headers } = await this.http.get(
`v1/statistics/categories?periodType=${periodType}&${intervalForQuery(
dateRange,
)}`,
addTokenToHttpConfig(this.token, {}),
);

return plainToClass(PeriodCategories, data);
return {
data: plainToClass(PeriodCategories, data),
meta: this.getMeta(headers),
};
};

fetchPeriods = async (
periodType: GroupBy,
dateRange: Interval,
): Promise<PeriodAmount[]> => {
const { data } = await this.http.get(
fetchPeriods = async (periodType: GroupBy, dateRange: Interval) => {
const { data, headers } = await this.http.get(
`v1/statistics/periods?periodType=${periodType}&${intervalForQuery(
dateRange,
)}`,
addTokenToHttpConfig(this.token, {}),
);

return plainToClass(PeriodAmount, data);
return {
data: plainToClass(PeriodAmount, data),
meta: this.getMeta(headers),
};
};

private getMeta = (headers: any) => ({
currency: headers['checkmoney-currency'] as Currency,
});
}
7 changes: 7 additions & 0 deletions src/app/auth/auth.actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import actionCreatorFactory from 'typescript-fsa';

const actionCreator = actionCreatorFactory('AUTH');

export const actions = {
unauthorized: actionCreator('UNAUTHORIZED'),
};
13 changes: 13 additions & 0 deletions src/app/auth/auth.sagas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { takeLatest, put } from 'redux-saga/effects';

import { actions } from './auth.actions';
import { actions as routerActions } from '../router/router.actions';

export function* handleLogoutSaga() {
yield takeLatest(
actions.unauthorized.type,
function* handleUnauthorizedAction() {
yield put(routerActions.push('/'));
},
);
}
24 changes: 24 additions & 0 deletions src/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,33 @@ import {
CategoriesState,
categoriesReducer,
} from './statistics/categories.reducers';
import {
DefaultCurrencyState,
defaultCurrencyReducer,
} from './profile/default_currency.reducers';
import { handleCategoriesFetchingSaga } from './statistics/categories.sagas';
import { GrowState, growReducer } from './statistics/grow.reducers';
import { handleGrowFetchingSaga } from './statistics/grow.sagas';
import { PeriodsState, periodsReducer } from './statistics/periods.reducers';
import { handlePeriodsFetchingSaga } from './statistics/periods.sagas';
import { handleLogoutSaga } from './auth/auth.sagas';
import { handleRouterPushSaga } from './router/router.sagas';
import { handleDefaultCurrencyFetchingSaga } from './profile/default_currency.sagas';
import { handleRequireRemoteDataSaga } from './utility/require.sagas';
import {
StatisticsMetaState,
statisticsMetaReducer,
} from './statistics/meta.reducers';

export function* applicationSaga() {
yield all([
handleGrowFetchingSaga(),
handleCategoriesFetchingSaga(),
handlePeriodsFetchingSaga(),
handleDefaultCurrencyFetchingSaga(),
handleRequireRemoteDataSaga(),
handleRouterPushSaga(),
handleLogoutSaga(),
]);
}

Expand All @@ -24,6 +40,10 @@ export interface ApplicationState {
grow: GrowState;
categories: CategoriesState;
periods: PeriodsState;
meta: StatisticsMetaState;
};
profile: {
defaultCurrency: DefaultCurrencyState;
};
}

Expand All @@ -32,5 +52,9 @@ export const applicationReducer = combineReducers({
grow: growReducer,
categories: categoriesReducer,
periods: periodsReducer,
meta: statisticsMetaReducer,
}),
profile: combineReducers({
defaultCurrency: defaultCurrencyReducer,
}),
});
13 changes: 13 additions & 0 deletions src/app/profile/default_currency.actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import actionCreatorFactory from 'typescript-fsa';

import { Currency } from '&shared/enum/Currency';

const actionCreator = actionCreatorFactory('DEFAULT_CURRENCY');

export const actions = actionCreator.async<
{
attempt?: number;
},
Currency,
string
>('FETCHING');
26 changes: 26 additions & 0 deletions src/app/profile/default_currency.reducers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { reducerWithInitialState } from 'typescript-fsa-reducers';

import { Currency } from '&shared/enum/Currency';

import { actions } from './default_currency.actions';

type StateData = { value?: Currency };

export interface DefaultCurrencyState extends StateData {
error: string | null;
}

const initialState: DefaultCurrencyState = {
error: null,
};

export const defaultCurrencyReducer = reducerWithInitialState(initialState)
.case(actions.done, (state, { result }) => ({
...state,
value: result,
error: null,
}))
.case(actions.failed, (state, { error }) => ({
...state,
error,
}));
38 changes: 38 additions & 0 deletions src/app/profile/default_currency.sagas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { takeLatest, put, call, delay } from 'redux-saga/effects';

import { createProfileApi } from '&front/app/api/api.sagas';
import { logError } from '&front/app/utility/errors.sagas';
import { Currency } from '&shared/enum/Currency';

import { actions } from './default_currency.actions';
import { ProfileApi } from '../api/parts/ProfileApi';

const ATTEMPT_THRESHOLD = 3;
const RETRY_DELAY = 100;

export function* handleDefaultCurrencyFetchingSaga() {
yield takeLatest(actions.started.type, function* (
action: ReturnType<typeof actions.started>,
) {
const { attempt = 0 } = action.payload;

try {
const apiClient: ProfileApi = yield createProfileApi();
const data: Currency = yield call(apiClient.getCurrency);
yield put(actions.done({ result: data, params: action.payload }));
} catch (error) {
yield logError(error);

if (attempt >= ATTEMPT_THRESHOLD) {
yield put(
actions.failed({ params: action.payload, error: 'Unknown error' }),
);
return;
}

// Ok, lets retry
yield delay(RETRY_DELAY * attempt);
yield put(actions.started({ attempt: attempt + 1 }));
}
});
}
7 changes: 7 additions & 0 deletions src/app/profile/default_currency.selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { State } from '&front/domain/store/State';

export const selectDefaultCurrency = (state: State) =>
state.application.profile.defaultCurrency.value;

export const selectDefaultCurrencyIsAvailable = (state: State) =>
Boolean(selectDefaultCurrency(state));
7 changes: 7 additions & 0 deletions src/app/router/router.actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import actionCreatorFactory from 'typescript-fsa';

const actionCreator = actionCreatorFactory('ROUTER');

export const actions = {
push: actionCreator<string>('PUSH'),
};
14 changes: 14 additions & 0 deletions src/app/router/router.sagas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { takeLatest } from 'redux-saga/effects';

import { pushRoute } from '&front/features/routing';

import { actions } from './router.actions';

export function* handleRouterPushSaga() {
// eslint-disable-next-line require-yield
yield takeLatest(actions.push.type, function* (
action: ReturnType<typeof actions.push>,
) {
pushRoute(action.payload);
});
}
6 changes: 4 additions & 2 deletions src/app/statistics/categories.sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createStatisticsApi } from '&front/app/api/api.sagas';
import { logError } from '&front/app/utility/errors.sagas';

import { actions } from './categories.actions';
import { PeriodCategories } from './categories.types';
import { actions as metaActions } from './meta.actions';

const ATTEMPT_THRESHOLD = 3;
const RETRY_DELAY = 100;
Expand All @@ -18,11 +18,13 @@ export function* handleCategoriesFetchingSaga() {

try {
const apiClient: StatisticsApi = yield createStatisticsApi();
const data: PeriodCategories[] = yield call(
const { data, meta } = yield call(
apiClient.fetchCategories,
periodType,
dateRange,
);

yield put(metaActions.setCurrency(meta.currency));
yield put(actions.done({ params: action.payload, result: data }));
} catch (error) {
yield logError(error);
Expand Down
6 changes: 4 additions & 2 deletions src/app/statistics/grow.sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createStatisticsApi } from '&front/app/api/api.sagas';
import { logError } from '&front/app/utility/errors.sagas';

import { actions } from './grow.actions';
import { Grow } from './grow.types';
import { actions as metaActions } from './meta.actions';

const ATTEMPT_THRESHOLD = 3;
const RETRY_DELAY = 100;
Expand All @@ -18,7 +18,9 @@ export function* handleGrowFetchingSaga() {

try {
const apiClient: StatisticsApi = yield createStatisticsApi();
const data: Grow = yield call(apiClient.findGrow, periodType);
const { data, meta } = yield call(apiClient.findGrow, periodType);

yield put(metaActions.setCurrency(meta.currency));
yield put(actions.done({ params: action.payload, result: data }));
} catch (error) {
yield logError(error);
Expand Down
9 changes: 9 additions & 0 deletions src/app/statistics/meta.actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import actionCreatorFactory from 'typescript-fsa';

import { Currency } from '&shared/enum/Currency';

const actionCreator = actionCreatorFactory('STATISTICS_META');

export const actions = {
setCurrency: actionCreator<Currency>('SET_CURRENCY'),
};
Loading

0 comments on commit 943205e

Please sign in to comment.