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

@@ -14,6 +14,7 @@ const CLASSES = {
DASHBOARD_CONTENT: 'dashboard-content',
DASHBOARD_CONTENT_PREFERENCES: 'dashboard-content--preferences',
DASHBOARD_CONTENT_PANE: 'Pane2',
DASHBOARD_CENTERED_EMPTY_STATUS: 'dashboard__centered-empty-status',
PAGE_FORM: 'page-form',
PAGE_FORM_HEADER: 'page-form__header',

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;

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import { DataTable } from 'components';
import { DataTable, DashboardContentTable } from 'components';
import ManualJournalsEmptyStatus from './ManualJournalsEmptyStatus';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
@@ -91,33 +91,35 @@ function ManualJournalsDataTable({
}
return (
<DataTable
noInitialFetch={true}
columns={columns}
data={manualJournals}
initialState={manualJournalsTableState}
manualSortBy={true}
selectionColumn={true}
expandable={true}
sticky={true}
loading={isManualJournalsLoading}
headerLoading={isManualJournalsLoading}
progressBarLoading={isManualJournalsFetching}
pagesCount={pagination.pagesCount}
pagination={true}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
onFetchData={handleFetchData}
payload={{
onDelete: handleDeleteJournal,
onPublish: handlePublishJournal,
onEdit: handleEditJournal,
onViewDetails: handleViewDetailJournal,
}}
/>
<DashboardContentTable>
<DataTable
noInitialFetch={true}
columns={columns}
data={manualJournals}
initialState={manualJournalsTableState}
manualSortBy={true}
selectionColumn={true}
expandable={true}
sticky={true}
loading={isManualJournalsLoading}
headerLoading={isManualJournalsLoading}
progressBarLoading={isManualJournalsFetching}
pagesCount={pagination.pagesCount}
pagination={true}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
onFetchData={handleFetchData}
payload={{
onDelete: handleDeleteJournal,
onPublish: handlePublishJournal,
onEdit: handleEditJournal,
onViewDetails: handleViewDetailJournal,
}}
/>
</DashboardContentTable>
);
}

View File

@@ -30,20 +30,19 @@ function ManualJournalsTable({
<DashboardPageContent>
<ManualJournalsViewTabs />
<DashboardContentTable>
<ManualJournalsDataTable />
</DashboardContentTable>
<ManualJournalsAlerts />
<ManualJournalsDataTable />
</DashboardPageContent>
<ManualJournalsAlerts />
</ManualJournalsListProvider>
);
}
export default compose(
withManualJournals(({ manualJournalsTableState, manualJournalTableStateChanged }) => ({
journalsTableState: manualJournalsTableState,
journalsTableStateChanged: manualJournalTableStateChanged,
})),
withManualJournals(
({ manualJournalsTableState, manualJournalTableStateChanged }) => ({
journalsTableState: manualJournalsTableState,
journalsTableStateChanged: manualJournalTableStateChanged,
}),
),
)(ManualJournalsTable);

View File

@@ -0,0 +1,15 @@
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

@@ -20,18 +20,20 @@ export default function Login() {
loginMutate({
crediential: values.crediential,
password: values.password,
})
.then(() => {
setSubmitting(false);
})
.catch(({ response: { data: { errors } } }) => {
}).catch(
({
response: {
data: { errors },
},
}) => {
const toastBuilders = transformLoginErrorsToToasts(errors);
toastBuilders.forEach((builder) => {
Toaster.show(builder);
});
setSubmitting(false);
});
},
);
};
return (
@@ -66,4 +68,4 @@ export default function Login() {
</div>
</AuthInsider>
);
}
}

View File

@@ -1,15 +1,16 @@
import React, { useMemo } from 'react';
import React, { useMemo } from 'react';
import { Formik } from 'formik';
import { Link, useHistory } from 'react-router-dom';
import {
Intent,
} from '@blueprintjs/core';
import { Link } from 'react-router-dom';
import { Intent } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import { FormattedMessage as T } from 'components';
import AppToaster from 'components/AppToaster';
import AuthInsider from 'containers/Authentication/AuthInsider';
import { useAuthLogin, useAuthRegister } from '../../hooks/query/authentication';
import {
useAuthLogin,
useAuthRegister,
} from '../../hooks/query/authentication';
import RegisterForm from './RegisterForm';
import { RegisterSchema, transformRegisterErrorsToForm } from './utils';
@@ -18,11 +19,9 @@ import { RegisterSchema, transformRegisterErrorsToForm } from './utils';
* Register form.
*/
export default function RegisterUserForm() {
const history = useHistory();
const { mutateAsync: authLoginMutate } = useAuthLogin();
const { mutateAsync: authRegisterMutate } = useAuthRegister();
const { mutateAsync: authLoginMutate } = useAuthLogin();
const { mutateAsync: authRegisterMutate } = useAuthRegister();
const initialValues = useMemo(
() => ({
first_name: '',
@@ -41,26 +40,33 @@ export default function RegisterUserForm() {
authLoginMutate({
crediential: values.email,
password: values.password,
})
.then(() => {
history.push('/register/subscription');
setSubmitting(false);
})
.catch(({ response: { data: { errors } } }) => {
}).catch(
({
response: {
data: { errors },
},
}) => {
AppToaster.show({
message: intl.get('something_wentwrong'),
intent: Intent.SUCCESS,
});
});
},
);
})
.catch(({ response: { data: { errors } } }) => {
const formErrors = transformRegisterErrorsToForm(errors);
.catch(
({
response: {
data: { errors },
},
}) => {
const formErrors = transformRegisterErrorsToForm(errors);
setErrors(formErrors);
setSubmitting(false);
});
setErrors(formErrors);
setSubmitting(false);
},
);
};
return (
<AuthInsider>
<div className={'register-form'}>
@@ -83,4 +89,4 @@ export default function RegisterUserForm() {
</div>
</AuthInsider>
);
}
}

View File

@@ -1,14 +1,15 @@
import { isAuthenticated } from 'store/authentication/authentication.reducer'
import { isAuthenticated } from 'store/authentication/authentication.reducer';
import { connect } from 'react-redux';
export default (mapState) => {
const mapStateToProps = (state, props) => {
const mapped = {
isAuthorized: isAuthenticated(state),
user: state.authentication.user,
authenticatedUserId: state.authentication.userId,
currentOrganizationId: state.authentication?.organizationId,
currentTenantId: state.authentication?.tenantId,
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};
};

View File

@@ -47,10 +47,7 @@ function CustomersList({
<DashboardPageContent>
<CustomersViewsTabs />
<DashboardContentTable>
<CustomersTable />
</DashboardContentTable>
<CustomersTable />
</DashboardPageContent>
<CustomersAlerts />
</CustomersListProvider>

View File

@@ -5,7 +5,7 @@ import CustomersEmptyStatus from './CustomersEmptyStatus';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
import { DataTable } from 'components';
import { DataTable, DashboardContentTable } from 'components';
import withCustomers from './withCustomers';
import withCustomersActions from './withCustomersActions';
@@ -100,37 +100,39 @@ function CustomersTable({
}
return (
<DataTable
noInitialFetch={true}
columns={columns}
data={customers}
initialState={customersTableState}
loading={isCustomersLoading}
headerLoading={isCustomersLoading}
progressBarLoading={isCustomersFetching}
onFetchData={handleFetchData}
selectionColumn={true}
expandable={false}
sticky={true}
spinnerProps={{ size: 30 }}
pagination={true}
manualSortBy={true}
manualPagination={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
payload={{
onDelete: handleCustomerDelete,
onEdit: handleCustomerEdit,
onDuplicate: handleContactDuplicate,
onInactivate: handleInactiveCustomer,
onActivate: handleActivateCustomer,
onViewDetails: handleViewDetailCustomer,
}}
ContextMenu={ActionsMenu}
/>
<DashboardContentTable>
<DataTable
noInitialFetch={true}
columns={columns}
data={customers}
initialState={customersTableState}
loading={isCustomersLoading}
headerLoading={isCustomersLoading}
progressBarLoading={isCustomersFetching}
onFetchData={handleFetchData}
selectionColumn={true}
expandable={false}
sticky={true}
spinnerProps={{ size: 30 }}
pagination={true}
manualSortBy={true}
manualPagination={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
payload={{
onDelete: handleCustomerDelete,
onEdit: handleCustomerEdit,
onDuplicate: handleContactDuplicate,
onInactivate: handleInactiveCustomer,
onActivate: handleActivateCustomer,
onViewDetails: handleViewDetailCustomer,
}}
ContextMenu={ActionsMenu}
/>
</DashboardContentTable>
);
}

View File

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

View File

@@ -2,6 +2,8 @@ import { connect } from 'react-redux';
import t from 'store/types';
import {
toggleExpendSidebar,
appIsLoading,
appIntlIsLoading
} from 'store/dashboard/dashboard.actions';
const mapActionsToProps = (dispatch) => ({
@@ -55,6 +57,8 @@ const mapActionsToProps = (dispatch) => ({
type: t.SET_DASHBOARD_BACK_LINK,
payload: { backLink },
}),
setAppIsLoading: (isLoading) => dispatch(appIsLoading(isLoading)),
setAppIntlIsLoading: (isLoading) => dispatch(appIntlIsLoading(isLoading)),
});
export default connect(null, mapActionsToProps);

View File

@@ -35,8 +35,12 @@ function PaymentViaLicenseDialogContent({
const handleSubmit = (values, { setSubmitting, setErrors }) => {
setSubmitting(true);
const mutateValues = {
plan_slug: `${values.plan_slug}-${values.period}ly`,
license_code: values.license_code,
};
// Payment via voucher mutate.
paymentViaVoucherMutate({ ...values })
paymentViaVoucherMutate({ ...mutateValues })
.then(() => {
Toaster.show({
message: intl.get('payment_via_voucher.success_message'),

View File

@@ -4,6 +4,7 @@ import { useHistory } from 'react-router-dom';
import { compose } from 'utils';
import { useExpensesListContext } from './ExpensesListProvider';
import { DashboardContentTable } from 'components';
import DataTable from 'components/DataTable';
import ExpensesEmptyStatus from './ExpensesEmptyStatus';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
@@ -84,32 +85,34 @@ function ExpensesDataTable({
}
return (
<DataTable
columns={columns}
data={expenses}
loading={isExpensesLoading}
headerLoading={isExpensesLoading}
progressBarLoading={isExpensesFetching}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onFetchData={handleFetchData}
pagination={true}
manualSortBy={true}
manualPagination={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onPublish: handlePublishExpense,
onDelete: handleDeleteExpense,
onEdit: handleEditExpense,
onViewDetails: handleViewDetailExpense,
}}
/>
<DashboardContentTable>
<DataTable
columns={columns}
data={expenses}
loading={isExpensesLoading}
headerLoading={isExpensesLoading}
progressBarLoading={isExpensesFetching}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onFetchData={handleFetchData}
pagination={true}
manualSortBy={true}
manualPagination={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onPublish: handlePublishExpense,
onDelete: handleDeleteExpense,
onEdit: handleEditExpense,
onViewDetails: handleViewDetailExpense,
}}
/>
</DashboardContentTable>
);
}

View File

@@ -47,10 +47,7 @@ function ExpensesList({
<DashboardPageContent>
<ExpenseViewTabs />
<DashboardContentTable>
<ExpenseDataTable />
</DashboardContentTable>
<ExpenseDataTable />
</DashboardPageContent>
<ExpensesAlerts />

View File

@@ -12,8 +12,8 @@ import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import { APAgingSummaryProvider } from './APAgingSummaryProvider';
import { APAgingSummarySheetLoadingBar } from './components';
import withSettings from 'containers/Settings/withSettings';
import withAPAgingSummaryActions from './withAPAgingSummaryActions'
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
import withAPAgingSummaryActions from './withAPAgingSummaryActions';
import { compose } from 'utils';
/**
@@ -51,9 +51,12 @@ function APAgingSummary({
};
// Hide the report filter drawer once the page unmount.
useEffect(() => () => {
toggleDisplayFilterDrawer(false);
}, [toggleDisplayFilterDrawer])
useEffect(
() => () => {
toggleDisplayFilterDrawer(false);
},
[toggleDisplayFilterDrawer],
);
return (
<APAgingSummaryProvider filter={filter}>
@@ -79,8 +82,8 @@ function APAgingSummary({
}
export default compose(
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings?.name,
withCurrentOrganization(({ organization }) => ({
organizationName: organization?.name,
})),
withAPAgingSummaryActions
withAPAgingSummaryActions,
)(APAgingSummary);

View File

@@ -13,7 +13,7 @@ import { ARAgingSummaryProvider } from './ARAgingSummaryProvider';
import { ARAgingSummarySheetLoadingBar } from './components';
import withARAgingSummaryActions from './withARAgingSummaryActions'
import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
import { compose } from 'utils';
@@ -77,8 +77,8 @@ function ReceivableAgingSummarySheet({
}
export default compose(
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings.name,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
withARAgingSummaryActions
)(ReceivableAgingSummarySheet);

View File

@@ -10,7 +10,7 @@ import { BalanceSheetAlerts, BalanceSheetLoadingBar } from './components';
import { FinancialStatement } from 'components';
import withBalanceSheetActions from './withBalanceSheetActions';
import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
import { BalanceSheetProvider } from './BalanceSheetProvider';
import { compose } from 'utils';
@@ -23,7 +23,7 @@ function BalanceSheet({
organizationName,
// #withBalanceSheetActions
toggleBalanceSheetFilterDrawer
toggleBalanceSheetFilterDrawer,
}) {
const [filter, setFilter] = useState({
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
@@ -52,9 +52,12 @@ function BalanceSheet({
};
// Hides the balance sheet filter drawer once the page unmount.
useEffect(() => () => {
toggleBalanceSheetFilterDrawer(false);
}, [toggleBalanceSheetFilterDrawer])
useEffect(
() => () => {
toggleBalanceSheetFilterDrawer(false);
},
[toggleBalanceSheetFilterDrawer],
);
return (
<BalanceSheetProvider filter={filter}>
@@ -81,8 +84,8 @@ function BalanceSheet({
}
export default compose(
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings.name,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
withBalanceSheetActions,
)(BalanceSheet);

View File

@@ -9,7 +9,7 @@ import CashFlowStatementHeader from './CashFlowStatementHeader';
import CashFlowStatementTable from './CashFlowStatementTable';
import CashFlowStatementActionsBar from './CashFlowStatementActionsBar';
import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
import withCashFlowStatementActions from './withCashFlowStatementActions';
import { CashFlowStatementProvider } from './CashFlowStatementProvider';
import {
@@ -85,8 +85,8 @@ function CashFlowStatement({
}
export default compose(
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings?.name,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
withCashFlowStatementActions,
)(CashFlowStatement);

View File

@@ -13,8 +13,7 @@ import CustomersBalanceSummaryTable from './CustomersBalanceSummaryTable';
import { CustomersBalanceLoadingBar } from './components';
import { CustomersBalanceSummaryProvider } from './CustomersBalanceSummaryProvider';
import withCustomersBalanceSummaryActions from './withCustomersBalanceSummaryActions';
import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
import { compose } from 'redux';
@@ -81,8 +80,8 @@ function CustomersBalanceSummary({
);
}
export default compose(
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings.name,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
withCustomersBalanceSummaryActions,
)(CustomersBalanceSummary);

View File

@@ -10,7 +10,7 @@ import CustomersTransactionsTable from './CustomersTransactionsTable';
import CustomersTransactionsActionsBar from './CustomersTransactionsActionsBar';
import withCustomersTransactionsActions from './withCustomersTransactionsActions';
import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
import { CustomersTransactionsLoadingBar } from './components';
import { CustomersTransactionsProvider } from './CustomersTransactionsProvider';
@@ -81,8 +81,8 @@ function CustomersTransactions({
);
}
export default compose(
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings.name,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
withCustomersTransactionsActions,
)(CustomersTransactions);

View File

@@ -15,7 +15,7 @@ import {
} from './components';
import withGeneralLedgerActions from './withGeneralLedgerActions';
import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
import { transformFilterFormToQuery } from 'containers/FinancialStatements/common';
import { compose } from 'utils';
@@ -85,7 +85,7 @@ function GeneralLedger({
export default compose(
withGeneralLedgerActions,
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings.name,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(GeneralLedger);

View File

@@ -10,7 +10,7 @@ import InventoryItemDetailsHeader from './InventoryItemDetailsHeader';
import InventoryItemDetailsTable from './InventoryItemDetailsTable';
import withInventoryItemDetailsActions from './withInventoryItemDetailsActions';
import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
import { InventoryItemDetailsProvider } from './InventoryItemDetailsProvider';
import {
InventoryItemDetailsLoadingBar,
@@ -84,8 +84,8 @@ function InventoryItemDetails({
}
export default compose(
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings?.name,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
withInventoryItemDetailsActions,
)(InventoryItemDetails);

View File

@@ -11,7 +11,7 @@ import InventoryValuationTable from './InventoryValuationTable';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import { InventoryValuationLoadingBar } from './components';
import withInventoryValuationActions from './withInventoryValuationActions';
import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
import { compose } from 'utils';
@@ -80,7 +80,7 @@ function InventoryValuation({
export default compose(
withInventoryValuationActions,
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings?.name,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(InventoryValuation);

View File

@@ -11,7 +11,7 @@ import JournalActionsBar from './JournalActionsBar';
import { JournalSheetProvider } from './JournalProvider';
import { JournalSheetLoadingBar, JournalSheetAlerts } from './components';
import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withJournalActions from './withJournalActions';
@@ -79,7 +79,7 @@ function Journal({
export default compose(
withDashboardActions,
withJournalActions,
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings.name,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(Journal);

View File

@@ -10,7 +10,7 @@ import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withProfitLossActions from './withProfitLossActions';
import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
import 'style/pages/FinancialStatements/ProfitLossSheet.scss';
import { ProfitLossSheetProvider } from './ProfitLossProvider';
@@ -91,7 +91,7 @@ function ProfitLossSheet({
export default compose(
withDashboardActions,
withProfitLossActions,
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings.name,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(ProfitLossSheet);

View File

@@ -11,7 +11,7 @@ import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import { PurchasesByItemsLoadingBar } from './components';
import withPurchasesByItemsActions from './withPurchasesByItemsActions';
import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
import { compose } from 'utils';
/**
@@ -82,7 +82,7 @@ function PurchasesByItems({
export default compose(
withPurchasesByItemsActions,
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings.name,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(PurchasesByItems);

View File

@@ -13,7 +13,7 @@ import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import { SalesByItemsLoadingBar } from './components';
import withSalesByItemsActions from './withSalesByItemsActions';
import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
import { compose } from 'utils';
@@ -84,7 +84,7 @@ function SalesByItems({
export default compose(
withSalesByItemsActions,
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings.name,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(SalesByItems);

View File

@@ -15,7 +15,7 @@ import {
} from './components';
import withTrialBalanceActions from './withTrialBalanceActions';
import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
import { compose } from 'utils';
@@ -91,7 +91,7 @@ function TrialBalanceSheet({
export default compose(
withTrialBalanceActions,
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings.name,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(TrialBalanceSheet);

View File

@@ -14,7 +14,7 @@ import { VendorsBalanceSummaryProvider } from './VendorsBalanceSummaryProvider';
import { VendorsSummarySheetLoadingBar } from './components';
import withVendorsBalanceSummaryActions from './withVendorsBalanceSummaryActions';
import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
import { compose } from 'utils';
@@ -82,8 +82,8 @@ function VendorsBalanceSummary({
}
export default compose(
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings?.name,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
withVendorsBalanceSummaryActions,
)(VendorsBalanceSummary);

View File

@@ -10,7 +10,7 @@ import VendorsTransactionsActionsBar from './VendorsTransactionsActionsBar';
import VendorsTransactionsTable from './VendorsTransactionsTable';
import withVendorsTransactionsActions from './withVendorsTransactionsActions';
import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
import { VendorsTransactionsProvider } from './VendorsTransactionsProvider';
import { VendorsTransactionsLoadingBar } from './components';
@@ -81,8 +81,8 @@ function VendorsTransactions({
);
}
export default compose(
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings.name,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
withVendorsTransactionsActions,
)(VendorsTransactions);

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { useHistory } from 'react-router-dom';
import { FormattedMessage as T } from 'components';
import { DataTable } from 'components';
import { DashboardContentTable, DataTable } from 'components';
import ItemsEmptyStatus from './ItemsEmptyStatus';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
@@ -108,41 +108,43 @@ function ItemsDataTable({
}
return (
<DataTable
columns={columns}
data={items}
initialState={itemsTableState}
loading={isItemsLoading}
headerLoading={isItemsLoading}
progressBarLoading={isItemsFetching}
noInitialFetch={true}
selectionColumn={true}
spinnerProps={{ size: 30 }}
expandable={false}
sticky={true}
rowClassNames={rowClassNames}
pagination={true}
manualSortBy={true}
manualPagination={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={true}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ItemsActionMenuList}
onFetchData={handleFetchData}
payload={{
onDeleteItem: handleDeleteItem,
onEditItem: handleEditItem,
onInactivateItem: handleInactiveItem,
onActivateItem: handleActivateItem,
onMakeAdjustment: handleMakeAdjustment,
onDuplicate: handleDuplicate,
onViewDetails: handleViewDetailItem,
}}
noResults={<T id={'there_is_no_items_in_the_table_yet'} />}
{...tableProps}
/>
<DashboardContentTable>
<DataTable
columns={columns}
data={items}
initialState={itemsTableState}
loading={isItemsLoading}
headerLoading={isItemsLoading}
progressBarLoading={isItemsFetching}
noInitialFetch={true}
selectionColumn={true}
spinnerProps={{ size: 30 }}
expandable={false}
sticky={true}
rowClassNames={rowClassNames}
pagination={true}
manualSortBy={true}
manualPagination={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={true}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ItemsActionMenuList}
onFetchData={handleFetchData}
payload={{
onDeleteItem: handleDeleteItem,
onEditItem: handleEditItem,
onInactivateItem: handleInactiveItem,
onActivateItem: handleActivateItem,
onMakeAdjustment: handleMakeAdjustment,
onDuplicate: handleDuplicate,
onViewDetails: handleViewDetailItem,
}}
noResults={<T id={'there_is_no_items_in_the_table_yet'} />}
{...tableProps}
/>
</DashboardContentTable>
);
}

View File

@@ -28,7 +28,7 @@ export default function ItemsEmptyStatus() {
</Button>
<Button intent={Intent.NONE} large={true}>
<T id={'learn_more'}/>
<T id={'learn_more'} />
</Button>
</>
}

View File

@@ -3,7 +3,7 @@ import { compose } from 'utils';
import 'style/pages/Items/List.scss';
import { DashboardContentTable, DashboardPageContent } from 'components';
import { DashboardPageContent } from 'components';
import ItemsActionsBar from './ItemsActionsBar';
import ItemsAlerts from './ItemsAlerts';
@@ -43,10 +43,7 @@ function ItemsList({
<DashboardPageContent>
<ItemsViewsTabs />
<DashboardContentTable>
<ItemsDataTable />
</DashboardContentTable>
<ItemsDataTable />
</DashboardPageContent>
<ItemsAlerts />

View File

@@ -51,6 +51,11 @@ function GeneralFormPage({
intent: Intent.SUCCESS,
});
setSubmitting(false);
// Reboot the application if the application's language is mutated.
if (organization.language !== values.language) {
window.location.reload();
}
};
// Handle request error.
const onError = (errors) => {

View File

@@ -47,10 +47,7 @@ function BillsList({
<DashboardPageContent>
<BillsViewsTabs />
<DashboardContentTable>
<BillsTable />
</DashboardContentTable>
<BillsTable />
</DashboardPageContent>
<BillsAlerts />

View File

@@ -4,6 +4,7 @@ import { useHistory } from 'react-router-dom';
import { compose } from 'utils';
import DataTable from 'components/DataTable';
import { DashboardContentTable } from 'components';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
@@ -92,6 +93,7 @@ function BillsDataTable({
}
return (
<DashboardContentTable>
<DataTable
columns={columns}
data={bills}
@@ -118,6 +120,7 @@ function BillsDataTable({
onViewDetails: handleViewDetailBill,
}}
/>
</DashboardContentTable>
);
}

View File

@@ -42,10 +42,7 @@ function PaymentMadeList({
<DashboardPageContent>
<PaymentMadeViewTabs />
<DashboardContentTable>
<PaymentMadesTable />
</DashboardContentTable>
<PaymentMadesTable />
</DashboardPageContent>
<PaymentMadesAlerts />

View File

@@ -3,7 +3,8 @@ import { useHistory } from 'react-router-dom';
import { compose } from 'utils';
import { DataTable } from 'components';
import { DataTable, DashboardContentTable } from 'components';
import PaymentMadesEmptyStatus from './PaymentMadesEmptyStatus';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
@@ -76,31 +77,33 @@ function PaymentMadesTable({
}
return (
<DataTable
columns={columns}
data={paymentMades}
initialState={paymentMadesTableState}
onFetchData={handleDataTableFetchData}
loading={isPaymentsLoading}
headerLoading={isPaymentsLoading}
progressBarLoading={isPaymentsFetching}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
pagination={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onEdit: handleEditPaymentMade,
onDelete: handleDeletePaymentMade,
onViewDetails: handleViewDetailPaymentMade,
}}
/>
<DashboardContentTable>
<DataTable
columns={columns}
data={paymentMades}
initialState={paymentMadesTableState}
onFetchData={handleDataTableFetchData}
loading={isPaymentsLoading}
headerLoading={isPaymentsLoading}
progressBarLoading={isPaymentsFetching}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
pagination={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onEdit: handleEditPaymentMade,
onDelete: handleDeletePaymentMade,
onViewDetails: handleViewDetailPaymentMade,
}}
/>
</DashboardContentTable>
);
}

View File

@@ -3,7 +3,7 @@ import { useHistory } from 'react-router-dom';
import { compose } from 'utils';
import { DataTable } from 'components';
import { DataTable, DashboardContentTable } from 'components';
import EstimatesEmptyStatus from './EstimatesEmptyStatus';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
@@ -109,35 +109,37 @@ function EstimatesDataTable({
}
return (
<DataTable
columns={columns}
data={estimates}
loading={isEstimatesLoading}
headerLoading={isEstimatesLoading}
progressBarLoading={isEstimatesFetching}
onFetchData={handleFetchData}
noInitialFetch={true}
manualSortBy={true}
selectionColumn={true}
sticky={true}
pagination={true}
manualPagination={true}
pagesCount={pagination.pagesCount}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onApprove: handleApproveEstimate,
onEdit: handleEditEstimate,
onReject: handleRejectEstimate,
onDeliver: handleDeliverEstimate,
onDelete: handleDeleteEstimate,
onDrawer: handleDrawerEstimate,
onConvert: handleConvertToInvoice,
onViewDetails: handleViewDetailEstimate,
onPrint: handlePrintEstimate,
}}
/>
<DashboardContentTable>
<DataTable
columns={columns}
data={estimates}
loading={isEstimatesLoading}
headerLoading={isEstimatesLoading}
progressBarLoading={isEstimatesFetching}
onFetchData={handleFetchData}
noInitialFetch={true}
manualSortBy={true}
selectionColumn={true}
sticky={true}
pagination={true}
manualPagination={true}
pagesCount={pagination.pagesCount}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onApprove: handleApproveEstimate,
onEdit: handleEditEstimate,
onReject: handleRejectEstimate,
onDeliver: handleDeliverEstimate,
onDelete: handleDeleteEstimate,
onDrawer: handleDrawerEstimate,
onConvert: handleConvertToInvoice,
onViewDetails: handleViewDetailEstimate,
onPrint: handlePrintEstimate,
}}
/>
</DashboardContentTable>
);
}

View File

@@ -42,10 +42,7 @@ function EstimatesList({
<DashboardPageContent>
<EstimatesViewTabs />
<DashboardContentTable>
<EstimatesDataTable />
</DashboardContentTable>
<EstimatesDataTable />
</DashboardPageContent>
<EstimatesAlerts />

View File

@@ -4,7 +4,7 @@ import { useHistory } from 'react-router-dom';
import InvoicesEmptyStatus from './InvoicesEmptyStatus';
import { compose } from 'utils';
import { DataTable } from 'components';
import { DataTable, DashboardContentTable } from 'components';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
@@ -108,37 +108,39 @@ function InvoicesDataTable({
}
return (
<DataTable
columns={columns}
data={invoices}
initialState={invoicesTableState}
loading={isInvoicesLoading}
headerLoading={isInvoicesLoading}
progressBarLoading={isInvoicesFetching}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
pagination={true}
manualPagination={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onDelete: handleDeleteInvoice,
onDeliver: handleDeliverInvoice,
onEdit: handleEditInvoice,
onDrawer: handleDrawerInvoice,
onQuick: handleQuickPaymentReceive,
onViewDetails: handleViewDetailInvoice,
onPrint: handlePrintInvoice,
baseCurrency,
}}
/>
<DashboardContentTable>
<DataTable
columns={columns}
data={invoices}
initialState={invoicesTableState}
loading={isInvoicesLoading}
headerLoading={isInvoicesLoading}
progressBarLoading={isInvoicesFetching}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
pagination={true}
manualPagination={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onDelete: handleDeleteInvoice,
onDeliver: handleDeliverInvoice,
onEdit: handleEditInvoice,
onDrawer: handleDrawerInvoice,
onQuick: handleQuickPaymentReceive,
onViewDetails: handleViewDetailInvoice,
onPrint: handlePrintInvoice,
baseCurrency,
}}
/>
</DashboardContentTable>
);
}

View File

@@ -44,10 +44,7 @@ function InvoicesList({
<DashboardPageContent>
<InvoiceViewTabs />
<DashboardContentTable>
<InvoicesDataTable />
</DashboardContentTable>
<InvoicesDataTable />
</DashboardPageContent>
<InvoicesAlerts />

View File

@@ -42,10 +42,7 @@ function PaymentReceiveList({
<DashboardPageContent>
<PaymentReceiveViewTabs />
<DashboardContentTable>
<PaymentReceivesTable />
</DashboardContentTable>
<PaymentReceivesTable />
</DashboardPageContent>
<PaymentReceiveAlerts />

View File

@@ -3,8 +3,8 @@ import { useHistory } from 'react-router-dom';
import { compose } from 'utils';
import { DataTable, DashboardContentTable } from 'components';
import PaymentReceivesEmptyStatus from './PaymentReceivesEmptyStatus';
import { DataTable } from 'components';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
@@ -85,32 +85,34 @@ function PaymentReceivesDataTable({
}
return (
<DataTable
columns={columns}
data={paymentReceives}
initialState={paymentReceivesTableState}
loading={isPaymentReceivesLoading}
headerLoading={isPaymentReceivesLoading}
progressBarLoading={isPaymentReceivesFetching}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
autoResetSortBy={false}
autoResetPage={false}
pagination={true}
pagesCount={pagination.pagesCount}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onDelete: handleDeletePaymentReceive,
onEdit: handleEditPaymentReceive,
onDrawer: handleDrawerPaymentReceive,
onViewDetails: handleViewDetailPaymentReceive,
}}
/>
<DashboardContentTable>
<DataTable
columns={columns}
data={paymentReceives}
initialState={paymentReceivesTableState}
loading={isPaymentReceivesLoading}
headerLoading={isPaymentReceivesLoading}
progressBarLoading={isPaymentReceivesFetching}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
autoResetSortBy={false}
autoResetPage={false}
pagination={true}
pagesCount={pagination.pagesCount}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onDelete: handleDeletePaymentReceive,
onEdit: handleEditPaymentReceive,
onDrawer: handleDrawerPaymentReceive,
onViewDetails: handleViewDetailPaymentReceive,
}}
/>
</DashboardContentTable>
);
}

View File

@@ -43,10 +43,7 @@ function ReceiptsList({
<DashboardPageContent>
<ReceiptViewTabs />
<DashboardContentTable>
<ReceiptsTable />
</DashboardContentTable>
<ReceiptsTable />
</DashboardPageContent>
<ReceiptsAlerts />

View File

@@ -2,7 +2,7 @@ import React, { useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import { compose } from 'utils';
import { DataTable } from 'components';
import { DataTable, DashboardContentTable } from 'components';
import ReceiptsEmptyStatus from './ReceiptsEmptyStatus';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
@@ -101,36 +101,38 @@ function ReceiptsDataTable({
}
return (
<DataTable
columns={columns}
data={receipts}
initialState={receiptTableState}
loading={isReceiptsLoading}
headerLoading={isReceiptsLoading}
progressBarLoading={isReceiptsFetching}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
pagination={true}
pagesCount={pagination.pagesCount}
manualPagination={true}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onEdit: handleEditReceipt,
onDelete: handleDeleteReceipt,
onClose: handleCloseReceipt,
onDrawer: handleDrawerReceipt,
onViewDetails: handleViewDetailReceipt,
onPrint: handlePrintInvoice,
baseCurrency,
}}
/>
<DashboardContentTable>
<DataTable
columns={columns}
data={receipts}
initialState={receiptTableState}
loading={isReceiptsLoading}
headerLoading={isReceiptsLoading}
progressBarLoading={isReceiptsFetching}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
pagination={true}
pagesCount={pagination.pagesCount}
manualPagination={true}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onEdit: handleEditReceipt,
onDelete: handleDeleteReceipt,
onClose: handleCloseReceipt,
onDrawer: handleDrawerReceipt,
onViewDetails: handleViewDetailReceipt,
onPrint: handlePrintInvoice,
baseCurrency,
}}
/>
</DashboardContentTable>
);
}

View File

@@ -1,6 +1,5 @@
import React, { useCallback } from 'react';
import React from 'react';
import { Button, Intent } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import WorkflowIcon from './WorkflowIcon';
import { FormattedMessage as T } from 'components';
@@ -10,17 +9,16 @@ import { compose } from 'utils';
import 'style/pages/Setup/Congrats.scss';
/**
* Setup congrats page.
*/
function SetupCongratsPage({ setOrganizationSetupCompleted }) {
const history = useHistory();
const [isReloading, setIsReloading] = React.useState(false);
const handleBtnClick = useCallback(() => {
setOrganizationSetupCompleted(false);
history.push('/homepage');
}, [setOrganizationSetupCompleted, history]);
const handleBtnClick = () => {
setIsReloading(true);
window.location.reload();
};
return (
<div class="setup-congrats">
@@ -37,7 +35,12 @@ function SetupCongratsPage({ setOrganizationSetupCompleted }) {
<T id={'setup.congrats.description'} />
</p>
<Button intent={Intent.PRIMARY} type="submit" onClick={handleBtnClick}>
<Button
intent={Intent.PRIMARY}
type="submit"
loading={isReloading}
onClick={handleBtnClick}
>
<T id={'setup.congrats.go_to_dashboard'} />
</Button>
</div>

View File

@@ -20,10 +20,12 @@ function SetupInitializingForm({
}) {
const { refetch, isSuccess } = useCurrentOrganization({ enabled: false });
// Job done state.
const [isJobDone, setIsJobDone] = React.useState(false);
const {
data: { running, queued, failed, completed },
isFetching: isJobFetching,
} = useJob(organization?.build_job_id, {
refetchInterval: 2000,
enabled: !!organization?.build_job_id,
@@ -45,17 +47,15 @@ function SetupInitializingForm({
return (
<div class="setup-initializing-form">
<ProgressBar intent={Intent.PRIMARY} value={null} />
<div className={'setup-initializing-form__title'}>
{failed ? (
<SetupInitializingFailed />
) : running || queued ? (
<SetupInitializingRunning />
) : completed ? (
<SetupInitializingCompleted />
) : null}
</div>
{failed ? (
<SetupInitializingFailed />
) : running || queued || isJobFetching ? (
<SetupInitializingRunning />
) : completed ? (
<SetupInitializingCompleted />
) : (
<SetupInitializingFailed />
)}
</div>
);
}
@@ -68,41 +68,60 @@ export default R.compose(
withOrganization(({ organization }) => ({ organization })),
)(SetupInitializingForm);
/**
* State initializing failed state.
*/
function SetupInitializingFailed() {
return (
<div class="failed">
<h1>
<T id={'setup.initializing.something_went_wrong'} />
</h1>
<p class="paragraph">
<T id={'setup.initializing.please_refresh_the_page'} />
</p>
<div class="setup-initializing__content">
<div className={'setup-initializing-form__title'}>
<h1>
<T id={'setup.initializing.something_went_wrong'} />
</h1>
<p class="paragraph">
<T id={'setup.initializing.please_refresh_the_page'} />
</p>
</div>
</div>
);
}
/**
* Setup initializing running state.
*/
function SetupInitializingRunning() {
return (
<div class="running">
<h1>
<T id={'setup.initializing.title'} />
</h1>
<p className={'paragraph'}>
<T id={'setup.initializing.description'} />
</p>
<div class="setup-initializing__content">
<ProgressBar intent={Intent.PRIMARY} value={null} />
<div className={'setup-initializing-form__title'}>
<h1>
<T id={'setup.initializing.title'} />
</h1>
<p className={'paragraph'}>
<T id={'setup.initializing.description'} />
</p>
</div>
</div>
);
}
/**
* Setup initializing completed state.
*/
function SetupInitializingCompleted() {
return (
<div class="completed">
<h1>
<T id={'setup.initializing.waiting_to_redirect'} />
</h1>
<p class="paragraph">
<T id={'setup.initializing.refresh_the_page_if_redirect_not_worked'} />
</p>
<div class="setup-initializing__content">
<div className={'setup-initializing-form__title'}>
<h1>
<T id={'setup.initializing.waiting_to_redirect'} />
</h1>
<p class="paragraph">
<T
id={'setup.initializing.refresh_the_page_if_redirect_not_worked'}
/>
</p>
</div>
</div>
);
}

View File

@@ -10,6 +10,7 @@ import {
} from '@blueprintjs/core';
import classNames from 'classnames';
import { TimezonePicker } from '@blueprintjs/timezone';
import useAutofocus from 'hooks/useAutofocus'
import { FormattedMessage as T } from 'components';
import { getCountries } from 'common/countries';
@@ -29,6 +30,8 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
const currencies = getAllCurrenciesOptions();
const countries = getCountries();
const accountRef = useAutofocus();
return (
<Form>
<h3>
@@ -44,7 +47,11 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'name'} />}
>
<InputGroup {...field} intent={inputIntent({ error, touched })} />
<InputGroup
{...field}
intent={inputIntent({ error, touched })}
inputRef={accountRef}
/>
</FormGroup>
)}
</FastField>

View File

@@ -9,7 +9,7 @@ import SetupOrganizationForm from './SetupOrganizationForm';
import { useOrganizationSetup } from 'hooks/query';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import { compose, transfromToSnakeCase } from 'utils';
import { setCookie, compose, transfromToSnakeCase } from 'utils';
import { getSetupOrganizationValidation } from './SetupOrganization.schema';
// Initial values.
@@ -41,6 +41,9 @@ function SetupOrganizationPage({ wizard }) {
organizationSetupMutate({ ...transfromToSnakeCase(values) })
.then((response) => {
setSubmitting(false);
// Sets locale cookie to next boot cycle.
setCookie('locale', values.language);
wizard.next();
})
.catch((erros) => {

View File

@@ -21,12 +21,12 @@ function SetupSubscription({
// Initial values.
const initialValues = {
plan_slug: 'starter',
plan_slug: 'essentials',
period: 'month',
license_code: '',
};
// Handle form submit.
const handleSubmit = () => {};
const handleSubmit = (values) => {};
// Retrieve momerized subscription form schema.
const SubscriptionFormSchema = React.useMemo(

View File

@@ -9,6 +9,10 @@ import withPlan from '../../Subscriptions/withPlan';
const SubscriptionPeriodsEnhanced = R.compose(
withPlan(({ plan }) => ({ plan })),
)(({ plan, ...restProps }) => {
// Can't continue if the current plan of the form not selected.
if (!plan) {
return null;
}
return <SubscriptionPeriods periods={plan.periods} {...restProps} />;
});

View File

@@ -42,7 +42,7 @@ function BillingForm({
// Initial values.
const initialValues = {
plan_slug: 'free',
plan_slug: 'essentials',
period: 'month',
license_code: '',
};

View File

@@ -12,6 +12,8 @@ import withPlan from './withPlan';
const SubscriptionPeriodsEnhanced = R.compose(
withPlan(({ plan }) => ({ plan })),
)(({ plan, ...restProps }) => {
if (!plan) return null;
return <SubscriptionPeriods periods={plan.periods} {...restProps} />;
});

View File

@@ -47,13 +47,9 @@ function VendorsList({
<DashboardPageContent>
<VendorViewsTabs />
<DashboardContentTable>
<VendorsTable />
</DashboardContentTable>
<VendorsAlerts />
<VendorsTable />
</DashboardPageContent>
<VendorsAlerts />
</VendorsListProvider>
);
}

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { useHistory } from 'react-router';
import { DataTable } from 'components';
import { DataTable, DashboardContentTable } from 'components';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
@@ -85,7 +85,7 @@ function VendorsTable({
const handleViewDetailVendor = ({ id }) => {
openDrawer('contact-detail-drawer', { contactId: id });
};
// Handle fetch data once the page index, size or sort by of the table change.
const handleFetchData = React.useCallback(
({ pageSize, pageIndex, sortBy }) => {
@@ -104,35 +104,37 @@ function VendorsTable({
}
return (
<DataTable
noInitialFetch={true}
columns={columns}
data={vendors}
initialState={vendorsTableState}
loading={isVendorsLoading}
headerLoading={isVendorsLoading}
progressBarLoading={isVendorsFetching}
onFetchData={handleFetchData}
selectionColumn={true}
expandable={false}
sticky={true}
pagination={true}
manualSortBy={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onEdit: handleEditVendor,
onDelete: handleDeleteVendor,
onDuplicate: handleContactDuplicate,
onInactivate: handleInactiveVendor,
onActivate: handleActivateVendor,
onViewDetails: handleViewDetailVendor,
}}
/>
<DashboardContentTable>
<DataTable
noInitialFetch={true}
columns={columns}
data={vendors}
initialState={vendorsTableState}
loading={isVendorsLoading}
headerLoading={isVendorsLoading}
progressBarLoading={isVendorsFetching}
onFetchData={handleFetchData}
selectionColumn={true}
expandable={false}
sticky={true}
pagination={true}
manualSortBy={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onEdit: handleEditVendor,
onDelete: handleDeleteVendor,
onDuplicate: handleContactDuplicate,
onInactivate: handleInactiveVendor,
onActivate: handleActivateVendor,
onViewDetails: handleViewDetailVendor,
}}
/>
</DashboardContentTable>
);
}

View File

@@ -1,28 +1,37 @@
import { useMutation } from 'react-query';
import useApiRequest from '../useRequest';
import { useAuthActions } from '../state';
import { persistor } from 'store/createStore';
import { setCookie } from '../../utils';
/**
* Saves the response data to cookies.
*/
function setAuthLoginCookies(data) {
setCookie('token', data.token);
setCookie('authenticated_user_id', data.user.id);
setCookie('organization_id', data.tenant.organization_id);
setCookie('tenant_id', data.tenant.id);
if (data?.tenant?.metadata?.language)
setCookie('locale', data.tenant.metadata.language);
}
/**
* Authentication login.
*/
export const useAuthLogin = (props) => {
const { setLogin } = useAuthActions();
const apiRequest = useApiRequest();
return useMutation(
(values) => apiRequest.post('auth/login', values),
{
select: (res) => res.data,
onSuccess: (data) => {
setLogin(data.data);
return useMutation((values) => apiRequest.post('auth/login', values), {
select: (res) => res.data,
onSuccess: (data) => {
// Set authentication cookies.
setAuthLoginCookies(data.data);
// Run the store persist.
persistor.persist();
},
...props
}
);
// Reboot the application.
window.location.reload();
},
...props,
});
};
/**
@@ -34,7 +43,7 @@ export const useAuthRegister = (props) => {
return useMutation(
(values) => apiRequest.post('auth/register', values),
props,
)
);
};
/**
@@ -45,9 +54,9 @@ export const useAuthSendResetPassword = (props) => {
return useMutation(
(email) => apiRequest.post('auth/send_reset_password', email),
props
props,
);
}
};
/**
* Authentication reset password.
@@ -58,5 +67,5 @@ export const useAuthResetPassword = (props) => {
return useMutation(
([token, values]) => apiRequest.post(`auth/reset/${token}`, values),
props,
)
}
);
};

View File

@@ -0,0 +1,9 @@
// Query client config.
export const queryConfig = {
defaultOptions: {
queries: {
refetchOnWindowFocus: true,
staleTime: 30000,
},
},
};

View File

@@ -112,11 +112,16 @@ export function useUsers(props) {
* Retrieve details of the given user.
*/
export function useUser(id, props) {
const apiRequest = useApiRequest();
return useQueryTenant(
return useRequestQuery(
[t.USER, id],
() => apiRequest.get(`users/${id}`).then((response) => response.data.user),
props,
{
method: 'get',
url: `users/${id}`,
},
{
select: (response) => response.data.user,
defaultData: {},
...props,
},
);
}

View File

@@ -6,6 +6,15 @@ import {
setStoreReset,
} from 'store/authentication/authentication.actions';
import { useQueryClient } from 'react-query';
import { removeCookie } from '../../utils';
function removeAuthenticationCookies() {
removeCookie('token');
removeCookie('organization_id');
removeCookie('tenant_id');
removeCookie('authenticated_user_id');
removeCookie('locale');
}
export const useAuthActions = () => {
const dispatch = useDispatch();
@@ -15,11 +24,15 @@ export const useAuthActions = () => {
setLogin: useCallback((login) => dispatch(setLogin(login)), [dispatch]),
setLogout: useCallback(() => {
// Resets store state.
dispatch(setStoreReset());
// dispatch(setStoreReset());
// Remove all cached queries.
queryClient.removeQueries();
}, [dispatch, queryClient]),
removeAuthenticationCookies();
window.location.reload();
}, [queryClient]),
};
};
@@ -41,12 +54,12 @@ export const useAuthToken = () => {
* Retrieve the authentication user.
*/
export const useAuthUser = () => {
return useSelector((state) => state.authentication.user);
return useSelector((state) => ({}));
};
/**
* Retrieve the authenticated organization id.
*/
export const useAuthOrganizationId = () => {
return useSelector((state) => state.authentication.organization);
return useSelector((state) => state.authentication.organizationId);
};

View File

@@ -1,9 +1,5 @@
import t from 'store/types';
export const setLogin = ({ user, token, tenant }) => ({
type: t.LOGIN_SUCCESS,
payload: { user, token, tenant, },
});
export const setLogin = () => ({ type: t.LOGIN_SUCCESS });
export const setLogout = () => ({ type: t.LOGOUT });
export const setStoreReset = () => ({ type: t.RESET });

View File

@@ -2,36 +2,28 @@ import { createReducer } from '@reduxjs/toolkit';
import { persistReducer } from 'redux-persist';
import purgeStoredState from 'redux-persist/es/purgeStoredState';
import storage from 'redux-persist/lib/storage';
import { getCookie, setCookie } from 'utils';
import t from 'store/types';
import { removeCookie } from '../../utils';
// Read stored data in cookies and merge it with the initial state.
const initialState = {
token: '',
organization: '',
organizationId: null,
user: '',
tenant: {},
locale: '',
token: getCookie('token'),
organizationId: getCookie('organization_id'),
tenantId: getCookie('tenant_id'),
userId: getCookie('authenticated_user_id'),
locale: getCookie('locale'),
errors: [],
};
const STORAGE_KEY = 'bigcapital:authentication';
const CONFIG = {
key: STORAGE_KEY,
blacklist: ['errors'],
whitelist: [],
storage,
};
const reducerInstance = createReducer(initialState, {
[t.LOGIN_SUCCESS]: (state, action) => {
const { token, user, tenant } = action.payload;
state.token = token;
state.user = user;
state.organization = tenant.organization_id;
state.organizationId = tenant.id;
state.tenant = tenant;
},
[t.LOGIN_FAILURE]: (state, action) => {
state.errors = action.errors;
},
@@ -40,15 +32,12 @@ const reducerInstance = createReducer(initialState, {
state.errors = [];
},
[t.RESET]: () => {
[t.RESET]: (state) => {
purgeStoredState(CONFIG);
}
},
});
export default persistReducer(
CONFIG,
reducerInstance,
);
export default persistReducer(CONFIG, reducerInstance);
export const isAuthenticated = (state) => !!state.authentication.token;
export const hasErrorType = (state, errorType) => {
@@ -59,4 +48,4 @@ export const isTenantSeeded = (state) => !!state.tenant.seeded_at;
export const isTenantBuilt = (state) => !!state.tenant.initialized_at;
export const isTenantHasSubscription = () => false;
export const isTenantSubscriptionExpired = () => false;
export const isTenantSubscriptionExpired = () => false;

View File

@@ -1,8 +1,8 @@
import { defaultTo } from 'lodash';
import { createSelector } from '@reduxjs/toolkit';
const getCurrentOrganizationId = (state) => state.authentication.organization;
const getCurrentTenantId = (state) => state.authentication.organizationId;
const getCurrentOrganizationId = (state) => state.authentication.organizationId;
const getCurrentTenantId = (state) => state.authentication.tenantId;
const getOrganizationsMap = (state) => state.organizations.data;
// Retrieve organization tenant id.

View File

@@ -10,13 +10,14 @@ import loggerMiddleware from 'middleware/logger';
import rootReducer from 'store/reducers';
import ResetMiddleware from './ResetMiddleware';
const createStoreFactory = (initialState = {}) => {
/**
|--------------------------------------------------
| Middleware Configuration
|--------------------------------------------------
*/
const middleware = [thunkMiddleware, loggerMiddleware ];
const middleware = [thunkMiddleware, loggerMiddleware];
/**
|--------------------------------------------------
@@ -41,8 +42,6 @@ const createStoreFactory = (initialState = {}) => {
rootReducer,
initialState,
composeEnhancers(applyMiddleware(...middleware), ...enhancers),
);
store.asyncReducers = {};
return store;

View File

@@ -69,4 +69,19 @@ export function toggleExpendSidebar(toggle) {
type: t.SIDEBAR_EXPEND_TOGGLE,
payload: { toggle }
};
}
export function appIsLoading(toggle) {
return {
type: t.APP_IS_LOADING,
payload: { isLoading: toggle },
};
}
export function appIntlIsLoading(toggle) {
return {
type: t.APP_INTL_IS_LOADING,
payload: { isLoading: toggle },
};
}

View File

@@ -16,6 +16,8 @@ const initialState = {
topbarEditViewId: null,
requestsLoading: 0,
backLink: false,
appIsLoading: true,
appIntlIsLoading: true,
};
const STORAGE_KEY = 'bigcapital:dashboard';
@@ -100,6 +102,16 @@ const reducerInstance = createReducer(initialState, {
state.backLink = backLink;
},
[t.APP_IS_LOADING]: (state, action) => {
const { isLoading } = action.payload;
state.appIsLoading = isLoading;
},
[t.APP_INTL_IS_LOADING]: (state, action) => {
const { isLoading } = action.payload;
state.appIntlIsLoading = isLoading;
},
[t.RESET]: () => {
purgeStoredState(CONFIG);
},

View File

@@ -14,4 +14,6 @@ export default {
SET_TOPBAR_EDIT_VIEW: 'SET_TOPBAR_EDIT_VIEW',
SIDEBAR_EXPEND_TOGGLE: 'SIDEBAR_EXPEND_TOGGLE',
SET_DASHBOARD_BACK_LINK: 'SET_DASHBOARD_BACK_LINK',
APP_IS_LOADING: 'APP_IS_LOADING',
APP_INTL_IS_LOADING: 'APP_INTL_IS_LOADING'
};

View File

@@ -29,12 +29,12 @@ export const fetchOrganizations = () => (dispatch) =>
export const setOrganizationSetupCompleted =
(congrats) => (dispatch, getState) => {
const organizationId = getState().authentication.organizationId;
const tenantId = getState().authentication.tenantId;
dispatch({
type: t.SET_ORGANIZATION_CONGRATS,
payload: {
organizationId,
tenantId,
congrats,
},
});

View File

@@ -27,10 +27,10 @@ const reducer = createReducer(initialState, {
},
[t.SET_ORGANIZATION_CONGRATS]: (state, action) => {
const { organizationId, congrats } = action.payload;
const { tenantId, congrats } = action.payload;
state.data[organizationId] = {
...(state.data[organizationId] || {}),
state.data[tenantId] = {
...(state.data[tenantId] || {}),
is_congrats: !!congrats,
};
}

View File

@@ -1,50 +1,39 @@
import { createSelector } from '@reduxjs/toolkit';
const organizationSelector = (state, props) => state.organizations.data[props.organizationId];
const organizationSelector = (state, props) => {
const tenantId = state.organizations.byOrganizationId[props.organizationId];
return state.organizations.data[tenantId];
};
export const getOrganizationByIdFactory = () => createSelector(
organizationSelector,
(organization) => organization
);
export const getOrganizationByIdFactory = () =>
createSelector(organizationSelector, (organization) => organization);
export const isOrganizationSeededFactory = () => createSelector(
organizationSelector,
(organization) => {
export const isOrganizationSeededFactory = () =>
createSelector(organizationSelector, (organization) => {
return !!organization?.seeded_at;
},
);
});
export const isOrganizationBuiltFactory = () => createSelector(
organizationSelector,
(organization) => {
export const isOrganizationBuiltFactory = () =>
createSelector(organizationSelector, (organization) => {
return !!organization?.initialized_at;
},
);
});
export const isOrganizationReadyFactory = () => createSelector(
organizationSelector,
(organization) => {
export const isOrganizationReadyFactory = () =>
createSelector(organizationSelector, (organization) => {
return organization?.is_ready;
},
);
});
export const isOrganizationSubscribedFactory = () => createSelector(
organizationSelector,
(organization) => {
export const isOrganizationSubscribedFactory = () =>
createSelector(organizationSelector, (organization) => {
return organization?.subscriptions?.length > 0;
}
);
});
export const isOrganizationCongratsFactory = () => createSelector(
organizationSelector,
(organization) => {
export const isOrganizationCongratsFactory = () =>
createSelector(organizationSelector, (organization) => {
return !!organization?.is_congrats;
}
);
});
export const isOrganizationBuildRunningFactory = () => createSelector(
organizationSelector,
(organization) => {
export const isOrganizationBuildRunningFactory = () =>
createSelector(organizationSelector, (organization) => {
return !!organization?.is_build_running;
}
)
});

View File

@@ -1,5 +1,6 @@
export default {
ORGANIZATION_SET: 'ORGANIZATION_SET',
ORGANIZATIONS_LIST_SET: 'ORGANIZATIONS_LIST_SET',
SET_ORGANIZATION_CONGRATS: 'SET_ORGANIZATION_CONGRATS'
};

View File

@@ -17,7 +17,7 @@ const getSubscriptionPeriods = () => [
const getSubscriptionPlans = () => [
{
name: intl.get('plan.essential.title'),
slug: 'free',
slug: 'essentials',
description: [
intl.get('plan.feature.sale_purchase_invoice'),
intl.get('plan.feature.receivable_payable_accounts'),

View File

@@ -3,6 +3,8 @@
width: 100%;
position: absolute;
display: flex;
background: #fff;
z-index: 999999;
.center {
width: auto;

View File

@@ -1,11 +1,11 @@
.datatable-empty-status {
max-width: 550px;
.root {
max-width: 500px;
width: 100%;
margin: 0 auto;
margin: auto;
padding-bottom: 40px;
text-align: center;
margin-top: 200px;
&__title {
&_title {
font-size: 20px;
color: #2c3a5d;
font-weight: 600;
@@ -19,20 +19,16 @@
font-size: 22px;
}
}
&__desc {
&_desc {
font-size: 16px;
color: #1f3255;
opacity: 0.8;
line-height: 1.6;
html[lang='ar'] & {
font-size: 18px;
}
}
&__actions {
&_actions {
margin-top: 26px;
.bp3-button {
:global .bp3-button {
min-height: 36px;
& + .bp3-button {

View File

@@ -371,6 +371,18 @@ $dashboard-views-bar-height: 44px;
}
}
&__centered-empty-status{
display: flex;
flex: 1 0 0;
flex-direction: column;
margin: auto;
.datatable-empty-status{
margin: auto;
padding-bottom: 40px;
}
}
&__datatable {
display: flex;
flex: 1 0 0;

View File

@@ -11,6 +11,19 @@ import deepMapKeys from 'deep-map-keys';
import { createSelectorCreator, defaultMemoize } from 'reselect';
import { isEqual } from 'lodash';
import jsCookie from 'js-cookie';
export const getCookie = (name, defaultValue) => _.defaultTo(jsCookie.get(name), defaultValue);
export const setCookie = (name, value, expiry = 365, secure = false) => {
jsCookie.set(name, value, { expires: expiry, path: '/', secure });
};
export const removeCookie = (name) => {
return jsCookie.remove(name, { path: '/' });
}
export function removeEmptyFromObject(obj) {
obj = Object.assign({}, obj);
var keys = Object.keys(obj);
@@ -227,6 +240,7 @@ export const firstLettersArgs = (...args) => {
return letters.join('').toUpperCase();
};
export const uniqueMultiProps = (items, props) => {
return _.uniqBy(items, (item) => {
return JSON.stringify(_.pick(item, props));

View File

@@ -59,7 +59,7 @@ export default class PlanSubscription extends mixin(SystemModel) {
const endDate = moment().format(dateFormat);
builder.where('trial_ends_at', '<=', endDate);
}
},
};
}
@@ -79,7 +79,7 @@ export default class PlanSubscription extends mixin(SystemModel) {
modelClass: Tenant.default,
join: {
from: 'subscription_plan_subscriptions.tenantId',
to: 'tenants.id'
to: 'tenants.id',
},
},
@@ -131,23 +131,18 @@ export default class PlanSubscription extends mixin(SystemModel) {
/**
* Set new period from the given details.
* @param {string} invoiceInterval
* @param {number} invoicePeriod
* @param {string} start
*
* @param {string} invoiceInterval
* @param {number} invoicePeriod
* @param {string} start
*
* @return {Object}
*/
setNewPeriod(invoiceInterval, invoicePeriod, start) {
let _invoiceInterval = invoiceInterval;
let _invoicePeriod = invoicePeriod;
if (!invoiceInterval) {
_invoiceInterval = this.plan.invoiceInterval;
}
if (!invoicePeriod) {
_invoicePeriod = this.plan.invoicePeriod;
}
const period = new SubscriptionPeriod(_invoiceInterval, _invoicePeriod, start);
static setNewPeriod(invoiceInterval, invoicePeriod, start) {
const period = new SubscriptionPeriod(
invoiceInterval,
invoicePeriod,
start,
);
const startsAt = period.getStartDate();
const endsAt = period.getEndDate();
@@ -159,12 +154,11 @@ export default class PlanSubscription extends mixin(SystemModel) {
* Renews subscription period.
* @Promise
*/
renew(plan) {
const { invoicePeriod, invoiceInterval } = plan;
const patch = { ...this.setNewPeriod(invoiceInterval, invoicePeriod) };
patch.cancelsAt = null;
patch.planId = plan.id;
return this.$query().patch(patch);
renew(invoiceInterval, invoicePeriod) {
const { startsAt, endsAt } = PlanSubscription.setNewPeriod(
invoiceInterval,
invoicePeriod,
);
return this.$query().update({ startsAt, endsAt });
}
}