mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 12:50:38 +00:00
feat: application booting.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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'}
|
||||
|
||||
28
client/src/components/Dashboard/AuthenticatedUser.js
Normal file
28
client/src/components/Dashboard/AuthenticatedUser.js
Normal 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);
|
||||
76
client/src/components/Dashboard/DashboardBoot.js
Normal file
76
client/src/components/Dashboard/DashboardBoot.js
Normal 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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
15
client/src/components/Dashboard/SplashScreen.js
Normal file
15
client/src/components/Dashboard/SplashScreen.js
Normal 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);
|
||||
@@ -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();
|
||||
|
||||
4
client/src/components/Dashboard/index.js
Normal file
4
client/src/components/Dashboard/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
|
||||
export * from './SplashScreen';
|
||||
export * from './DashboardBoot';
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -74,6 +74,7 @@ export * from './Drawer/DrawerMainTabs';
|
||||
export * from './TotalLines/index'
|
||||
export * from './Alert';
|
||||
export * from './Subscriptions';
|
||||
export * from './Dashboard';
|
||||
|
||||
const Hint = FieldHint;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user