BIG-117: fix dashboard redirect all routes to homepage once refresh the page.

This commit is contained in:
a.bouhuolia
2021-09-23 09:44:30 +02:00
parent 8864e89674
commit 364859a793
13 changed files with 205 additions and 103 deletions

View File

@@ -7,9 +7,10 @@ import rtlDetect from 'rtl-detect';
import * as R from 'ramda'; import * as R from 'ramda';
import { AppIntlProvider } from './AppIntlProvider'; import { AppIntlProvider } from './AppIntlProvider';
import { useSplashLoading } from '../hooks/state';
import { useWatch } from '../hooks';
import withDashboardActions from '../containers/Dashboard/withDashboardActions'; import withDashboardActions from '../containers/Dashboard/withDashboardActions';
import withDashboard from '../containers/Dashboard/withDashboard';
const SUPPORTED_LOCALES = [ const SUPPORTED_LOCALES = [
{ name: 'English', value: 'en' }, { name: 'English', value: 'en' },
@@ -63,20 +64,13 @@ function transformMomentLocale(currentLocale) {
} }
/** /**
* Application Intl loader. * Loads application locales of the given current locale.
* @param {string} currentLocale
* @returns {{ isLoading: boolean }}
*/ */
function AppIntlLoader({ appIntlIsLoading, setAppIntlIsLoading, children }) { function useAppLoadLocales(currentLocale) {
const [isLocalsLoading, setIsLocalsLoading] = React.useState(true); const [startLoading, stopLoading] = useSplashLoading();
const [isYupLoading, setIsYupLoading] = React.useState(true); const [isLoading, setIsLoading] = React.useState(true);
// Retrieve the current locale.
const currentLocale = getCurrentLocal();
// Detarmines the document direction based on the given locale.
const isRTL = rtlDetect.isRtlLang(currentLocale);
// Modifies the html document direction
useDocumentDirectionModifier(currentLocale, isRTL);
React.useEffect(() => { React.useEffect(() => {
// Lodas the locales data file. // Lodas the locales data file.
@@ -91,33 +85,72 @@ function AppIntlLoader({ appIntlIsLoading, setAppIntlIsLoading, children }) {
}) })
.then(() => { .then(() => {
moment.locale(transformMomentLocale(currentLocale)); moment.locale(transformMomentLocale(currentLocale));
setIsLocalsLoading(false); setIsLoading(false);
}); });
}, [currentLocale, setIsLocalsLoading]); }, [currentLocale, stopLoading]);
// Watches the value to start/stop splash screen.
useWatch(isLoading, (value) => (value ? startLoading() : stopLoading()), {
immediate: true,
});
return { isLoading };
}
/**
* Loads application yup locales based on the given current locale.
* @param {string} currentLocale
* @returns {{ isLoading: boolean }}
*/
function useAppYupLoadLocales(currentLocale) {
const [startLoading, stopLoading] = useSplashLoading();
const [isLoading, setIsLoading] = React.useState(true);
React.useEffect(() => { React.useEffect(() => {
loadYupLocales(currentLocale) loadYupLocales(currentLocale)
.then(({ locale }) => { .then(({ locale }) => {
setLocale(locale); setLocale(locale);
setIsYupLoading(false); setIsLoading(false);
}) })
.then(() => {}); .then(() => {});
}, [currentLocale]); }, [currentLocale, stopLoading]);
React.useEffect(() => { // Watches the valiue to start/stop splash screen.
if (!isLocalsLoading && !isYupLoading) { useWatch(isLoading, (value) => (value ? startLoading() : stopLoading()), {
setAppIntlIsLoading(false); immediate: true,
}
}); });
return { isLoading };
}
/**
* Application Intl loader.
*/
function AppIntlLoader({ children }) {
// Retrieve the current locale.
const currentLocale = getCurrentLocal();
// Detarmines the document direction based on the given locale.
const isRTL = rtlDetect.isRtlLang(currentLocale);
// Modifies the html document direction
useDocumentDirectionModifier(currentLocale, isRTL);
// Loads yup localization of the given locale.
const { isLoading: isAppYupLocalesLoading } =
useAppYupLoadLocales(currentLocale);
// Loads application locales of the given locale.
const { isLoading: isAppLocalesLoading } = useAppLoadLocales(currentLocale);
// Detarmines whether the app locales loading.
const isLoading = isAppYupLocalesLoading && isAppLocalesLoading;
return ( return (
<AppIntlProvider currentLocale={currentLocale} isRTL={isRTL}> <AppIntlProvider currentLocale={currentLocale} isRTL={isRTL}>
{appIntlIsLoading ? null : children} {isLoading ? null : children}
</AppIntlProvider> </AppIntlProvider>
); );
} }
export default R.compose( export default R.compose(withDashboardActions)(AppIntlLoader);
withDashboardActions,
withDashboard(({ appIntlIsLoading }) => ({ appIntlIsLoading })),
)(AppIntlLoader);

View File

@@ -6,7 +6,6 @@ import authenticationRoutes from 'routes/authentication';
import { FormattedMessage as T } from 'components'; import { FormattedMessage as T } from 'components';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import { useIsAuthenticated } from 'hooks/state'; import { useIsAuthenticated } from 'hooks/state';
import {AuthenticationBoot} from '../containers/Authentication/AuthenticationBoot';
import 'style/pages/Authentication/Auth.scss'; import 'style/pages/Authentication/Auth.scss';
function PageFade(props) { function PageFade(props) {
@@ -26,7 +25,6 @@ export default function AuthenticationWrapper({ ...rest }) {
) : ( ) : (
<BodyClassName className={'authentication'}> <BodyClassName className={'authentication'}>
<div class="authentication-page"> <div class="authentication-page">
<AuthenticationBoot />
<a <a
href={'http://bigcapital.ly'} href={'http://bigcapital.ly'}
className={'authentication-page__goto-bigcapital'} className={'authentication-page__goto-bigcapital'}

View File

@@ -1,23 +1,30 @@
import React from 'react'; import React from 'react';
import * as R from 'ramda'; import * as R from 'ramda';
import { useUser, useCurrentOrganization } from 'hooks/query'; import { useUser, useCurrentOrganization } from '../../hooks/query';
import { useSplashLoading } from '../../hooks/state';
import { useWatch, useWhen } from '../../hooks';
import withAuthentication from '../../containers/Authentication/withAuthentication'; import withAuthentication from '../../containers/Authentication/withAuthentication';
import withDashboardActions from '../../containers/Dashboard/withDashboardActions';
import { setCookie, getCookie } from '../../utils'; import { setCookie, getCookie } from '../../utils';
/** /**
* Dashboard async booting. * Dashboard async booting.
*/ */
function DashboardBootJSX({ setAppIsLoading, authenticatedUserId }) { function DashboardBootJSX({ authenticatedUserId }) {
// Fetches the current user's organization. // Fetches the current user's organization.
const { isSuccess: isCurrentOrganizationSuccess, data: organization } = const {
useCurrentOrganization(); isSuccess: isCurrentOrganizationSuccess,
isLoading: isOrgLoading,
data: organization,
} = useCurrentOrganization();
// Authenticated user. // Authenticated user.
const { isSuccess: isAuthUserSuccess, data: authUser } = const {
useUser(authenticatedUserId); isSuccess: isAuthUserSuccess,
isLoading: isAuthUserLoading,
} = useUser(authenticatedUserId);
// Initial locale cookie value. // Initial locale cookie value.
const localeCookie = getCookie('locale'); const localeCookie = getCookie('locale');
@@ -48,23 +55,39 @@ function DashboardBootJSX({ setAppIsLoading, authenticatedUserId }) {
} }
}, [localeCookie, organization]); }, [localeCookie, organization]);
React.useEffect(() => { const [startLoading, stopLoading] = useSplashLoading();
// Once the all requests complete change the app loading state.
if ( // Splash loading when organization request loading and
isAuthUserSuccess && // applicaiton still not booted.
useWatch(isOrgLoading, (value) => {
value && !isBooted.current && startLoading();
});
// Splash loading when request authenticated user loading and
// application still not booted yet.
useWatch(isAuthUserLoading, (value) => {
value && !isBooted.current && startLoading();
});
// Stop splash loading once organization request success.
useWatch(isCurrentOrganizationSuccess, (value) => {
value && stopLoading();
});
// Stop splash loading once authenticated user request success.
useWatch(isAuthUserSuccess, (value) => {
value && stopLoading();
});
// Once the all requests complete change the app loading state.
useWhen(
isAuthUserSuccess &&
isCurrentOrganizationSuccess && isCurrentOrganizationSuccess &&
localeCookie === organization?.metadata?.language localeCookie === organization?.metadata?.language,
) { () => {
setAppIsLoading(false);
isBooted.current = true; isBooted.current = true;
} },
}, [ );
isAuthUserSuccess,
isCurrentOrganizationSuccess,
organization,
setAppIsLoading,
localeCookie,
]);
return null; return null;
} }
@@ -72,5 +95,4 @@ export const DashboardBoot = R.compose(
withAuthentication(({ authenticatedUserId }) => ({ withAuthentication(({ authenticatedUserId }) => ({
authenticatedUserId, authenticatedUserId,
})), })),
withDashboardActions,
)(DashboardBootJSX); )(DashboardBootJSX);

View File

@@ -4,10 +4,9 @@ import { Switch, Route } from 'react-router';
import Dashboard from 'components/Dashboard/Dashboard'; import Dashboard from 'components/Dashboard/Dashboard';
import SetupWizardPage from 'containers/Setup/WizardSetupPage'; import SetupWizardPage from 'containers/Setup/WizardSetupPage';
import EnsureOrganizationIsReady from 'components/Guards/EnsureOrganizationIsReady'; import EnsureOrganizationIsReady from '../../components/Guards/EnsureOrganizationIsReady';
import EnsureOrganizationIsNotReady from 'components/Guards/EnsureOrganizationIsNotReady'; import EnsureOrganizationIsNotReady from '../../components/Guards/EnsureOrganizationIsNotReady';
import { PrivatePagesProvider } from './PrivatePagesProvider'; import { PrivatePagesProvider } from './PrivatePagesProvider';
import { DashboardBoot } from '../../components';
import 'style/pages/Dashboard/Dashboard.scss'; import 'style/pages/Dashboard/Dashboard.scss';
@@ -17,8 +16,6 @@ import 'style/pages/Dashboard/Dashboard.scss';
export default function DashboardPrivatePages() { export default function DashboardPrivatePages() {
return ( return (
<PrivatePagesProvider> <PrivatePagesProvider>
<DashboardBoot />
<Switch> <Switch>
<Route path={'/setup'}> <Route path={'/setup'}>
<EnsureOrganizationIsNotReady> <EnsureOrganizationIsNotReady>

View File

@@ -1,9 +1,31 @@
import React from 'react'; import React from 'react';
import * as R from 'ramda';
import { AuthenticatedUser } from './AuthenticatedUser'; import { AuthenticatedUser } from './AuthenticatedUser';
import { DashboardBoot } from '../../components';
import withDashboard from '../../containers/Dashboard/withDashboard';
/** /**
* Private pages provider. * Private pages provider.
*/ */
export function PrivatePagesProvider({ children }) { function PrivatePagesProviderComponent({
return <AuthenticatedUser>{children}</AuthenticatedUser>; splashScreenCompleted,
// #ownProps
children,
}) {
return (
<AuthenticatedUser>
<DashboardBoot />
{splashScreenCompleted ? children : null}
</AuthenticatedUser>
);
} }
export const PrivatePagesProvider = R.compose(
withDashboard(({ splashScreenCompleted }) => ({
splashScreenCompleted,
})),
)(PrivatePagesProviderComponent);

View File

@@ -3,13 +3,12 @@ import * as R from 'ramda';
import BigcapitalLoading from './BigcapitalLoading'; import BigcapitalLoading from './BigcapitalLoading';
import withDashboard from '../../containers/Dashboard/withDashboard'; import withDashboard from '../../containers/Dashboard/withDashboard';
function SplashScreenComponent({ appIsLoading, appIntlIsLoading }) { function SplashScreenComponent({ splashScreenLoading }) {
return appIsLoading || appIntlIsLoading ? <BigcapitalLoading /> : null; return splashScreenLoading ? <BigcapitalLoading /> : null;
} }
export const SplashScreen = R.compose( export const SplashScreen = R.compose(
withDashboard(({ appIsLoading, appIntlIsLoading }) => ({ withDashboard(({ splashScreenLoading }) => ({
appIsLoading, splashScreenLoading,
appIntlIsLoading,
})), })),
)(SplashScreenComponent); )(SplashScreenComponent);

View File

@@ -6,7 +6,6 @@ import { compose } from 'utils';
import withAuthentication from 'containers/Authentication/withAuthentication'; import withAuthentication from 'containers/Authentication/withAuthentication';
import withOrganization from 'containers/Organization/withOrganization'; import withOrganization from 'containers/Organization/withOrganization';
function EnsureOrganizationIsReady({ function EnsureOrganizationIsReady({
// #ownProps // #ownProps
children, children,
@@ -15,10 +14,10 @@ function EnsureOrganizationIsReady({
// #withOrganizationByOrgId // #withOrganizationByOrgId
isOrganizationReady, isOrganizationReady,
}) { }) {
return (isOrganizationReady) ? children : ( return isOrganizationReady ? (
<Redirect children
to={{ pathname: redirectTo }} ) : (
/> <Redirect to={{ pathname: redirectTo }} />
); );
} }

View File

@@ -1,15 +0,0 @@
import React from 'react';
import * as R from 'ramda';
import withDashboardActions from '../../containers/Dashboard/withDashboardActions';
function AuthenticationBootJSX({ setAppIsLoading }) {
React.useEffect(() => {
setAppIsLoading(false);
}, [setAppIsLoading]);
return null;
}
export const AuthenticationBoot = R.compose(withDashboardActions)(
AuthenticationBootJSX,
);

View File

@@ -11,8 +11,8 @@ export default (mapState) => {
sidebarExpended: state.dashboard.sidebarExpended, sidebarExpended: state.dashboard.sidebarExpended,
preferencesPageTitle: state.dashboard.preferencesPageTitle, preferencesPageTitle: state.dashboard.preferencesPageTitle,
dashboardBackLink: state.dashboard.backLink, dashboardBackLink: state.dashboard.backLink,
appIsLoading: state.dashboard.appIsLoading, splashScreenLoading: state.dashboard.splashScreenLoading > 0,
appIntlIsLoading: state.dashboard.appIntlIsLoading splashScreenCompleted: state.dashboard.splashScreenLoading === 0,
}; };
return mapState ? mapState(mapped, state, props) : mapped; return mapState ? mapState(mapped, state, props) : mapped;
}; };

View File

@@ -2,9 +2,8 @@ import { connect } from 'react-redux';
import t from 'store/types'; import t from 'store/types';
import { import {
toggleExpendSidebar, toggleExpendSidebar,
appIsLoading,
appIntlIsLoading
} from 'store/dashboard/dashboard.actions'; } from 'store/dashboard/dashboard.actions';
import { splashStartLoading, splashStopLoading } from '../../store/dashboard/dashboard.actions';
const mapActionsToProps = (dispatch) => ({ const mapActionsToProps = (dispatch) => ({
changePageTitle: (pageTitle) => changePageTitle: (pageTitle) =>
@@ -57,8 +56,10 @@ const mapActionsToProps = (dispatch) => ({
type: t.SET_DASHBOARD_BACK_LINK, type: t.SET_DASHBOARD_BACK_LINK,
payload: { backLink }, payload: { backLink },
}), }),
setAppIsLoading: (isLoading) => dispatch(appIsLoading(isLoading)),
setAppIntlIsLoading: (isLoading) => dispatch(appIntlIsLoading(isLoading)), // Splash screen start/stop loading.
splashStartLoading: () => splashStartLoading(),
splashStopLoading: () => splashStopLoading(),
}); });
export default connect(null, mapActionsToProps); export default connect(null, mapActionsToProps);

View File

@@ -1,3 +1,4 @@
import { isEqual } from 'lodash';
import React, { useRef, useEffect, useMemo } from 'react'; import React, { useRef, useEffect, useMemo } from 'react';
import useAsync from './async'; import useAsync from './async';
import useAutofocus from './useAutofocus'; import useAutofocus from './useAutofocus';
@@ -97,7 +98,6 @@ export function useLocalStorage(key, initialValue) {
return [storedValue, setValue]; return [storedValue, setValue];
} }
export function useMemorizedColumnsWidths(tableName) { export function useMemorizedColumnsWidths(tableName) {
const [get, save] = useLocalStorage(`${tableName}.columns_widths`, {}); const [get, save] = useLocalStorage(`${tableName}.columns_widths`, {});
@@ -106,3 +106,42 @@ export function useMemorizedColumnsWidths(tableName) {
}; };
return [get, save, handleColumnResizing]; return [get, save, handleColumnResizing];
} }
// Hook
function usePrevious(value) {
// The ref object is a generic container whose current property is mutable ...
// ... and can hold any value, similar to an instance property on a class
const ref = useRef();
// Store current value in ref
useEffect(() => {
ref.current = value;
}, [value]); // Only re-run if value changes
// Return previous value (happens before update in useEffect above)
return ref.current;
}
export function useWhen(condition, callback) {
React.useEffect(() => {
if (condition) {
callback();
}
}, [condition, callback]);
}
export function useWhenNot(condition, callback) {
return useWhen(!condition, callback);
}
export function useWatch(state, callback, props) {
const config = { immediate: false, ...props };
const previosuState = usePrevious(state);
const flag = React.useRef(false);
React.useEffect(() => {
if (!isEqual(previosuState, state) || (config.immediate && !flag.current)) {
flag.current = true;
callback(state);
}
}, [previosuState, state, config.immediate, callback]);
}

View File

@@ -3,11 +3,13 @@ import { useCallback } from 'react';
import { isAuthenticated } from 'store/authentication/authentication.reducer'; import { isAuthenticated } from 'store/authentication/authentication.reducer';
import { import {
setLogin, setLogin,
setStoreReset,
} from 'store/authentication/authentication.actions'; } from 'store/authentication/authentication.actions';
import { useQueryClient } from 'react-query'; import { useQueryClient } from 'react-query';
import { removeCookie } from '../../utils'; import { removeCookie } from '../../utils';
/**
* Removes the authentication cookies.
*/
function removeAuthenticationCookies() { function removeAuthenticationCookies() {
removeCookie('token'); removeCookie('token');
removeCookie('organization_id'); removeCookie('organization_id');

View File

@@ -1,6 +1,10 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { dashboardPageTitle } from 'store/dashboard/dashboard.actions'; import {
splashStopLoading,
splashStartLoading,
dashboardPageTitle,
} from '../../store/dashboard/dashboard.actions';
export const useDispatchAction = (action) => { export const useDispatchAction = (action) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@@ -17,11 +21,12 @@ export const useDashboardPageTitle = () => {
return useDispatchAction(dashboardPageTitle); return useDispatchAction(dashboardPageTitle);
}; };
export const useSetAccountsTableQuery = () => { /**
* Splash loading screen actions.
*/
export const useSplashLoading = () => {
return [
useDispatchAction(splashStartLoading),
useDispatchAction(splashStopLoading),
];
}; };
export const useAccountsTableQuery = () => {
}