feat: application booting.

This commit is contained in:
a.bouhuolia
2021-09-08 16:27:16 +02:00
parent 7b3d310eab
commit 361aab89e6
93 changed files with 961 additions and 723 deletions

View File

@@ -13,29 +13,14 @@ import PrivateRoute from 'components/Guards/PrivateRoute';
import GlobalErrors from 'containers/GlobalErrors/GlobalErrors';
import DashboardPrivatePages from 'components/Dashboard/PrivatePages';
import Authentication from 'components/Authentication';
// Query client config.
const queryConfig = {
defaultOptions: {
queries: {
refetchOnWindowFocus: true,
staleTime: 30000,
},
},
};
// Global fetch query.
function GlobalFetchQuery({
children
}) {
window.localStorage.setItem('lang', 'ar-ly');
return children
}
import { SplashScreen } from '../components';
import { queryConfig } from '../hooks/query/base'
/**
* Core application.
*/
function App({ locale }) {
export default function App() {
// Browser history.
const history = createBrowserHistory();
// Query client.
@@ -43,30 +28,24 @@ function App({ locale }) {
return (
<QueryClientProvider client={queryClient}>
<GlobalFetchQuery>
<AppIntlLoader>
<div className="App">
<Router history={history}>
<Switch>
<Route path={'/auth'} component={Authentication} />
<Route path={'/'}>
<PrivateRoute component={DashboardPrivatePages} />
</Route>
</Switch>
</Router>
<SplashScreen />
<GlobalErrors />
</div>
</AppIntlLoader>
</GlobalFetchQuery>
<AppIntlLoader>
<div className="App">
<Router history={history}>
<Switch>
<Route path={'/auth'} component={Authentication} />
<Route path={'/'}>
<PrivateRoute component={DashboardPrivatePages} />
</Route>
</Switch>
</Router>
<GlobalErrors />
</div>
</AppIntlLoader>
<ReactQueryDevtools initialIsOpen />
</QueryClientProvider>
);
}
App.defaultProps = {
locale: 'en',
};
export default App;
}

View File

@@ -4,12 +4,14 @@ import { setLocale } from 'yup';
import intl from 'react-intl-universal';
import { find } from 'lodash';
import rtlDetect from 'rtl-detect';
import * as R from 'ramda';
import { AppIntlProvider } from './AppIntlProvider';
import DashboardLoadingIndicator from 'components/Dashboard/DashboardLoadingIndicator';
import withDashboardActions from '../containers/Dashboard/withDashboardActions';
import withDashboard from '../containers/Dashboard/withDashboard';
const SUPPORTED_LOCALES = [
{ name: 'English', value: 'en' },
{ name: 'العربية', value: 'ar-ly' },
{ name: 'العربية', value: 'ar' },
];
/**
@@ -18,7 +20,7 @@ const SUPPORTED_LOCALES = [
function getCurrentLocal() {
let currentLocale = intl.determineLocale({
urlLocaleKey: 'lang',
cookieLocaleKey: 'lang',
cookieLocaleKey: 'locale',
localStorageLocaleKey: 'lang',
});
if (!find(SUPPORTED_LOCALES, { value: currentLocale })) {
@@ -57,10 +59,14 @@ function useDocumentDirectionModifier(locale, isRTL) {
/**
* Application Intl loader.
*/
export default function AppIntlLoader({ children }) {
const [isLoading, setIsLoading] = React.useState(true);
function AppIntlLoader({ appIntlIsLoading, setAppIntlIsLoading, children }) {
const [isLocalsLoading, setIsLocalsLoading] = React.useState(true);
const [isYupLoading, setIsYupLoading] = 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
@@ -79,23 +85,33 @@ export default function AppIntlLoader({ children }) {
})
.then(() => {
moment.locale(currentLocale);
setIsLoading(false);
setIsLocalsLoading(false);
});
}, [currentLocale, setIsLoading]);
}, [currentLocale, setIsLocalsLoading]);
React.useEffect(() => {
loadYupLocales(currentLocale)
.then(({ locale }) => {
setLocale(locale);
setIsYupLoading(false);
})
.then(() => {});
}, [currentLocale]);
React.useEffect(() => {
if (!isLocalsLoading && !isYupLoading) {
setAppIntlIsLoading(false);
}
});
return (
<AppIntlProvider currentLocale={currentLocale} isRTL={isRTL}>
<DashboardLoadingIndicator isLoading={isLoading}>
{children}
</DashboardLoadingIndicator>
{appIntlIsLoading ? null : children}
</AppIntlProvider>
);
}
export default R.compose(
withDashboardActions,
withDashboard(({ appIntlIsLoading }) => ({ appIntlIsLoading })),
)(AppIntlLoader);

View File

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

View File

@@ -0,0 +1,28 @@
import React from 'react';
import { useUser } from 'hooks/query';
import withAuthentication from '../../containers/Authentication/withAuthentication';
const AuthenticatedUserContext = React.createContext();
function AuthenticatedUserComponent({ authenticatedUserId, children }) {
const { data: user, ...restProps } = useUser(authenticatedUserId);
return (
<AuthenticatedUserContext.Provider
value={{
user,
...restProps,
}}
children={children}
/>
);
}
export const AuthenticatedUser = withAuthentication(
({ authenticatedUserId }) => ({
authenticatedUserId,
}),
)(AuthenticatedUserComponent);
export const useAuthenticatedUser = () =>
React.useContext(AuthenticatedUserContext);

View File

@@ -0,0 +1,76 @@
import React from 'react';
import * as R from 'ramda';
import { useUser, useCurrentOrganization } from 'hooks/query';
import withAuthentication from '../../containers/Authentication/withAuthentication';
import withDashboardActions from '../../containers/Dashboard/withDashboardActions';
import { setCookie, getCookie } from '../../utils';
/**
* Dashboard async booting.
*/
function DashboardBootJSX({ setAppIsLoading, authenticatedUserId }) {
// Fetches the current user's organization.
const { isSuccess: isCurrentOrganizationSuccess, data: organization } =
useCurrentOrganization();
// Authenticated user.
const { isSuccess: isAuthUserSuccess, data: authUser } =
useUser(authenticatedUserId);
// Initial locale cookie value.
const localeCookie = getCookie('locale');
// Is the dashboard booted.
const isBooted = React.useRef(false);
// Syns the organization language with locale cookie.
React.useEffect(() => {
if (organization?.metadata?.language) {
setCookie('locale', organization.metadata.language);
}
}, [organization]);
React.useEffect(() => {
// Can't continue if the organization metadata is not loaded yet.
if (!organization?.metadata?.language) {
return;
}
// Can't continue if the organization is already booted.
if (isBooted.current) {
return;
}
// Reboot the application in case the initial locale not equal
// the current organization language.
if (localeCookie !== organization.metadata.language) {
window.location.reload();
}
}, [localeCookie, organization]);
React.useEffect(() => {
// Once the all requests complete change the app loading state.
if (
isAuthUserSuccess &&
isCurrentOrganizationSuccess &&
localeCookie === organization?.metadata?.language
) {
setAppIsLoading(false);
isBooted.current = true;
}
}, [
isAuthUserSuccess,
isCurrentOrganizationSuccess,
organization,
setAppIsLoading,
localeCookie,
]);
return null;
}
export const DashboardBoot = R.compose(
withAuthentication(({ authenticatedUserId }) => ({
authenticatedUserId,
})),
withDashboardActions,
)(DashboardBootJSX);

View File

@@ -1,13 +1,8 @@
import React from 'react';
import DashboardLoadingIndicator from './DashboardLoadingIndicator';
/**
* Dashboard provider.
*/
export default function DashboardProvider({ children }) {
return (
<DashboardLoadingIndicator isLoading={false}>
{ children }
</DashboardLoadingIndicator>
)
}
return children;
}

View File

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

View File

@@ -1,17 +1,9 @@
import React from 'react';
import DashboardLoadingIndicator from 'components/Dashboard/DashboardLoadingIndicator';
import { useCurrentOrganization } from '../../hooks/query/organization';
import { AuthenticatedUser } from './AuthenticatedUser';
/**
* Private pages provider.
*/
export function PrivatePagesProvider({ children }) {
// Fetches the current user's organization.
const { isLoading } = useCurrentOrganization();
return (
<DashboardLoadingIndicator isLoading={isLoading}>
{children}
</DashboardLoadingIndicator>
)
}
return <AuthenticatedUser>{children}</AuthenticatedUser>;
}

View File

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

View File

@@ -11,11 +11,12 @@ import {
import { If, FormattedMessage as T } from 'components';
import { firstLettersArgs } from 'utils';
import { useAuthActions, useAuthUser } from 'hooks/state';
import { useAuthActions } from 'hooks/state';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
import withSubscriptions from '../../containers/Subscriptions/withSubscriptions';
import { useAuthenticatedUser } from './AuthenticatedUser';
function DashboardTopbarUser({
openDialog,
@@ -25,7 +26,9 @@ function DashboardTopbarUser({
}) {
const history = useHistory();
const { setLogout } = useAuthActions();
const user = useAuthUser();
// Retrieve authenticated user information.
const { user } = useAuthenticatedUser();
const onClickLogout = () => {
setLogout();

View File

@@ -0,0 +1,4 @@
export * from './SplashScreen';
export * from './DashboardBoot';

View File

@@ -1,26 +1,17 @@
import React from 'react';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import 'style/components/DataTable/DataTableEmptyStatus.scss';
import Style from 'style/components/DataTable/DataTableEmptyStatus.module.scss';
/**
* Datatable empty status.
*/
export default function EmptyStatuts({ title, description, action, children }) {
export default function EmptyStatus({ title, description, action, children }) {
return (
<div className={classNames(CLASSES.DATATABLE_EMPTY_STATUS)}>
<h1 className={classNames(CLASSES.DATATABLE_EMPTY_STATUS_TITLE)}>
{title}
</h1>
<div className={classNames(CLASSES.DATATABLE_EMPTY_STATUS_DESC)}>
{description}
</div>
<div className={classNames(CLASSES.DATATABLE_EMPTY_STATUS_ACTIONS)}>
{action}
</div>
<div className={classNames(Style.root)}>
<h1 className={classNames(Style.root_title)}>{title}</h1>
<div className={classNames(Style.root_desc)}>{description}</div>
<div className={classNames(Style.root_actions)}>{action}</div>
{children}
</div>
);

View File

@@ -1,9 +1,9 @@
import React from 'react';
import { Button, Popover, Menu, Position } from '@blueprintjs/core';
import Icon from 'components/Icon';
import { useAuthUser } from 'hooks/state';
import { compose, firstLettersArgs } from 'utils';
import withCurrentOrganization from '../../containers/Organization/withCurrentOrganization';
import { useAuthenticatedUser } from '../Dashboard/AuthenticatedUser';
// Popover modifiers.
const POPOVER_MODIFIERS = {
@@ -17,7 +17,8 @@ function SidebarHead({
// #withCurrentOrganization
organization,
}) {
const user = useAuthUser();
// Retrieve authenticated user information.
const { user } = useAuthenticatedUser();
return (
<div className="sidebar__head">

View File

@@ -74,6 +74,7 @@ export * from './Drawer/DrawerMainTabs';
export * from './TotalLines/index'
export * from './Alert';
export * from './Subscriptions';
export * from './Dashboard';
const Hint = FieldHint;