feat: Fix axios interceptors.

This commit is contained in:
Ahmed Bouhuolia
2020-05-26 17:51:00 +02:00
parent dd49774f93
commit 72ba394c53
15 changed files with 369 additions and 223 deletions

View File

@@ -1,29 +1,21 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { IntlProvider } from 'react-intl'; import { IntlProvider } from 'react-intl';
import { connect } from 'react-redux'; import { Router, Switch, Route } from 'react-router';
import { Router, Switch, Redirect } from 'react-router';
import { createBrowserHistory } from 'history'; import { createBrowserHistory } from 'history';
import { ReactQueryConfigProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query-devtools';
import PrivateRoute from 'components/PrivateRoute'; import PrivateRoute from 'components/PrivateRoute';
import Authentication from 'components/Authentication'; import Authentication from 'components/Authentication';
import Dashboard from 'components/Dashboard/Dashboard'; import Dashboard from 'components/Dashboard/Dashboard';
import { isAuthenticated } from 'store/authentication/authentication.reducer' import GlobalErrors from 'containers/GlobalErrors/GlobalErrors';
import { ReactQueryConfigProvider } from 'react-query';
import { ReactQueryDevtools } from "react-query-devtools";
import messages from 'lang/en'; import messages from 'lang/en';
import 'style/App.scss'; import 'style/App.scss';
function App({ function App({ locale }) {
isAuthorized,
locale,
}) {
const history = createBrowserHistory(); const history = createBrowserHistory();
history.listen((location, action) => {
console.log(`new location via ${action}`, location);
});
const queryConfig = { const queryConfig = {
refetchAllOnWindowFocus: false, refetchAllOnWindowFocus: false,
cacheTime: 10000, cacheTime: 10000,
@@ -34,10 +26,18 @@ function App({
<div className="App"> <div className="App">
<ReactQueryConfigProvider config={queryConfig}> <ReactQueryConfigProvider config={queryConfig}>
<Router history={history}> <Router history={history}>
<Authentication isAuthenticated={isAuthorized} /> <Switch>
<PrivateRoute isAuthenticated={isAuthorized} component={Dashboard} /> <Route path={'/auth'}>
<Authentication />
</Route>
<Route path={'/'}>
<PrivateRoute component={Dashboard} />
</Route>
</Switch>
</Router> </Router>
<GlobalErrors />
<ReactQueryDevtools /> <ReactQueryDevtools />
</ReactQueryConfigProvider> </ReactQueryConfigProvider>
</div> </div>
@@ -49,10 +49,4 @@ App.defaultProps = {
locale: 'en', locale: 'en',
}; };
const mapStateToProps = (state) => { export default App;
return {
isAuthorized: isAuthenticated(state),
};
};
export default connect(mapStateToProps)(App);

View File

@@ -2,22 +2,22 @@ import React from 'react';
import { Redirect, Route, Switch, Link } from 'react-router-dom'; import { Redirect, Route, Switch, Link } from 'react-router-dom';
import BodyClassName from 'react-body-classname'; import BodyClassName from 'react-body-classname';
import authenticationRoutes from 'routes/authentication'; import authenticationRoutes from 'routes/authentication';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import withAuthentication from 'containers/Authentication/withAuthentication';
import { compose } from 'utils';
export default function AuthenticationWrapper({
isAuthenticated = false, function AuthenticationWrapper({ isAuthorized = false, ...rest }) {
...rest
}) {
const to = { pathname: '/homepage' }; const to = { pathname: '/homepage' };
return ( return (
<Route path='/auth'> <>
{isAuthenticated ? ( {isAuthorized ? (
<Redirect to={to} /> <Redirect to={to} />
) : ( ) : (
<BodyClassName className={'authentication'}> <BodyClassName className={'authentication'}>
<Switch> <Switch>
<div class='authentication-page'> <div class="authentication-page">
<Link <Link
to={'bigcapital.io'} to={'bigcapital.io'}
className={'authentication-page__goto-bigcapital'} className={'authentication-page__goto-bigcapital'}
@@ -25,7 +25,7 @@ export default function AuthenticationWrapper({
<T id={'go_to_bigcapital_com'} /> <T id={'go_to_bigcapital_com'} />
</Link> </Link>
<div class='authentication-page__form-wrapper'> <div class="authentication-page__form-wrapper">
{authenticationRoutes.map((route, index) => ( {authenticationRoutes.map((route, index) => (
<Route <Route
key={index} key={index}
@@ -39,6 +39,8 @@ export default function AuthenticationWrapper({
</Switch> </Switch>
</BodyClassName> </BodyClassName>
)} )}
</Route> </>
); );
} }
export default compose(withAuthentication)(AuthenticationWrapper);

View File

@@ -9,14 +9,13 @@ import {
Popover Popover
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import t from 'store/types'; import t from 'store/types';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
function DashboardTopbarUser({ logout }) { function DashboardTopbarUser({ logout }) {
const history = useHistory(); const history = useHistory();
const onClickLogout = useCallback(() => { const onClickLogout = useCallback(() => {
logout(); logout();
history.go('/auth/login');
}, [logout, history]); }, [logout, history]);
const userAvatarDropMenu = useMemo(() => ( const userAvatarDropMenu = useMemo(() => (

View File

@@ -1,37 +1,29 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import BodyClassName from 'react-body-classname'; import BodyClassName from 'react-body-classname';
import { Route, Redirect } from 'react-router-dom'; import { Redirect } from 'react-router-dom';
import withAuthentication from 'containers/Authentication/withAuthentication';
import { compose } from 'utils';
const propTypes = {
isAuthenticated: PropTypes.bool,
component: PropTypes.func.isRequired
};
function PrivateRoute({ function PrivateRoute({
component: Component, component: Component,
isAuthenticated = false, isAuthorized = false,
...rest ...rest
}) { }) {
return ( return (
<BodyClassName className={''}> <BodyClassName className={''}>
<Route {(isAuthorized) ? (
{...rest} <Component />
path="/" ) : (
render={_props =>
isAuthenticated ? (<Component {..._props} />) :
(
<Redirect <Redirect
to={{ to={{
pathname: '/auth/login', pathname: '/auth/login',
}} }}
/> />
)} )}
/>
</BodyClassName> </BodyClassName>
); );
} }
PrivateRoute.propTypes = propTypes; export default compose(withAuthentication)(PrivateRoute);
export default PrivateRoute;

View File

@@ -66,7 +66,6 @@ function Login({
crediential: values.crediential, crediential: values.crediential,
password: values.password, password: values.password,
}).then(() => { }).then(() => {
history.go('/homepage');
setSubmitting(false); setSubmitting(false);
}).catch((errors) => { }).catch((errors) => {
const toastBuilders = []; const toastBuilders = [];

View File

@@ -0,0 +1,11 @@
import { isAuthenticated } from 'store/authentication/authentication.reducer'
import { connect } from 'react-redux';
const mapStateToProps = (state) => {
return {
isAuthorized: isAuthenticated(state),
};
};
export default connect(mapStateToProps);

View File

@@ -0,0 +1,47 @@
import { Intent } from '@blueprintjs/core';
import { useIntl } from 'react-intl';
import AppToaster from 'components/AppToaster';
import withGlobalErrors from './withGlobalErrors';
import withGlobalErrorsActions from './withGlobalErrorsActions';
import { compose } from 'utils';
let toastKeySessionExpired;
let toastKeySomethingWrong;
function GlobalErrors({
// #withGlobalErrors
globalErrors,
// #withGlobalErrorsActions
globalErrorsSet,
}) {
const { formatMessage } = useIntl();
if (globalErrors.something_wrong) {
toastKeySessionExpired = AppToaster.show({
message: formatMessage({ id: 'ops_something_went_wrong' }),
intent: Intent.DANGER,
onDismiss: () => {
globalErrorsSet({ something_wrong: false });
}
}, toastKeySessionExpired);
}
if (globalErrors.session_expired) {
toastKeySomethingWrong = AppToaster.show({
message: formatMessage({ id: 'session_expired' }),
intent: Intent.DANGER,
onDismiss: () => {
globalErrorsSet({ session_expired: false });
}
}, toastKeySomethingWrong);
}
return null;
}
export default compose(
withGlobalErrors,
withGlobalErrorsActions,
)(GlobalErrors);

View File

@@ -0,0 +1,10 @@
import { connect } from 'react-redux';
const mapStateToProps = (state) => {
return {
globalErrors: state.globalErrors.data,
};
};
export default connect(mapStateToProps);

View File

@@ -0,0 +1,9 @@
import {connect} from 'react-redux';
import { setGlobalErrors } from 'store/globalErrors/globalErrors.actions';
import t from 'store/types';
export const mapDispatchToProps = (dispatch) => ({
globalErrorsSet: (errors) => dispatch(setGlobalErrors(errors)),
});
export default connect(null, mapDispatchToProps);

View File

@@ -1,12 +1,13 @@
export default { export default {
hello_world: 'Hello World', hello_world: 'Hello World',
'email_or_phone_number': 'Email or phone number', email_or_phone_number: 'Email or phone number',
password: 'Password', password: 'Password',
login: 'Login', login: 'Login',
invalid_email_or_phone_number: 'Invalid email or phone number.', invalid_email_or_phone_number: 'Invalid email or phone number.',
'required': 'Required', required: 'Required',
reset_password: 'Reset Password', reset_password: 'Reset Password',
the_user_has_been_suspended_from_admin: 'The user has been suspended from the administrator.', the_user_has_been_suspended_from_admin:
'The user has been suspended from the administrator.',
email_and_password_entered_did_not_match: email_and_password_entered_did_not_match:
'The email and password you entered did not match our records.', 'The email and password you entered did not match our records.',
field_name_must_be_number: 'field_name_must_be_number', field_name_must_be_number: 'field_name_must_be_number',
@@ -47,13 +48,15 @@ export default {
organization_name: 'Organization Name', organization_name: 'Organization Name',
email: 'Email', email: 'Email',
register: 'Register', register: 'Register',
password_successfully_updated: 'The Password for your account was successfully updated.', password_successfully_updated:
'The Password for your account was successfully updated.',
choose_a_new_password: 'Choose a new password', choose_a_new_password: 'Choose a new password',
you_remembered_your_password: 'You remembered your password ?', you_remembered_your_password: 'You remembered your password ?',
new_password: 'New Password', new_password: 'New Password',
submit_new_password: 'Submit new password', submit_new_password: 'Submit new password',
reset_your_password: 'Reset Your Password', reset_your_password: 'Reset Your Password',
we_ll_send_you_a_link_to_reset_your_password: 'Enter your email address and well send you a link to reset your password.', we_ll_send_you_a_link_to_reset_your_password:
'Enter your email address and well send you a link to reset your password.',
send_password_reset_link: 'Send password reset link', send_password_reset_link: 'Send password reset link',
return_to_log_in: 'Return to log in', return_to_log_in: 'Return to log in',
sub_account: 'Sub account?', sub_account: 'Sub account?',
@@ -84,7 +87,8 @@ export default {
new: 'New', new: 'New',
new_category: 'New Category', new_category: 'New Category',
invite_user: 'invite User', invite_user: 'invite User',
your_access_to_your_team: 'Your teammate will get an email that gives them access to your team.', your_access_to_your_team:
'Your teammate will get an email that gives them access to your team.',
invite: 'invite', invite: 'invite',
count: 'Count', count: 'Count',
item_type: 'Item Type', item_type: 'Item Type',
@@ -169,31 +173,51 @@ export default {
view_name: 'View Name', view_name: 'View Name',
new_conditional: 'New Conditional', new_conditional: 'New Conditional',
item: 'Item', item: 'Item',
service_has_been_successful_created: '{service} {name} has been successfully created.', service_has_been_successful_created:
service_has_been_successful_edited: '{service} {name} has been successfully edited.', '{service} {name} has been successfully created.',
service_has_been_successful_edited:
'{service} {name} has been successfully edited.',
you_are_about_permanently_delete_this_journal: `You're about to permanently delete this journal and all its transactions on accounts and attachments, and all of its data. <br /><br />If you're not sure, you can archive this journal instead.`, you_are_about_permanently_delete_this_journal: `You're about to permanently delete this journal and all its transactions on accounts and attachments, and all of its data. <br /><br />If you're not sure, you can archive this journal instead.`,
once_delete_these_accounts_you_will_not_able_restore_them: 'Once you delete these accounts, you won\'t be able to retrieve them later. Are you sure you want to delete them?', once_delete_these_accounts_you_will_not_able_restore_them:
once_delete_these_service_you_will_not_able_restore_it: 'Once you delete these {service}, you won\'t be able to retrieve them later. Are you sure you want to delete this {service}?', "Once you delete these accounts, you won't be able to retrieve them later. Are you sure you want to delete them?",
you_could_not_delete_predefined_accounts: 'You could\'t delete predefined accounts.', once_delete_these_service_you_will_not_able_restore_it:
cannot_delete_account_has_associated_transactions: 'you could\'t not delete account that has associated transactions.', "Once you delete these {service}, you won't be able to retrieve them later. Are you sure you want to delete this {service}?",
the_account_has_been_successfully_inactivated: 'The account has been successfully inactivated.', you_could_not_delete_predefined_accounts:
the_account_has_been_successfully_activated: 'The account has been successfully activated.', "You could't delete predefined accounts.",
the_account_has_been_successfully_deleted: 'The account has been successfully deleted.', cannot_delete_account_has_associated_transactions:
the_accounts_has_been_successfully_deleted: 'The accounts have been successfully deleted.', "you could't not delete account that has associated transactions.",
are_sure_to_inactive_this_account: 'Are you sure you want to inactive this account? You will be able to activate it later', the_account_has_been_successfully_inactivated:
are_sure_to_activate_this_account: 'Are you sure you want to activate this account? You will be able to inactivate it later', 'The account has been successfully inactivated.',
the_account_has_been_successfully_activated:
'The account has been successfully activated.',
the_account_has_been_successfully_deleted:
'The account has been successfully deleted.',
the_accounts_has_been_successfully_deleted:
'The accounts have been successfully deleted.',
are_sure_to_inactive_this_account:
'Are you sure you want to inactive this account? You will be able to activate it later',
are_sure_to_activate_this_account:
'Are you sure you want to activate this account? You will be able to inactivate it later',
once_delete_this_account_you_will_able_to_restore_it: `Once you delete this account, you won\'t be able to restore it later. Are you sure you want to delete this account?<br /><br />If you're not sure, you can inactivate this account instead.`, once_delete_this_account_you_will_able_to_restore_it: `Once you delete this account, you won\'t be able to restore it later. Are you sure you want to delete this account?<br /><br />If you're not sure, you can inactivate this account instead.`,
the_journal_has_been_successfully_created: 'The journal #{number} has been successfully created.', the_journal_has_been_successfully_created:
the_journal_has_been_successfully_edited: 'The journal #{number} has been successfully edited.', 'The journal #{number} has been successfully created.',
the_journal_has_been_successfully_edited:
'The journal #{number} has been successfully edited.',
credit: 'Credit', credit: 'Credit',
debit: 'Debit', debit: 'Debit',
once_delete_this_item_you_will_able_to_restore_it: `Once you delete this item, you won\'t be able to restore the item later. Are you sure you want to delete ?<br /><br />If you're not sure, you can inactivate it instead.`, once_delete_this_item_you_will_able_to_restore_it: `Once you delete this item, you won\'t be able to restore the item later. Are you sure you want to delete ?<br /><br />If you're not sure, you can inactivate it instead.`,
the_item_has_been_successfully_deleted: 'The item has been successfully deleted.', the_item_has_been_successfully_deleted:
the_item_category_has_been_successfully_created: 'The item category has been successfully created.', 'The item has been successfully deleted.',
the_item_category_has_been_successfully_edited: 'The item category has been successfully edited.', the_item_category_has_been_successfully_created:
once_delete_these_views_you_will_not_able_restore_them: 'Once you delete the custom view, you won\'t be able to restore it later. Are you sure you want to delete this view?', 'The item category has been successfully created.',
the_custom_view_has_been_successfully_deleted: 'The custom view has been successfully deleted.', the_item_category_has_been_successfully_edited:
teammate_invited_to_organization_account: 'Your teammate has been invited to the organization account.', 'The item category has been successfully edited.',
once_delete_these_views_you_will_not_able_restore_them:
"Once you delete the custom view, you won't be able to restore it later. Are you sure you want to delete this view?",
the_custom_view_has_been_successfully_deleted:
'The custom view has been successfully deleted.',
teammate_invited_to_organization_account:
'Your teammate has been invited to the organization account.',
select_account_type: 'Select account type', select_account_type: 'Select account type',
menu: 'Menu', menu: 'Menu',
graph: 'Graph', graph: 'Graph',
@@ -201,7 +225,8 @@ export default {
table: 'Table', table: 'Table',
nucleus: 'Nucleus', nucleus: 'Nucleus',
logout: 'Logout', logout: 'Logout',
the_expense_has_been_successfully_created: 'The expense has been successfully created.', the_expense_has_been_successfully_created:
'The expense has been successfully created.',
select_payment_account: 'Select Payment Account', select_payment_account: 'Select Payment Account',
select_expense_account: 'Select Expense Account', select_expense_account: 'Select Expense Account',
and: 'And', and: 'And',
@@ -247,16 +272,23 @@ export default {
auditing_system: 'Auditing System', auditing_system: 'Auditing System',
all: 'All', all: 'All',
organization: 'Organization.', organization: 'Organization.',
check_your_email_for_a_link_to_reset: 'Check your email for a link to reset your password.If it doesnt appear within a few minutes, check your spam folder.', check_your_email_for_a_link_to_reset:
we_couldn_t_find_your_account_with_that_email:'We couldn\'t find your account with that email.', 'Check your email for a link to reset your password.If it doesnt appear within a few minutes, check your spam folder.',
we_couldn_t_find_your_account_with_that_email:
"We couldn't find your account with that email.",
select_parent_account: 'Select Parent Account', select_parent_account: 'Select Parent Account',
the_exchange_rate_has_been_successfully_edited:'The exchange rate has been successfully edited', the_exchange_rate_has_been_successfully_edited:
the_exchange_rate_has_been_successfully_created:'The exchange rate has been successfully created', 'The exchange rate has been successfully edited',
the_exchange_rate_has_been_successfully_deleted: 'The exchange rate has been successfully deleted.', the_exchange_rate_has_been_successfully_created:
'The exchange rate has been successfully created',
the_exchange_rate_has_been_successfully_deleted:
'The exchange rate has been successfully deleted.',
the_user_details_has_been_updated: 'The user details has been updated', the_user_details_has_been_updated: 'The user details has been updated',
the_category_has_been_successfully_created: 'The category has been successfully created.', the_category_has_been_successfully_created:
'The category has been successfully created.',
filters_applied: 'filters applied', filters_applied: 'filters applied',
the_expense_has_been_successfully_deleted: 'The expense has been successfully deleted.', the_expense_has_been_successfully_deleted:
'The expense has been successfully deleted.',
select_item_type: 'Select Item Type', select_item_type: 'Select Item Type',
service: 'Service', service: 'Service',
inventory: 'Inventory', inventory: 'Inventory',
@@ -264,7 +296,8 @@ export default {
select_category: 'Select category', select_category: 'Select category',
select_account: 'Select Account', select_account: 'Select Account',
custom_fields: 'Custom Fields', custom_fields: 'Custom Fields',
the_currency_has_been_successfully_deleted:'The currency has been successfully deleted', the_currency_has_been_successfully_deleted:
'The currency has been successfully deleted',
organization_industry: 'Organization Industry', organization_industry: 'Organization Industry',
business_location: 'Business Location', business_location: 'Business Location',
base_currency: 'Base Currency', base_currency: 'Base Currency',
@@ -277,8 +310,10 @@ export default {
inactivate_user: 'Inactivate User', inactivate_user: 'Inactivate User',
delete_user: 'Delete User', delete_user: 'Delete User',
full_name: 'Full Name', full_name: 'Full Name',
the_user_has_been_successfully_inactivated: 'The user has been successfully inactivated.', the_user_has_been_successfully_inactivated:
the_user_has_been_successfully_deleted: 'The user has been successfully deleted.', 'The user has been successfully inactivated.',
the_user_has_been_successfully_deleted:
'The user has been successfully deleted.',
customize_report: 'Customize Report', customize_report: 'Customize Report',
print: 'Print', print: 'Print',
export: 'Export', export: 'Export',
@@ -296,8 +331,10 @@ export default {
display_report_columns: 'Display report columns', display_report_columns: 'Display report columns',
select_display_columns_by: 'Select display columns by...', select_display_columns_by: 'Select display columns by...',
credit_and_debit_not_equal: 'credit and debit not equal', credit_and_debit_not_equal: 'credit and debit not equal',
the_currency_has_been_successfully_edited:'The currency has been successfully edited', the_currency_has_been_successfully_edited:
the_currency_has_been_successfully_created:'The currency has been successfully created', 'The currency has been successfully edited',
the_currency_has_been_successfully_created:
'The currency has been successfully created',
// Name Labels // Name Labels
expense_account_id: 'Expense account', expense_account_id: 'Expense account',
@@ -324,9 +361,10 @@ export default {
date_format_: 'Date format', date_format_: 'Date format',
category_name_: 'Category name', category_name_: 'Category name',
view_name_: 'View name', view_name_: 'View name',
the_items_has_been_successfully_deleted: 'The items have been successfully deleted.', the_items_has_been_successfully_deleted:
once_delete_these_items_you_will_not_able_restore_them: 'Once you delete these items, you won\'t be able to retrieve them later. Are you sure you want to delete them?', 'The items have been successfully deleted.',
once_delete_these_items_you_will_not_able_restore_them:
"Once you delete these items, you won't be able to retrieve them later. Are you sure you want to delete them?",
ops_something_went_wrong: 'Something went wrong! Please try again.',
session_expired: 'Session Expired!',
}; };

View File

@@ -1,8 +1,15 @@
import axios from 'axios'; import axios from 'axios';
import React from 'react';
import { Intent } from '@blueprintjs/core';
import store from 'store/createStore'; import store from 'store/createStore';
import { logout } from 'store/authentication/authentication.actions';
import AppToaster from 'components/AppToaster';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { setGlobalErrors } from 'store/globalErrors/globalErrors.actions';
const http = axios.create(); const http = axios.create();
http.interceptors.request.use((request) => { http.interceptors.request.use((request) => {
const state = store.getState(); const state = store.getState();
const { token, organization } = state.authentication; const { token, organization } = state.authentication;
@@ -26,10 +33,11 @@ http.interceptors.response.use((response) => response, (error) => {
const { status } = error.response; const { status } = error.response;
if (status >= 500) { if (status >= 500) {
store.dispatch(setGlobalErrors({ something_wrong: true }));
} }
if (status === 401) { if (status === 401) {
store.dispatch(setGlobalErrors({ session_expired: true }));
store.dispatch(logout());
} }
return Promise.reject(error); return Promise.reject(error);
}); });

View File

@@ -26,6 +26,12 @@ export function login({ form }) {
}); });
} }
export const logout = () => {
return dispatch => dispatch({
type: t.LOGOUT,
});
};
export const register = ({ form }) => { export const register = ({ form }) => {
return (dispatch) => { return (dispatch) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@@ -0,0 +1,12 @@
export const setGlobalErrors = (errors) => {
return dispatch => {
dispatch({
type: 'GLOBAL_ERRORS_SET',
payload: {
errors,
},
});
}
}

View File

@@ -0,0 +1,17 @@
import { createReducer } from '@reduxjs/toolkit';
import t from 'store/types';
const initialState = {
data: {},
};
export default createReducer(initialState, {
['GLOBAL_ERRORS_SET']: (state, action) => {
const { errors } = action.payload;
state.data = {
...state.data,
...errors,
};
},
});

View File

@@ -16,6 +16,8 @@ import settings from './settings/settings.reducer';
import manualJournals from './manualJournals/manualJournals.reducers'; import manualJournals from './manualJournals/manualJournals.reducers';
import globalSearch from './search/search.reducer'; import globalSearch from './search/search.reducer';
import exchangeRates from './ExchangeRate/exchange.reducer' import exchangeRates from './ExchangeRate/exchange.reducer'
import globalErrors from './globalErrors/globalErrors.reducer';
export default combineReducers({ export default combineReducers({
authentication, authentication,
@@ -33,6 +35,6 @@ export default combineReducers({
itemCategories, itemCategories,
settings, settings,
globalSearch, globalSearch,
exchangeRates exchangeRates,
globalErrors,
}); });