re-structure to monorepo.

This commit is contained in:
a.bouhuolia
2023-02-03 01:02:31 +02:00
parent 8242ec64ba
commit 7a0a13f9d5
10400 changed files with 46966 additions and 17223 deletions

View File

@@ -0,0 +1,19 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import { Icon } from '@/components';
import '@/style/components/BigcapitalLoading.scss';
/**
* Bigcapital logo loading.
*/
export default function BigcapitalLoading({ className }) {
return (
<div className={classNames('bigcapital-loading', className)}>
<div class="center">
<Icon icon="bigcapital" height={37} width={228} />
</div>
</div>
);
}

View File

@@ -0,0 +1,63 @@
// @ts-nocheck
import React from 'react';
import { Switch, Route } from 'react-router';
import '@/style/pages/Dashboard/Dashboard.scss';
import { Sidebar } from '@/containers/Dashboard/Sidebar/Sidebar';
import DashboardContent from '@/components/Dashboard/DashboardContent';
import DialogsContainer from '@/components/DialogsContainer';
import PreferencesPage from '@/components/Preferences/PreferencesPage';
import DashboardUniversalSearch from '@/containers/UniversalSearch/DashboardUniversalSearch';
import DashboardSplitPane from '@/components/Dashboard/DashboardSplitePane';
import GlobalHotkeys from './GlobalHotkeys';
import DashboardProvider from './DashboardProvider';
import DrawersContainer from '@/components/DrawersContainer';
import AlertsContainer from '@/containers/AlertsContainer';
import EnsureSubscriptionIsActive from '../Guards/EnsureSubscriptionIsActive';
/**
* Dashboard preferences.
*/
function DashboardPreferences() {
return (
<EnsureSubscriptionIsActive>
<DashboardSplitPane>
<Sidebar />
<PreferencesPage />
</DashboardSplitPane>
</EnsureSubscriptionIsActive>
);
}
/**
* Dashboard other routes.
*/
function DashboardAnyPage() {
return (
<DashboardSplitPane>
<Sidebar />
<DashboardContent />
</DashboardSplitPane>
);
}
/**
* Dashboard page.
*/
export default function Dashboard() {
return (
<DashboardProvider>
<Switch>
<Route path="/preferences" component={DashboardPreferences} />
<Route path="/" component={DashboardAnyPage} />
</Switch>
<DashboardUniversalSearch />
<DialogsContainer />
<GlobalHotkeys />
<DrawersContainer />
<AlertsContainer />
</DashboardProvider>
);
}

View File

@@ -0,0 +1,27 @@
// @ts-nocheck
import React from 'react';
import { Ability } from '@casl/ability';
import { createContextualCan } from '@casl/react';
import { useDashboardMetaBoot } from './DashboardBoot';
export const AbilityContext = React.createContext();
export const Can = createContextualCan(AbilityContext.Consumer);
/**
* Dashboard ability provider.
*/
export function DashboardAbilityProvider({ children }) {
const {
meta: { abilities },
} = useDashboardMetaBoot();
// Ability instance.
const ability = new Ability(abilities);
return (
<AbilityContext.Provider value={ability}>
{children}
</AbilityContext.Provider>
);
}

View File

@@ -0,0 +1,69 @@
// @ts-nocheck
import React, { useMemo, useState } from 'react';
import classNames from 'classnames';
import {
Button,
Classes,
MenuItem,
Menu,
Popover,
PopoverInteractionKind,
Position,
Divider,
} from '@blueprintjs/core';
import { FormattedMessage as T } from '@/components';
import { Icon } from '@/components';
/**
* Dashboard action views list.
*/
export function DashboardActionViewsList({
resourceName,
allMenuItem,
allMenuItemText,
views,
onChange,
}) {
const handleClickViewItem = (view) => {
onChange && onChange(view);
};
const viewsMenuItems = views.map((view) => (
<MenuItem onClick={() => handleClickViewItem(view)} text={view.name} />
));
const handleAllTabClick = () => {
handleClickViewItem(null);
};
const content = (
<Menu>
{allMenuItem && (
<>
<MenuItem
onClick={handleAllTabClick}
text={allMenuItemText || 'All'}
/>
<Divider />
</>
)}
{viewsMenuItems}
</Menu>
);
return (
<Popover
content={content}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--table-views')}
icon={<Icon icon="table-16" iconSize={16} />}
text={<T id={'table_views'} />}
rightIcon={'caret-down'}
/>
</Popover>
);
}

View File

@@ -0,0 +1,20 @@
// @ts-nocheck
import React from 'react';
import clsx from 'classnames';
import { Navbar } from '@blueprintjs/core';
export function DashboardActionsBar({ className, children, name }) {
return (
<div
className={clsx(
{
'dashboard__actions-bar': true,
[`dashboard__actions-bar--${name}`]: !!name,
},
className,
)}
>
<Navbar className="navbar--dashboard-actions-bar">{children}</Navbar>
</div>
);
}

View File

@@ -0,0 +1,40 @@
// @ts-nocheck
import React from 'react';
import withBreadcrumbs from 'react-router-breadcrumbs-hoc';
import { useHistory } from 'react-router-dom';
import { getDashboardRoutes } from '@/routes/dashboard';
import { If, Icon } from '@/components';
import { FormattedMessage as T } from '@/components';
import withDashboard from '@/containers/Dashboard/withDashboard';
import { compose } from '@/utils';
function DashboardBackLink({ dashboardBackLink, breadcrumbs }) {
const history = useHistory();
const crumb = breadcrumbs[breadcrumbs.length - 2];
const handleClick = (event) => {
const url =
typeof dashboardBackLink === 'string'
? dashboardBackLink
: crumb.match.url;
history.push(url);
event.preventDefault();
};
return (
<If condition={dashboardBackLink && crumb}>
<div class="dashboard__back-link">
<a href="#no-link" onClick={handleClick}>
<Icon icon={'arrow-left'} iconSize={18} /> <T id={'back_to_list'} />
</a>
</div>
</If>
);
}
export default compose(
withBreadcrumbs([]),
withDashboard(({ dashboardBackLink }) => ({
dashboardBackLink,
})),
)(DashboardBackLink);

View File

@@ -0,0 +1,129 @@
// @ts-nocheck
import React from 'react';
import {
useAuthenticatedAccount,
useCurrentOrganization,
useDashboardMeta,
} from '@/hooks/query';
import { useSplashLoading } from '@/hooks/state';
import { useWatch, useWatchImmediate, useWhen } from '@/hooks';
import { useSubscription } from '@/hooks/state';
import { setCookie, getCookie } from '@/utils';
/**
* Dashboard meta async booting.
* - Fetches the dashboard meta only if the organization subscribe is active.
* - Once the dashboard meta query is loading display dashboard splash screen.
*/
export function useDashboardMetaBoot() {
const { isSubscriptionActive } = useSubscription();
const {
data: dashboardMeta,
isLoading: isDashboardMetaLoading,
isSuccess: isDashboardMetaSuccess,
} = useDashboardMeta({
keepPreviousData: true,
// Avoid run the query if the organization subscription is not active.
enabled: isSubscriptionActive,
});
const [startLoading, stopLoading] = useSplashLoading();
useWatchImmediate((value) => {
value && startLoading();
}, isDashboardMetaLoading);
useWatchImmediate(() => {
isDashboardMetaSuccess && stopLoading();
}, isDashboardMetaSuccess);
return {
meta: dashboardMeta,
isLoading: isDashboardMetaLoading,
isSuccess: isDashboardMetaSuccess,
};
}
/**
* Application async booting.
*/
export function useApplicationBoot() {
// Fetches the current user's organization.
const {
isSuccess: isCurrentOrganizationSuccess,
isLoading: isOrgLoading,
data: organization,
} = useCurrentOrganization();
// Authenticated user.
const { isSuccess: isAuthUserSuccess, isLoading: isAuthUserLoading } =
useAuthenticatedAccount();
// 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]);
const [startLoading, stopLoading] = useSplashLoading();
// Splash loading when organization request loading and
// applicaiton still not booted.
useWatchImmediate((value) => {
value && !isBooted.current && startLoading();
}, isOrgLoading);
// Splash loading when request authenticated user loading and
// application still not booted yet.
useWatchImmediate((value) => {
value && !isBooted.current && startLoading();
}, isAuthUserLoading);
// Stop splash loading once organization request success.
useWatch((value) => {
value && stopLoading();
}, isCurrentOrganizationSuccess);
// Stop splash loading once authenticated user request success.
useWatch((value) => {
value && stopLoading();
}, isAuthUserSuccess);
// Once the all requests complete change the app loading state.
useWhen(
isAuthUserSuccess &&
isCurrentOrganizationSuccess &&
localeCookie === organization?.metadata?.language,
() => {
isBooted.current = true;
},
);
return {
isLoading: isOrgLoading || isAuthUserLoading,
};
}

View File

@@ -0,0 +1,35 @@
// @ts-nocheck
import React from 'react';
import {
CollapsibleList,
MenuItem,
Classes,
Boundary,
} from '@blueprintjs/core';
import withBreadcrumbs from 'react-router-breadcrumbs-hoc';
import { getDashboardRoutes } from '@/routes/dashboard';
import { useHistory } from 'react-router-dom';
function DashboardBreadcrumbs({ breadcrumbs }){
const history = useHistory();
return(
<CollapsibleList
className={Classes.BREADCRUMBS}
dropdownTarget={<span className={Classes.BREADCRUMBS_COLLAPSED} />}
collapseFrom={Boundary.START}
visibleItemCount={0}>
{
breadcrumbs.map(({ breadcrumb,match })=>{
return (<MenuItem
key={match.url}
icon={'folder-close'}
text={breadcrumb}
onClick={() => history.push(match.url) } />)
})
}
</CollapsibleList>
)
}
export default withBreadcrumbs([])(DashboardBreadcrumbs)

View File

@@ -0,0 +1,18 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import { CLASSES } from '@/constants/classes';
// Dashboard card.
export function DashboardCard({ children, page }) {
return (
<div
className={classNames(CLASSES.DASHBOARD_CARD, {
[CLASSES.DASHBOARD_CARD_PAGE]: page,
})}
>
{children}
</div>
);
}

View File

@@ -0,0 +1,17 @@
// @ts-nocheck
import React from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import DashboardTopbar from '@/components/Dashboard/DashboardTopbar';
import DashboardContentRoutes from '@/components/Dashboard/DashboardContentRoute';
import DashboardErrorBoundary from './DashboardErrorBoundary';
export default React.forwardRef(({}, ref) => {
return (
<ErrorBoundary FallbackComponent={DashboardErrorBoundary}>
<div className="dashboard-content" id="dashboard" ref={ref}>
<DashboardTopbar />
<DashboardContentRoutes />
</div>
</ErrorBoundary>
);
});

View File

@@ -0,0 +1,60 @@
// @ts-nocheck
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import { getDashboardRoutes } from '@/routes/dashboard';
import EnsureSubscriptionsIsActive from '../Guards/EnsureSubscriptionsIsActive';
import EnsureSubscriptionsIsInactive from '../Guards/EnsureSubscriptionsIsInactive';
import DashboardPage from './DashboardPage';
/**
* Dashboard inner route content.
*/
function DashboardContentRouteContent({ route }) {
const content = (
<DashboardPage
name={route.name}
Component={route.component}
pageTitle={route.pageTitle}
backLink={route.backLink}
hint={route.hint}
sidebarExpand={route.sidebarExpand}
pageType={route.pageType}
defaultSearchResource={route.defaultSearchResource}
/>
);
return route.subscriptionActive ? (
<EnsureSubscriptionsIsInactive
subscriptionTypes={route.subscriptionActive}
children={content}
redirectTo={'/billing'}
/>
) : route.subscriptionInactive ? (
<EnsureSubscriptionsIsActive
subscriptionTypes={route.subscriptionInactive}
children={content}
redirectTo={'/'}
/>
) : (
content
);
}
/**
* Dashboard content route.
*/
export default function DashboardContentRoute() {
const routes = getDashboardRoutes();
return (
<Route pathname="/">
<Switch>
{routes.map((route, index) => (
<Route exact={route.exact} key={index} path={`${route.path}`}>
<DashboardContentRouteContent route={route} />
</Route>
))}
</Switch>
</Route>
);
}

View File

@@ -0,0 +1,13 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import { CLASSES } from '@/constants/classes';
/**
* Dashboard content table.
*/
export function DashboardContentTable({ children }) {
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>{children}</div>
);
}

View File

@@ -0,0 +1,13 @@
// @ts-nocheck
import React from 'react';
import { FormattedMessage as T, Icon } from '@/components';
export default function DashboardErrorBoundary({}) {
return (
<div class="dashboard__error-boundary">
<h1><T id={'sorry_about_that_something_went_wrong'} /></h1>
<p><T id={'if_the_problem_stuck_please_contact_us_as_soon_as_possible'} /></p>
<Icon icon="bigcapital" height={30} width={160} />
</div>
)
}

View File

@@ -0,0 +1,27 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import intl from "react-intl-universal";
import { Classes, Button } from '@blueprintjs/core';
import { T, Icon } from '@/components';
/**
* Dashboard advanced filter button.
*/
export function DashboardFilterButton({ conditionsCount }) {
return (
<Button
className={classNames(Classes.MINIMAL, 'button--filter', {
'has-active-filters': conditionsCount > 0,
})}
text={
conditionsCount > 0 ? (
intl.get('count_filters_applied', { count: conditionsCount })
) : (
<T id={'filter'} />
)
}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
);
}

View File

@@ -0,0 +1,26 @@
// @ts-nocheck
import React from 'react';
import { getFooterLinks } from '@/constants/footerLinks';
import { For } from '@/components';
function FooterLinkItem({ title, link }) {
return (
<div class="">
<a href={link} target="_blank" rel="noopener noreferrer">
{title}
</a>
</div>
);
}
export default function DashboardFooter() {
const footerLinks = getFooterLinks();
return (
<div class="dashboard__footer">
<div class="footer-links">
<For render={FooterLinkItem} of={footerLinks} />
</div>
</div>
);
}

View File

@@ -0,0 +1,29 @@
// @ts-nocheck
import React from 'react';
import classnames from 'classnames';
import { LoadingIndicator } from '../Indicator';
export function DashboardInsider({
loading,
children,
name,
mount = false,
className,
}) {
return (
<div
className={classnames(
{
dashboard__insider: true,
'dashboard__insider--loading': loading,
[`dashboard__insider--${name}`]: !!name,
},
className,
)}
>
<LoadingIndicator loading={loading} mount={mount}>
{children}
</LoadingIndicator>
</div>
);
}

View File

@@ -0,0 +1,25 @@
// @ts-nocheck
import React from 'react';
import { Choose } from '@/components';
import BigcapitalLoading from './BigcapitalLoading';
/**
* Dashboard loading indicator.
*/
export default function DashboardLoadingIndicator({
isLoading = false,
className,
children,
}) {
return (
<Choose>
<Choose.When condition={isLoading}>
<BigcapitalLoading />
</Choose.When>
<Choose.Otherwise>
{ children }
</Choose.Otherwise>
</Choose>
);
}

View File

@@ -0,0 +1,105 @@
// @ts-nocheck
import React, { useEffect, Suspense } from 'react';
import { CLASSES } from '@/constants/classes';
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
import { compose } from '@/utils';
import { Spinner } from '@blueprintjs/core';
import withUniversalSearchActions from '@/containers/UniversalSearch/withUniversalSearchActions';
/**
* Dashboard pages wrapper.
*/
function DashboardPage({
// #ownProps
pageTitle,
backLink,
sidebarExpand = true,
Component,
name,
hint,
defaultSearchResource,
// #withDashboardActions
changePageTitle,
setDashboardBackLink,
changePageHint,
toggleSidebarExpand,
// #withUniversalSearch
setResourceTypeUniversalSearch,
resetResourceTypeUniversalSearch,
}) {
// Hydrate the given page title.
useEffect(() => {
pageTitle && changePageTitle(pageTitle);
return () => {
pageTitle && changePageTitle('');
};
});
// Hydrate the given page hint.
useEffect(() => {
hint && changePageHint(hint);
return () => {
hint && changePageHint('');
};
}, [hint, changePageHint]);
// Hydrate the dashboard back link status.
useEffect(() => {
backLink && setDashboardBackLink(backLink);
return () => {
backLink && setDashboardBackLink(false);
};
}, [backLink, setDashboardBackLink]);
useEffect(() => {
const className = `page-${name}`;
name && document.body.classList.add(className);
return () => {
name && document.body.classList.remove(className);
};
}, [name]);
useEffect(() => {
toggleSidebarExpand(sidebarExpand);
}, [toggleSidebarExpand, sidebarExpand]);
useEffect(() => {
if (defaultSearchResource) {
setResourceTypeUniversalSearch(defaultSearchResource);
}
return () => {
resetResourceTypeUniversalSearch();
};
}, [
defaultSearchResource,
resetResourceTypeUniversalSearch,
setResourceTypeUniversalSearch,
]);
return (
<div className={CLASSES.DASHBOARD_PAGE}>
<Suspense
fallback={
<div class="dashboard__fallback-loading">
<Spinner size={40} value={null} />
</div>
}
>
<Component />
</Suspense>
</div>
);
}
export default compose(
withDashboardActions,
// withUniversalSearch,
withUniversalSearchActions,
)(DashboardPage);

View File

@@ -0,0 +1,9 @@
// @ts-nocheck
import React from 'react';
/**
* Dashboard page content.
*/
export function DashboardPageContent({ children }) {
return <div class="dashboard__page-content">{children}</div>;
}

View File

@@ -0,0 +1,17 @@
// @ts-nocheck
import React from 'react';
import { DashboardAbilityProvider } from '../../components';
import { useDashboardMetaBoot } from './DashboardBoot';
/**
* Dashboard provider.
*/
export default function DashboardProvider({ children }) {
const { isLoading } = useDashboardMetaBoot();
// Avoid display any dashboard component before complete booting.
if (isLoading) {
return null;
}
return <DashboardAbilityProvider>{children}</DashboardAbilityProvider>;
}

View File

@@ -0,0 +1,71 @@
// @ts-nocheck
import React from 'react';
import {
Button,
PopoverInteractionKind,
Popover,
Menu,
MenuItem,
MenuDivider,
Classes,
Tooltip,
Position,
} from '@blueprintjs/core';
import clsx from 'classnames';
import { Icon, T } from '@/components';
import Style from './style.module.scss';
/**
* Dashboard rows height button control.
*/
export function DashboardRowsHeightButton({ initialValue, value, onChange }) {
const [localSize, setLocalSize] = React.useState(initialValue);
// Handle menu item click.
const handleItemClick = (size) => (event) => {
setLocalSize(size);
onChange && onChange(size, event);
};
// Button icon name.
const btnIcon = `table-row-${localSize}`;
return (
<Popover
minimal={true}
content={
<Menu className={Style.menu}>
<MenuDivider title={<T id={'dashboard.rows_height'} />} />
<MenuItem
onClick={handleItemClick('small')}
text={<T id={'dashboard.row_small'} />}
/>
<MenuItem
onClick={handleItemClick('medium')}
text={<T id={'dashboard.row_medium'} />}
/>
</Menu>
}
placement="bottom-start"
modifiers={{
offset: { offset: '0, 4' },
}}
interactionKind={PopoverInteractionKind.CLICK}
>
<Tooltip
content={<T id={'dashboard.rows_height'} />}
minimal={true}
position={Position.BOTTOM}
>
<Button
className={clsx(Classes.MINIMAL, Style.button)}
icon={<Icon icon={btnIcon} iconSize={16} />}
/>
</Tooltip>
</Popover>
);
}
DashboardRowsHeightButton.defaultProps = {
initialValue: 'medium',
};

View File

@@ -0,0 +1,12 @@
.menu{
:global .bp3-heading{
font-weight: 400;
opacity: 0.5;
font-size: 12px;
}
}
.button{
min-width: 34px;
}

View File

@@ -0,0 +1,45 @@
// @ts-nocheck
import React, { useState, useRef } from 'react';
import SplitPane from 'react-split-pane';
import { debounce } from 'lodash';
import withDashboard from '@/containers/Dashboard/withDashboard';
import { compose } from '@/utils';
function DashboardSplitPane({
sidebarExpended,
children
}) {
const initialSize = 220;
const [defaultSize, setDefaultSize] = useState(
parseInt(localStorage.getItem('dashboard-size'), 10) || initialSize,
);
const debounceSaveSize = useRef(
debounce((size) => {
localStorage.setItem('dashboard-size', size);
}, 500),
);
const handleChange = (size) => {
debounceSaveSize.current(size);
setDefaultSize(size);
}
return (
<SplitPane
allowResize={sidebarExpended}
split="vertical"
minSize={180}
maxSize={300}
defaultSize={sidebarExpended ? defaultSize : 50}
size={sidebarExpended ? defaultSize : 50}
onChange={handleChange}
className="primary"
>
{children}
</SplitPane>
);
}
export default compose(
withDashboard(({ sidebarExpended }) => ({ sidebarExpended }))
)(DashboardSplitPane);

View File

@@ -0,0 +1,23 @@
// @ts-nocheck
import React from 'react';
import { ThemeProvider, StyleSheetManager } from 'styled-components';
import rtlcss from 'stylis-rtlcss';
import { useAppIntlContext } from '../AppIntlProvider';
interface DashboardThemeProviderProps {
children: React.ReactNode;
}
export function DashboardThemeProvider({
children,
}: DashboardThemeProviderProps) {
const { direction } = useAppIntlContext();
return (
<StyleSheetManager
{...(direction === 'rtl' ? { stylisPlugins: [rtlcss] } : {})}
>
<ThemeProvider theme={{ dir: direction }}>{children}</ThemeProvider>
</StyleSheetManager>
);
}

View File

@@ -0,0 +1,216 @@
// @ts-nocheck
import React from 'react';
import { useHistory } from 'react-router';
import {
Navbar,
NavbarGroup,
NavbarDivider,
Button,
Classes,
Tooltip,
Position,
} from '@blueprintjs/core';
import { FormattedMessage as T } from '@/components';
import DashboardTopbarUser from '@/components/Dashboard/TopbarUser';
import DashboardBreadcrumbs from '@/components/Dashboard/DashboardBreadcrumbs';
import DashboardBackLink from '@/components/Dashboard/DashboardBackLink';
import { Icon, Hint, If } from '@/components';
import withUniversalSearchActions from '@/containers/UniversalSearch/withUniversalSearchActions';
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
import withDashboard from '@/containers/Dashboard/withDashboard';
import QuickNewDropdown from '@/containers/QuickNewDropdown/QuickNewDropdown';
import { compose } from '@/utils';
import withSubscriptions from '@/containers/Subscriptions/withSubscriptions';
import { useGetUniversalSearchTypeOptions } from '@/containers/UniversalSearch/utils';
function DashboardTopbarSubscriptionMessage() {
return (
<div class="dashboard__topbar-subscription-msg">
<span>
<T id={'dashboard.subscription_msg.period_over'} />
</span>
</div>
);
}
function DashboardHamburgerButton({ ...props }) {
return (
<Button minimal={true} {...props}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
role="img"
focusable="false"
>
<title>
<T id={'menu'} />
</title>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-miterlimit="5"
stroke-width="2"
d="M4 7h15M4 12h15M4 17h15"
></path>
</svg>
</Button>
);
}
/**
* Dashboard topbar.
*/
function DashboardTopbar({
// #withDashboard
pageTitle,
editViewId,
pageHint,
// #withDashboardActions
toggleSidebarExpand,
// #withDashboard
sidebarExpended,
// #withGlobalSearch
openGlobalSearch,
// #withSubscriptions
isSubscriptionActive,
isSubscriptionInactive,
}) {
const history = useHistory();
const handlerClickEditView = () => {
history.push(`/custom_views/${editViewId}/edit`);
};
const handleSidebarToggleBtn = () => {
toggleSidebarExpand();
};
return (
<div class="dashboard__topbar">
<div class="dashboard__topbar-left">
<div class="dashboard__topbar-sidebar-toggle">
<Tooltip
content={
!sidebarExpended ? (
<T id={'open_sidebar'} />
) : (
<T id={'close_sidebar'} />
)
}
position={Position.RIGHT}
>
<DashboardHamburgerButton onClick={handleSidebarToggleBtn} />
</Tooltip>
</div>
<div class="dashboard__title">
<h1>{pageTitle}</h1>
<If condition={pageHint}>
<div class="dashboard__hint">
<Hint content={pageHint} />
</div>
</If>
<If condition={editViewId}>
<Button
className={Classes.MINIMAL + ' button--view-edit'}
icon={<Icon icon="pen" iconSize={13} />}
onClick={handlerClickEditView}
/>
</If>
</div>
<div class="dashboard__breadcrumbs">
<DashboardBreadcrumbs />
</div>
<DashboardBackLink />
</div>
<div class="dashboard__topbar-right">
<If condition={isSubscriptionInactive}>
<DashboardTopbarSubscriptionMessage />
</If>
<Navbar class="dashboard__topbar-navbar">
<NavbarGroup>
<If condition={isSubscriptionActive}>
<DashboardQuickSearchButton
onClick={() => openGlobalSearch(true)}
/>
<QuickNewDropdown />
<Tooltip
content={<T id={'notifications'} />}
position={Position.BOTTOM}
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'notification-24'} iconSize={20} />}
/>
</Tooltip>
</If>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'help-24'} iconSize={20} />}
text={<T id={'help'} />}
/>
<NavbarDivider />
</NavbarGroup>
</Navbar>
<div class="dashboard__topbar-user">
<DashboardTopbarUser />
</div>
</div>
</div>
);
}
export default compose(
withUniversalSearchActions,
withDashboard(({ pageTitle, pageHint, editViewId, sidebarExpended }) => ({
pageTitle,
editViewId,
sidebarExpended,
pageHint,
})),
withDashboardActions,
withSubscriptions(
({ isSubscriptionActive, isSubscriptionInactive }) => ({
isSubscriptionActive,
isSubscriptionInactive,
}),
'main',
),
)(DashboardTopbar);
/**
* Dashboard quick search button.
*/
function DashboardQuickSearchButton({ ...rest }) {
const searchTypeOptions = useGetUniversalSearchTypeOptions();
// Can't continue if there is no any search type option.
if (searchTypeOptions.length <= 0) {
return null;
}
return (
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'search-24'} iconSize={20} />}
text={<T id={'quick_find'} />}
{...rest}
/>
);
}

View File

@@ -0,0 +1,104 @@
// @ts-nocheck
import React, { useRef, useState, useEffect } from 'react';
import { FormattedMessage as T } from '@/components';
import PropTypes from 'prop-types';
import { Button, Tabs, Tab, Tooltip, Position } from '@blueprintjs/core';
import { useHistory } from 'react-router';
import { debounce } from 'lodash';
import { If, Icon } from '@/components';
import { saveInvoke } from '@/utils';
/**
* Dashboard views tabs.
*/
export function DashboardViewsTabs({
initialViewSlug = 0,
currentViewSlug,
tabs,
defaultTabText = <T id={'all'} />,
allTab = true,
newViewTab = true,
resourceName,
onNewViewTabClick,
onChange,
OnThrottledChange,
throttleTime = 250,
}) {
const history = useHistory();
const [currentView, setCurrentView] = useState(initialViewSlug || 0);
useEffect(() => {
if (
typeof currentViewSlug !== 'undefined' &&
currentViewSlug !== currentView
) {
setCurrentView(currentViewSlug || 0);
}
}, [currentView, setCurrentView, currentViewSlug]);
const throttledOnChange = useRef(
debounce((viewId) => saveInvoke(OnThrottledChange, viewId), throttleTime),
);
// Trigger `onChange` and `onThrottledChange` events.
const triggerOnChange = (viewSlug) => {
const value = viewSlug === 0 ? null : viewSlug;
saveInvoke(onChange, value);
throttledOnChange.current(value);
};
// Handles click a new view.
const handleClickNewView = () => {
history.push(`/custom_views/${resourceName}/new`);
onNewViewTabClick && onNewViewTabClick();
};
// Handle tabs change.
const handleTabsChange = (viewSlug) => {
setCurrentView(viewSlug);
triggerOnChange(viewSlug);
};
return (
<div class="dashboard__views-tabs">
<Tabs
id="navbar"
large={true}
selectedTabId={currentView}
className="tabs--dashboard-views"
onChange={handleTabsChange}
animate={false}
>
{allTab && <Tab id={0} title={defaultTabText} />}
{tabs.map((tab) => (
<Tab id={tab.slug} title={tab.name} />
))}
<If condition={newViewTab}>
<Tooltip
content={<T id={'create_a_new_view'} />}
position={Position.RIGHT}
>
<Button
className="button--new-view"
icon={<Icon icon="plus" />}
onClick={handleClickNewView}
minimal={true}
/>
</Tooltip>
</If>
</Tabs>
</div>
);
}
DashboardViewsTabs.propTypes = {
tabs: PropTypes.array.isRequired,
allTab: PropTypes.bool,
newViewTab: PropTypes.bool,
onNewViewTabClick: PropTypes.func,
onChange: PropTypes.func,
OnThrottledChange: PropTypes.func,
throttleTime: PropTypes.number,
};

View File

@@ -0,0 +1,46 @@
// @ts-nocheck
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useHistory } from 'react-router-dom';
import { getDashboardRoutes } from '@/routes/dashboard';
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import { compose } from '@/utils';
function GlobalHotkeys({
// #withDashboardActions
toggleSidebarExpend,
// #withDialogActions
openDialog,
}) {
const history = useHistory();
const routes = getDashboardRoutes();
const globalHotkeys = routes
.filter(({ hotkey }) => hotkey)
.map(({ hotkey }) => hotkey)
.toString();
const handleSidebarToggleBtn = () => {
toggleSidebarExpend();
};
useHotkeys(
globalHotkeys,
(event, handle) => {
routes.map(({ path, hotkey }) => {
if (handle.key === hotkey) {
history.push(path);
}
});
},
[history],
);
useHotkeys('ctrl+/', (event, handle) => handleSidebarToggleBtn());
useHotkeys('shift+d', (event, handle) => openDialog('money-in', {}));
useHotkeys('shift+q', (event, handle) => openDialog('money-out', {}));
return <div></div>;
}
export default compose(withDashboardActions, withDialogActions)(GlobalHotkeys);

View File

@@ -0,0 +1,35 @@
// @ts-nocheck
import React from 'react';
import { Switch, Route } from 'react-router';
import Dashboard from '@/components/Dashboard/Dashboard';
import SetupWizardPage from '@/containers/Setup/WizardSetupPage';
import EnsureOrganizationIsReady from '../Guards/EnsureOrganizationIsReady';
import EnsureOrganizationIsNotReady from '../Guards/EnsureOrganizationIsNotReady';
import { PrivatePagesProvider } from './PrivatePagesProvider';
import '@/style/pages/Dashboard/Dashboard.scss';
/**
* Dashboard inner private pages.
*/
export default function DashboardPrivatePages() {
return (
<PrivatePagesProvider>
<Switch>
<Route path={'/setup'}>
<EnsureOrganizationIsNotReady>
<SetupWizardPage />
</EnsureOrganizationIsNotReady>
</Route>
<Route path="/">
<EnsureOrganizationIsReady>
<Dashboard />
</EnsureOrganizationIsReady>
</Route>
</Switch>
</PrivatePagesProvider>
);
}

View File

@@ -0,0 +1,15 @@
// @ts-nocheck
import React from 'react';
import { useApplicationBoot } from '@/components';
/**
* Private pages provider.
*/
export function PrivatePagesProvider({
// #ownProps
children,
}) {
const { isLoading } = useApplicationBoot();
return <React.Fragment>{!isLoading ? children : null}</React.Fragment>;
}

View File

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

View File

@@ -0,0 +1,94 @@
// @ts-nocheck
import React from 'react';
import { useHistory } from 'react-router-dom';
import {
Menu,
MenuItem,
MenuDivider,
Button,
Popover,
Position,
} from '@blueprintjs/core';
import { If, FormattedMessage as T } from '@/components';
import { firstLettersArgs } from '@/utils';
import { useAuthActions } from '@/hooks/state';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import withSubscriptions from '@/containers/Subscriptions/withSubscriptions';
import { useAuthenticatedAccount } from '@/hooks/query';
import { compose } from '@/utils';
/**
* Dashboard topbar user.
*/
function DashboardTopbarUser({
openDialog,
// #withSubscriptions
isSubscriptionActive,
}) {
const history = useHistory();
const { setLogout } = useAuthActions();
// Retrieve authenticated user information.
const { data: user } = useAuthenticatedAccount();
const onClickLogout = () => {
setLogout();
};
const onKeyboardShortcut = () => {
openDialog('keyboard-shortcuts');
};
return (
<Popover
content={
<Menu className={'menu--logged-user-dropdown'}>
<MenuItem
multiline={true}
className={'menu-item--profile'}
text={
<div>
<div class="person">
{user.first_name} {user.last_name}
</div>
<div class="org">
<T id="organization_id" />: {user.tenant_id}
</div>
</div>
}
/>
<MenuDivider />
<If condition={isSubscriptionActive}>
<MenuItem
text={<T id={'keyboard_shortcuts'} />}
onClick={onKeyboardShortcut}
/>
<MenuItem
text={<T id={'preferences'} />}
onClick={() => history.push('/preferences')}
/>
</If>
<MenuItem text={<T id={'logout'} />} onClick={onClickLogout} />
</Menu>
}
position={Position.BOTTOM}
>
<Button>
<div className="user-text">
{firstLettersArgs(user.first_name, user.last_name)}
</div>
</Button>
</Popover>
);
}
export default compose(
withDialogActions,
withSubscriptions(
({ isSubscriptionActive }) => ({ isSubscriptionActive }),
'main',
),
)(DashboardTopbarUser);

View File

@@ -0,0 +1,14 @@
// @ts-nocheck
export * from './SplashScreen';
export * from './DashboardBoot';
export * from './DashboardThemeProvider';
export * from './DashboardAbilityProvider';
export * from './DashboardCard'
export * from './DashboardActionsBar'
export * from './DashboardFilterButton'
export * from './DashboardRowsHeightButton'
export * from './DashboardViewsTabs'
export * from './DashboardActionViewsList'
export * from './DashboardContentTable'
export * from './DashboardPageContent'
export * from './DashboardInsider'