feat: ensure to access dashboard user's subscription is active.

This commit is contained in:
a.bouhuolia
2021-08-30 10:42:39 +02:00
parent 47da64e625
commit 9dca9f3317
59 changed files with 1299 additions and 546 deletions

View File

@@ -2,15 +2,15 @@ import intl from 'react-intl-universal';
export const getSetupWizardSteps = () => [
{
label: intl.get('Plans & Payment'),
label: intl.get('setup.plan.plans'),
},
{
label: intl.get('Initializing'),
label: intl.get('setup.plan.initializing'),
},
{
label: intl.get('Getting started'),
label: intl.get('setup.plan.getting_started'),
},
{
label: intl.get('Congratulations'),
label: intl.get('setup.plan.congrats'),
},
];

View File

@@ -0,0 +1,17 @@
import React from 'react';
import clsx from 'classnames';
import Style from './style.module.scss';
export function Alert({ title, description, intent }) {
return (
<div
className={clsx(Style.root, {
[`${Style['root_' + intent]}`]: intent,
})}
>
{title && <h3 className={clsx(Style.title)}>{title}</h3>}
{description && <p class={clsx(Style.description)}>{description}</p>}
</div>
);
}

View File

@@ -0,0 +1,32 @@
.root {
border: 1px solid rgb(223, 227, 230);
padding: 12px;
border-radius: 6px;
margin-bottom: 20px;
&_danger {
border-color: rgb(249, 198, 198);
background: rgb(255, 248, 248);
.description {
color: #d95759;
}
.title {
color: rgb(205, 43, 49);
}
}
}
.title {
color: rgb(17, 24, 28);
margin-bottom: 4px;
font-size: 14px;
font-weight: 600;
}
.description {
color: rgb(104, 112, 118);
margin: 0;
}

View File

@@ -12,6 +12,33 @@ import DashboardSplitPane from 'components/Dashboard/DashboardSplitePane';
import GlobalHotkeys from './GlobalHotkeys';
import DashboardProvider from './DashboardProvider';
import DrawersContainer from 'components/DrawersContainer';
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.
@@ -20,19 +47,8 @@ export default function Dashboard() {
return (
<DashboardProvider>
<Switch>
<Route path="/preferences">
<DashboardSplitPane>
<Sidebar />
<PreferencesPage />
</DashboardSplitPane>
</Route>
<Route path="/">
<DashboardSplitPane>
<Sidebar />
<DashboardContent />
</DashboardSplitPane>
</Route>
<Route path="/preferences" component={DashboardPreferences} />
<Route path="/" component={DashboardAnyPage} />
</Switch>
<DashboardUniversalSearch />

View File

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

View File

@@ -2,8 +2,43 @@ 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.
*/
@@ -14,21 +49,8 @@ export default function DashboardContentRoute() {
<Route pathname="/">
<Switch>
{routes.map((route, index) => (
<Route
exact={route.exact}
key={index}
path={`${route.path}`}
>
<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}
/>
<Route exact={route.exact} key={index} path={`${route.path}`}>
<DashboardContentRouteContent route={route} />
</Route>
))}
</Switch>

View File

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

View File

@@ -23,6 +23,43 @@ import withSettings from 'containers/Settings/withSettings';
import QuickNewDropdown from 'containers/QuickNewDropdown/QuickNewDropdown';
import { compose } from 'utils';
import withSubscriptions from '../../containers/Subscriptions/withSubscriptions';
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.
@@ -44,6 +81,10 @@ function DashboardTopbar({
// #withGlobalSearch
openGlobalSearch,
// #withSubscriptions
isSubscriptionActive,
isSubscriptionInactive,
}) {
const history = useHistory();
@@ -69,27 +110,7 @@ function DashboardTopbar({
}
position={Position.RIGHT}
>
<Button minimal={true} onClick={handleSidebarToggleBtn}>
<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>
<DashboardHamburgerButton onClick={handleSidebarToggleBtn} />
</Tooltip>
</div>
@@ -114,29 +135,36 @@ function DashboardTopbar({
<div class="dashboard__breadcrumbs">
<DashboardBreadcrumbs />
</div>
<DashboardBackLink />
</div>
<div class="dashboard__topbar-right">
<If condition={isSubscriptionInactive}>
<DashboardTopbarSubscriptionMessage />
</If>
<Navbar class="dashboard__topbar-navbar">
<NavbarGroup>
<Button
onClick={() => openGlobalSearch(true)}
className={Classes.MINIMAL}
icon={<Icon icon={'search-24'} iconSize={20} />}
text={<T id={'quick_find'} />}
/>
<QuickNewDropdown />
<Tooltip
content={<T id={'notifications'} />}
position={Position.BOTTOM}
>
<If condition={isSubscriptionActive}>
<Button
onClick={() => openGlobalSearch(true)}
className={Classes.MINIMAL}
icon={<Icon icon={'notification-24'} iconSize={20} />}
icon={<Icon icon={'search-24'} iconSize={20} />}
text={<T id={'quick_find'} />}
/>
</Tooltip>
<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} />}
@@ -166,4 +194,11 @@ export default compose(
organizationName: organizationSettings.name,
})),
withDashboardActions,
withSubscriptions(
({ isSubscriptionActive, isSubscriptionInactive }) => ({
isSubscriptionActive,
isSubscriptionInactive,
}),
'main',
),
)(DashboardTopbar);

View File

@@ -8,15 +8,21 @@ import {
Popover,
Position,
} from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import { If, FormattedMessage as T } from 'components';
import { firstLettersArgs } from 'utils';
import { useAuthActions, useAuthUser } from 'hooks/state';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
import withSubscriptions from '../../containers/Subscriptions/withSubscriptions';
function DashboardTopbarUser({ openDialog }) {
function DashboardTopbarUser({
openDialog,
// #withSubscriptions
isSubscriptionActive
}) {
const history = useHistory();
const { setLogout } = useAuthActions();
const user = useAuthUser();
@@ -48,14 +54,16 @@ function DashboardTopbarUser({ openDialog }) {
}
/>
<MenuDivider />
<MenuItem
text={<T id={'keyboard_shortcuts'} />}
onClick={onKeyboardShortcut}
/>
<MenuItem
text={<T id={'preferences'} />}
onClick={() => history.push('/preferences')}
/>
<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>
}
@@ -69,4 +77,10 @@ function DashboardTopbarUser({ openDialog }) {
</Popover>
);
}
export default compose(withDialogActions)(DashboardTopbarUser);
export default compose(
withDialogActions,
withSubscriptions(
({ isSubscriptionActive }) => ({ isSubscriptionActive }),
'main',
),
)(DashboardTopbarUser);

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { includes } from 'lodash';
import { compose } from 'utils';
import { Redirect } from 'react-router-dom';
import withSubscriptions from '../../containers/Subscriptions/withSubscriptions';
/**
* Ensures the given subscription type is active or redirect to the given route.
*/
function EnsureSubscriptionIsActive({
children,
subscriptionType = 'main',
redirectTo = '/billing',
routePath,
exclude,
isSubscriptionActive,
}) {
return isSubscriptionActive || includes(exclude, routePath) ? (
children
) : (
<Redirect to={{ pathname: redirectTo }} />
);
}
export default compose(
withSubscriptions(
({ isSubscriptionActive }) => ({ isSubscriptionActive }),
'main',
),
)(EnsureSubscriptionIsActive);

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { includes } from 'lodash';
import { compose } from 'utils';
import { Redirect } from 'react-router-dom';
import withSubscriptions from '../../containers/Subscriptions/withSubscriptionss';
/**
* Ensures the given subscription type is active or redirect to the given route.
*/
function EnsureSubscriptionsIsActive({
children,
subscriptionType = 'main',
redirectTo = '/billing',
routePath,
exclude,
isSubscriptionsActive,
}) {
return !isSubscriptionsActive || includes(exclude, routePath) ? (
children
) : (
<Redirect to={{ pathname: redirectTo }} />
);
}
export default compose(
withSubscriptions(
({ isSubscriptionsActive }) => ({ isSubscriptionsActive }),
'main',
),
)(EnsureSubscriptionsIsActive);

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { includes } from 'lodash';
import { compose } from 'utils';
import { Redirect } from 'react-router-dom';
import withSubscriptions from '../../containers/Subscriptions/withSubscriptionss';
/**
* Ensures the given subscription type is active or redirect to the given route.
*/
function EnsureSubscriptionsIsInactive({
children,
subscriptionType = 'main',
redirectTo = '/billing',
routePath,
exclude,
isSubscriptionsInactive,
}) {
return !isSubscriptionsInactive || includes(exclude, routePath) ? (
children
) : (
<Redirect to={{ pathname: redirectTo }} />
);
}
export default compose(
withSubscriptions(
({ isSubscriptionsInactive }) => ({ isSubscriptionsInactive }),
'main',
),
)(EnsureSubscriptionsIsInactive);

View File

@@ -1,22 +1,20 @@
import React from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import preferencesRoutes from 'routes/preferences'
import { Route, Switch } from 'react-router-dom';
import preferencesRoutes from 'routes/preferences';
export default function DashboardContentRoute() {
return (
<Route pathname="/preferences">
<Switch>
{ preferencesRoutes.map((route, index) => (
{preferencesRoutes.map((route, index) => (
<Route
key={index}
path={`${route.path}`}
exact={route.exact}
component={route.component}
/>
/>
))}
</Switch>
</Route>
);
}
}

View File

@@ -5,6 +5,7 @@ import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withDashboard from 'containers/Dashboard/withDashboard';
import { compose } from 'utils';
import withSubscriptions from '../../containers/Subscriptions/withSubscriptions';
function SidebarContainer({
// #ownProps
@@ -15,6 +16,9 @@ function SidebarContainer({
// #withDashboard
sidebarExpended,
// #withSubscription
isSubscriptionActive,
}) {
const sidebarScrollerRef = React.useRef();
@@ -30,8 +34,8 @@ function SidebarContainer({
}, [sidebarExpended]);
const handleSidebarMouseLeave = () => {
if (!sidebarExpended && sidebarScrollerRef.current) {
sidebarScrollerRef.current.scrollTo({ top: 0, left: 0, });
if (!sidebarExpended && sidebarScrollerRef.current) {
sidebarScrollerRef.current.scrollTo({ top: 0, left: 0 });
}
};
@@ -43,6 +47,7 @@ function SidebarContainer({
<div
className={classNames('sidebar', {
'sidebar--mini-sidebar': !sidebarExpended,
'is-subscription-inactive': !isSubscriptionActive,
})}
id="sidebar"
onMouseLeave={handleSidebarMouseLeave}
@@ -64,4 +69,8 @@ export default compose(
withDashboard(({ sidebarExpended }) => ({
sidebarExpended,
})),
withSubscriptions(
({ isSubscriptionActive }) => ({ isSubscriptionActive }),
'main',
),
)(SidebarContainer);

View File

@@ -8,6 +8,8 @@ import MenuItem from 'components/MenuItem';
import { MenuItemLabel } from 'components';
import classNames from 'classnames';
import SidebarOverlay from 'components/SidebarOverlay';
import { compose } from 'redux';
import withSubscriptions from '../../containers/Subscriptions/withSubscriptions';
const DEFAULT_ITEM = {
text: '',
@@ -19,12 +21,10 @@ function matchPath(pathname, path, matchExact) {
}
function SidebarMenuItemSpace({ space }) {
return (
<div class="bp3-menu-spacer" style={{ height: `${space}px` }} />
)
return <div class="bp3-menu-spacer" style={{ height: `${space}px` }} />;
}
export default function SidebarMenu() {
function SidebarMenu({ isSubscriptionActive }) {
const history = useHistory();
const location = useLocation();
@@ -92,7 +92,11 @@ export default function SidebarMenu() {
);
});
};
const items = menuItemsMapper(sidebarMenuList);
const filterItems = sidebarMenuList.filter(
(item) => isSubscriptionActive || item.enableBilling,
);
const items = menuItemsMapper(filterItems);
const handleSidebarOverlayClose = () => {
setIsOpen(false);
@@ -110,3 +114,10 @@ export default function SidebarMenu() {
</div>
);
}
export default compose(
withSubscriptions(
({ isSubscriptionActive }) => ({ isSubscriptionActive }),
'main',
),
)(SidebarMenu);

View File

@@ -0,0 +1,135 @@
import React from 'react';
import classNames from 'classnames';
import { T } from 'components';
import { saveInvoke } from 'utils';
import 'style/pages/Subscription/PlanRadio.scss';
import 'style/pages/Subscription/PlanPeriodRadio.scss';
export function SubscriptionPlans({ value, plans, onSelect }) {
const handleSelect = (value) => {
onSelect && onSelect(value);
};
return (
<div className={'plan-radios'}>
{plans.map((plan) => (
<SubscriptionPlan
name={plan.name}
description={plan.description}
slug={plan.slug}
price={plan.price}
currencyCode={plan.currencyCode}
value={plan.slug}
onSelected={handleSelect}
selectedOption={value}
/>
))}
</div>
);
}
export function SubscriptionPlan({
name,
description,
price,
currencyCode,
value,
selectedOption,
onSelected,
}) {
const handlePlanClick = () => {
saveInvoke(onSelected, value);
};
return (
<div
id={'basic-plan'}
className={classNames('plan-radio', {
'is-selected': selectedOption === value,
})}
onClick={handlePlanClick}
>
<div className={'plan-radio__header'}>
<div className={'plan-radio__name'}>{name}</div>
</div>
<div className={'plan-radio__description'}>
<ul>
{description.map((line) => (
<li>{line}</li>
))}
</ul>
</div>
<div className={'plan-radio__price'}>
<span className={'plan-radio__amount'}>
{price} {currencyCode}
</span>
<span className={'plan-radio__period'}>
<T id={'monthly'} />
</span>
</div>
</div>
);
}
/**
* Subscription periods.
*/
export function SubscriptionPeriods({ periods, selectedPeriod, onPeriodSelect }) {
const handleSelected = (value) => {
saveInvoke(onPeriodSelect, value);
};
return (
<div className={'plan-periods'}>
{periods.map((period) => (
<SubscriptionPeriod
period={period.slug}
label={period.label}
onSelected={handleSelected}
price={period.price}
selectedPeriod={selectedPeriod}
/>
))}
</div>
);
}
/**
* Billing period.
*/
export function SubscriptionPeriod({
// #ownProps
label,
selectedPeriod,
onSelected,
period,
price,
currencyCode,
}) {
const handlePeriodClick = () => {
saveInvoke(onSelected, period);
};
return (
<div
id={`plan-period-${period}`}
className={classNames(
{ 'is-selected': period === selectedPeriod },
'period-radio',
)}
onClick={handlePeriodClick}
>
<span className={'period-radio__label'}>{label}</span>
<div className={'period-radio__price'}>
<span className={'period-radio__amount'}>
{price} {currencyCode}
</span>
<span className={'period-radio__period'}>{label}</span>
</div>
</div>
);
}

View File

@@ -72,6 +72,8 @@ export * from './Details';
export * from './Drawer/DrawerInsider';
export * from './Drawer/DrawerMainTabs';
export * from './TotalLines/index'
export * from './Alert';
export * from './Subscriptions';
const Hint = FieldHint;

View File

@@ -298,6 +298,7 @@ export default [
},
{
text: <T id={'system'} />,
enableBilling: true,
label: true,
},
{
@@ -307,5 +308,6 @@ export default [
{
text: <T id={'billing'} />,
href: '/billing',
enableBilling: true,
},
];

View File

@@ -1,9 +1,10 @@
import React from 'react';
import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import * as Yup from 'yup';
import { useHistory } from 'react-router-dom';
import Toaster from 'components/AppToaster';
import 'style/pages/Setup/PaymentViaVoucherDialog.scss';
@@ -14,7 +15,6 @@ import PaymentViaLicenseForm from './PaymentViaVoucherForm';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
import { Intent } from '@blueprintjs/core';
/**
* Payment via license dialog content.
@@ -35,10 +35,11 @@ function PaymentViaLicenseDialogContent({
const handleSubmit = (values, { setSubmitting, setErrors }) => {
setSubmitting(true);
// Payment via voucher mutate.
paymentViaVoucherMutate({ ...values })
.then(() => {
Toaster.show({
message: intl.get('payment_has_been_done_successfully'),
message: intl.get('payment_via_voucher.success_message'),
intent: Intent.SUCCESS,
});
return closeDialog('payment-via-voucher');
@@ -54,7 +55,7 @@ function PaymentViaLicenseDialogContent({
}) => {
if (errors.find((e) => e.type === 'LICENSE.CODE.IS.INVALID')) {
setErrors({
license_code: 'The license code is not valid, please try agin.',
license_code: 'payment_via_voucher.license_code_not_valid',
});
}
},

View File

@@ -31,7 +31,7 @@ function PaymentViaLicenseForm({
<Form>
<div className={CLASSES.DIALOG_BODY}>
<p>
<T id={'Pleasse enter your voucher number that you received from reseller.'} />
<T id={'payment_via_voucher.dialog.description'} />
</p>
<FastField name="license_code">

View File

@@ -1,5 +1,7 @@
import React from 'react';
import { Intent } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import { useHistory } from 'react-router-dom';
import AppToaster from 'components/AppToaster';
import withGlobalErrors from './withGlobalErrors';
@@ -17,29 +19,31 @@ function GlobalErrors({
globalErrorsSet,
}) {
if (globalErrors.something_wrong) {
toastKeySessionExpired = AppToaster.show({
message: intl.get('ops_something_went_wrong'),
intent: Intent.DANGER,
onDismiss: () => {
globalErrorsSet({ something_wrong: false });
}
}, toastKeySessionExpired);
toastKeySessionExpired = AppToaster.show(
{
message: intl.get('ops_something_went_wrong'),
intent: Intent.DANGER,
onDismiss: () => {
globalErrorsSet({ something_wrong: false });
},
},
toastKeySessionExpired,
);
}
if (globalErrors.session_expired) {
toastKeySomethingWrong = AppToaster.show({
message: intl.get('session_expired'),
intent: Intent.DANGER,
onDismiss: () => {
globalErrorsSet({ session_expired: false });
}
}, toastKeySomethingWrong);
toastKeySomethingWrong = AppToaster.show(
{
message: intl.get('session_expired'),
intent: Intent.DANGER,
onDismiss: () => {
globalErrorsSet({ session_expired: false });
},
},
toastKeySomethingWrong,
);
}
return null;
}
export default compose(
withGlobalErrors,
withGlobalErrorsActions,
)(GlobalErrors);
export default compose(withGlobalErrors, withGlobalErrorsActions)(GlobalErrors);

View File

@@ -1,13 +1,15 @@
import React, { useCallback } from 'react';
import { Button, Intent } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import WorkflowIcon from './WorkflowIcon';
import { FormattedMessage as T } from 'components';
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
import { compose } from 'utils';
import 'style/pages/Setup/Congrats.scss';
import { compose } from 'utils';
/**
* Setup congrats page.
@@ -28,15 +30,15 @@ function SetupCongratsPage({ setOrganizationSetupCompleted }) {
<div class="setup-congrats__text">
<h1>
<T id={'congrats_you_are_ready_to_go'} />
<T id={'setup.congrats.title'} />
</h1>
<p class="paragraph">
<T id={'it_is_a_long_established_fact_that_a_reader'} />
<T id={'setup.congrats.description'} />
</p>
<Button intent={Intent.PRIMARY} type="submit" onClick={handleBtnClick}>
<T id={'go_to_dashboard'} />
<T id={'setup.congrats.go_to_dashboard'} />
</Button>
</div>
</div>

View File

@@ -15,7 +15,7 @@ export default function SetupInitializingForm() {
isError,
} = useBuildTenant();
useEffect(() => {
React.useEffect(() => {
buildTenantMutate();
}, [buildTenantMutate]);
@@ -27,32 +27,32 @@ export default function SetupInitializingForm() {
{isLoading ? (
<>
<h1>
<T id={'it_s_time_to_make_your_accounting_really_simple'} />
<T id={'setup.initializing.title'} />
</h1>
<p className={'paragraph'}>
<T
id={
'while_we_set_up_your_account_please_remember_to_verify_your_account'
}
/>
<T id={'setup.initializing.description'} />
</p>
</>
) : isError ? (
<>
<h1>
<T id={'something_went_wrong'} />
<T id={'setup.initializing.something_went_wrong'} />
</h1>
<p class="paragraph">
<T id={'please_refresh_the_page'} />
<T id={'setup.initializing.please_refresh_the_page'} />
</p>
</>
) : (
<>
<h1>
<T id={'waiting_to_redirect'} />
<T id={'setup.initializing.waiting_to_redirect'} />
</h1>
<p class="paragraph">
<T id={'refresh_the_page_if_redirect_not_worked'} />
<T
id={
'setup.initializing.refresh_the_page_if_redirect_not_worked'
}
/>
</p>
</>
)}

View File

@@ -3,11 +3,14 @@ import { Icon, For } from 'components';
import { FormattedMessage as T } from 'components';
import { getFooterLinks } from 'config/footerLinks';
import { useAuthActions, useAuthOrganizationId } from 'hooks/state';
import { useAuthActions } from 'hooks/state';
/**
* Footer item link.
*/
function FooterLinkItem({ title, link }) {
return (
<div class="">
<div class="content__links-item">
<a href={link} target="_blank">
{title}
</a>
@@ -16,20 +19,65 @@ function FooterLinkItem({ title, link }) {
}
/**
* Wizard setup left section.
* Setup left section footer.
*/
export default function SetupLeftSection() {
const { setLogout } = useAuthActions();
const organizationId = useAuthOrganizationId();
function SetupLeftSectionFooter() {
// Retrieve the footer links.
const footerLinks = getFooterLinks();
return (
<div className={'content__footer'}>
<div className={'content__contact-info'}>
<p>
<T id={'setup.left_side.footer_help'} />{' '}
<span>{'+21892-738-1987'}</span>
</p>
</div>
<div className={'content__links'}>
<For render={FooterLinkItem} of={footerLinks} />
</div>
</div>
);
}
/**
* Setup left section header.
*/
function SetupLeftSectionHeader() {
const { setLogout } = useAuthActions();
// Handle logout link click.
const onClickLogout = () => {
setLogout();
};
return (
<div className={'content__header'}>
<h1 className={'content__title'}>
<T id={'setup.left_side.title'} />
</h1>
<p className={'content__text'}>
<T id={'setup.left_side.description'} />
</p>
<div class="content__divider"></div>
<div className={'content__organization'}>
<span class="signout">
<a onClick={onClickLogout} href="#">
<T id={'sign_out'} />
</a>
</span>
</div>
</div>
);
}
/**
* Wizard setup left section.
*/
export default function SetupLeftSection() {
return (
<section className={'setup-page__left-section'}>
<div className={'content'}>
@@ -41,40 +89,8 @@ export default function SetupLeftSection() {
width={190}
/>
</div>
<h1 className={'content__title'}>
<T id={'register_a_new_organization_now'} />
</h1>
<p className={'content__text'}>
<T id={'you_have_a_bigcapital_account'} />
</p>
<span class="content__divider"></span>
<div className={'content__organization'}>
<span class="organization-id">
<T id={'organization_id'} />:{' '}
<span class="id">{organizationId}</span>,
</span>
<br />
<span class="signout">
<a onClick={onClickLogout} href="#">
<T id={'sign_out'} />
</a>
</span>
</div>
<div className={'content__footer'}>
<div className={'content__contact-info'}>
<p>
<T id={'we_re_here_to_help'} /> <span>{'+21892-738-1987'}</span>
</p>
</div>
<div className={'content__links'}>
<For render={FooterLinkItem} of={footerLinks} />
</div>
</div>
<SetupLeftSectionHeader />
<SetupLeftSectionFooter />
</div>
</section>
);

View File

@@ -0,0 +1,15 @@
import * as Yup from 'yup';
import intl from 'react-intl-universal';
// Retrieve the setup organization form validation.
export const getSetupOrganizationValidation = () =>
Yup.object().shape({
organization_name: Yup.string()
.required()
.label(intl.get('organization_name_')),
financialDateStart: Yup.date().required().label(intl.get('date_start_')),
baseCurrency: Yup.string().required().label(intl.get('base_currency_')),
language: Yup.string().required().label(intl.get('language')),
fiscalYear: Yup.string().required().label(intl.get('fiscal_year_')),
timeZone: Yup.string().required().label(intl.get('time_zone_')),
});

View File

@@ -14,20 +14,18 @@ import classNames from 'classnames';
import { TimezonePicker } from '@blueprintjs/timezone';
import { FormattedMessage as T } from 'components';
import { FieldRequiredHint, Col, Row, ListSelect } from 'components';
import { Col, Row, ListSelect } from 'components';
import {
momentFormatter,
tansformDateValue,
inputIntent,
handleDateChange
handleDateChange,
} from 'utils';
import { getFiscalYear } from 'common/fiscalYearOptions';
import { getLanguages } from 'common/languagesOptions';
import { getCurrencies } from 'common/currencies';
/**
* Setup organization form.
*/
@@ -46,22 +44,24 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
<FastField name={'organization_name'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
labelInfo={<FieldRequiredHint />}
label={<T id={'legal_organization_name'} />}
className={'form-group--name'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'organization_name'} />}
>
<InputGroup {...field} />
<InputGroup {...field} intent={inputIntent({ error, touched })} />
</FormGroup>
)}
</FastField>
{/* ---------- Financial starting date ---------- */}
<FastField name={'financialDateStart'}>
{({ form: { setFieldValue }, field: { value }, meta: { error, touched } }) => (
{({
form: { setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
labelInfo={<FieldRequiredHint />}
label={<T id={'financial_starting_date'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="financialDateStart" />}
@@ -74,6 +74,7 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
onChange={handleDateChange((formattedDate) => {
setFieldValue('financialDateStart', formattedDate);
})}
intent={inputIntent({ error, touched })}
/>
</FormGroup>
)}
@@ -89,7 +90,6 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
meta: { error, touched },
}) => (
<FormGroup
labelInfo={<FieldRequiredHint />}
label={<T id={'base_currency'} />}
className={classNames(
'form-group--base-currency',
@@ -101,7 +101,9 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
>
<ListSelect
items={Currencies}
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
noResults={
<MenuItem disabled={true} text={<T id={'no_results'} />} />
}
popoverProps={{ minimal: true }}
onItemSelect={(item) => {
setFieldValue('baseCurrency', item.code);
@@ -110,6 +112,7 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
textProp={'name'}
defaultText={<T id={'select_base_currency'} />}
selectedItem={value}
intent={inputIntent({ error, touched })}
/>
</FormGroup>
)}
@@ -136,7 +139,9 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
>
<ListSelect
items={Languages}
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
noResults={
<MenuItem disabled={true} text={<T id={'no_results'} />} />
}
onItemSelect={(item) => {
setFieldValue('language', item.value);
}}
@@ -146,6 +151,7 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
defaultText={<T id={'select_language'} />}
popoverProps={{ minimal: true }}
filterable={false}
intent={inputIntent({ error, touched })}
/>
</FormGroup>
)}
@@ -154,9 +160,12 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
</Row>
{/* --------- Fiscal Year ----------- */}
<FastField name={'fiscalYear'}>
{({ form: { setFieldValue }, field: { value }, meta: { error, touched } }) => (
{({
form: { setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
labelInfo={<FieldRequiredHint />}
label={<T id={'fiscal_year'} />}
className={classNames(
'form-group--fiscal_year',
@@ -168,14 +177,16 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
>
<ListSelect
items={FiscalYear}
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
noResults={
<MenuItem disabled={true} text={<T id={'no_results'} />} />
}
selectedItem={value}
selectedItemProp={'value'}
textProp={'name'}
defaultText={<T id={'select_fiscal_year'} />}
popoverProps={{ minimal: true }}
onItemSelect={(item) => {
setFieldValue('fiscalYear', item.value)
setFieldValue('fiscalYear', item.value);
}}
filterable={false}
/>
@@ -191,7 +202,6 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
meta: { error, touched },
}) => (
<FormGroup
labelInfo={<FieldRequiredHint />}
label={<T id={'time_zone'} />}
className={classNames(
'form-group--time-zone',
@@ -216,20 +226,11 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
</FastField>
<p className={'register-org-note'}>
<T
id={
'note_you_can_change_your_preferences_later_in_dashboard_if_needed'
}
/>
<T id={'setup.organization.note_you_can_change_your_preferences'} />
</p>
<div className={'register-org-button'}>
<Button
intent={Intent.PRIMARY}
disabled={isSubmitting}
loading={isSubmitting}
type="submit"
>
<Button intent={Intent.PRIMARY} loading={isSubmitting} type="submit">
<T id={'save_continue'} />
</Button>
</div>

View File

@@ -1,9 +1,8 @@
import React from 'react';
import * as Yup from 'yup';
import { Formik } from 'formik';
import moment from 'moment';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
import 'style/pages/Setup/Organization.scss';
@@ -13,53 +12,30 @@ import { useOrganizationSetup } from 'hooks/query';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
import {
compose,
transfromToSnakeCase,
} from 'utils';
import { compose, transfromToSnakeCase } from 'utils';
import { getSetupOrganizationValidation } from './SetupOrganization.schema';
// Initial values.
const defaultValues = {
organization_name: '',
financialDateStart: moment(new Date()).format('YYYY-MM-DD'),
baseCurrency: '',
language: 'en',
fiscalYear: '',
timeZone: '',
};
/**
* Setup organization form.
*/
function SetupOrganizationPage({
wizard,
setOrganizationSetupCompleted,
}) {
function SetupOrganizationPage({ wizard, setOrganizationSetupCompleted }) {
const { mutateAsync: organizationSetupMutate } = useOrganizationSetup();
// Validation schema.
const validationSchema = Yup.object().shape({
organization_name: Yup.string()
.required()
.label(intl.get('organization_name_')),
financialDateStart: Yup.date()
.required()
.label(intl.get('date_start_')),
baseCurrency: Yup.string()
.required()
.label(intl.get('base_currency_')),
language: Yup.string()
.required()
.label(intl.get('language')),
fiscalYear: Yup.string()
.required()
.label(intl.get('fiscal_year_')),
timeZone: Yup.string()
.required()
.label(intl.get('time_zone_')),
});
// Initial values.
const defaultValues = {
organization_name: '',
financialDateStart: moment(new Date()).format('YYYY-MM-DD'),
baseCurrency: '',
language: 'en',
fiscalYear: '',
timeZone: '',
};
const validationSchema = getSetupOrganizationValidation();
// Initialize values.
const initialValues = {
...defaultValues,
};
@@ -83,10 +59,10 @@ function SetupOrganizationPage({
<div className={'setup-organization'}>
<div className={'setup-organization__title-wrap'}>
<h1>
<T id={'let_s_get_started'} />
<T id={'setup.organization.title'} />
</h1>
<p class="paragraph">
<T id={'tell_the_system_a_little_bit_about_your_organization'} />
<T id={'setup.organization.description'} />
</p>
</div>

View File

@@ -4,7 +4,7 @@ import * as R from 'ramda';
import 'style/pages/Setup/Subscription.scss';
import SetupSubscriptionForm from './SetupSubscriptionForm';
import SetupSubscriptionForm from './SetupSubscription/SetupSubscriptionForm';
import { getSubscriptionFormSchema } from './SubscriptionForm.schema';
import withSubscriptionPlansActions from '../Subscriptions/withSubscriptionPlansActions';
@@ -13,13 +13,11 @@ import withSubscriptionPlansActions from '../Subscriptions/withSubscriptionPlans
*/
function SetupSubscription({
// #withSubscriptionPlansActions
initSubscriptionPlans
initSubscriptionPlans,
}) {
React.useEffect(() => {
initSubscriptionPlans();
}, [
initSubscriptionPlans
]);
}, [initSubscriptionPlans]);
// Initial values.
const initialValues = {
@@ -30,10 +28,14 @@ function SetupSubscription({
// Handle form submit.
const handleSubmit = () => {};
const SubscriptionFormSchema = getSubscriptionFormSchema();
// Retrieve momerized subscription form schema.
const SubscriptionFormSchema = React.useMemo(
() => getSubscriptionFormSchema(),
[],
);
return (
<div className={'setup-subscription-form'}>
<div className={'setup-subscription-form'}>
<Formik
validationSchema={SubscriptionFormSchema}
initialValues={initialValues}
@@ -44,6 +46,4 @@ function SetupSubscription({
);
}
export default R.compose(
withSubscriptionPlansActions,
)(SetupSubscription);
export default R.compose(withSubscriptionPlansActions)(SetupSubscription);

View File

@@ -0,0 +1,16 @@
import React from 'react';
import SubscriptionPlansSection from './SubscriptionPlansSection';
import SubscriptionPeriodsSection from './SubscriptionPeriodsSection';
import SubscriptionPaymentMethodsSection from './SubscriptionPaymentsMethodsSection';
export default function SetupSubscriptionForm() {
return (
<div class="billing-plans">
<SubscriptionPlansSection />
<SubscriptionPeriodsSection />
<SubscriptionPaymentMethodsSection />
</div>
);
}

View File

@@ -0,0 +1,19 @@
import React from 'react';
import { T } from 'components';
import { PaymentMethodTabs } from '../../Subscriptions/SubscriptionTabs';
export default ({ formik, title, description }) => {
return (
<section class="billing-plans__section">
<h1 className="title">
<T id={'setup.plans.payment_methods.title'} />
</h1>
<p className="paragraph">
<T id={'setup.plans.payment_methods.description'} />
</p>
<PaymentMethodTabs formik={formik} />
</section>
);
};

View File

@@ -0,0 +1,43 @@
import React from 'react';
import { Field } from 'formik';
import * as R from 'ramda';
import { T, SubscriptionPeriods } from 'components';
import withPlan from '../../Subscriptions/withPlan';
const SubscriptionPeriodsEnhanced = R.compose(
withPlan(({ plan }) => ({ plan })),
)(({ plan, ...restProps }) => {
return <SubscriptionPeriods periods={plan.periods} {...restProps} />;
});
/**
* Billing periods.
*/
export default function SubscriptionPeriodsSection() {
return (
<section class="billing-plans__section">
<h1 class="title">
<T id={'setup.plans.select_period.title'} />
</h1>
<div class="description">
<p className="paragraph">
<T id={'setup.plans.select_period.description'} />
</p>
</div>
<Field name={'period'}>
{({ form: { setFieldValue, values }, field: { value } }) => (
<SubscriptionPeriodsEnhanced
planSlug={values.plan_slug}
selectedPeriod={value}
onPeriodSelect={(period) => {
setFieldValue('period', period);
}}
/>
)}
</Field>
</section>
);
}

View File

@@ -0,0 +1,41 @@
import React from 'react';
import { Field } from 'formik';
import { T, SubscriptionPlans } from 'components';
import { compose } from 'utils';
import withPlans from '../../Subscriptions/withPlans';
/**
* Billing plans.
*/
function SubscriptionPlansSection({ plans }) {
return (
<section class="billing-plans__section">
<h1 class="title">
<T id={'setup.plans.select_plan.title'} />
</h1>
<div class="description">
<p className="paragraph">
<T id={'setup.plans.select_plan.description'} />
</p>
</div>
<Field name={'plan_slug'}>
{({ form: { setFieldValue }, field: { value } }) => (
<SubscriptionPlans
value={value}
plans={plans}
onSelect={(value) => {
setFieldValue('plan_slug', value);
}}
/>
)}
</Field>
</section>
);
}
export default compose(withPlans(({ plans }) => ({ plans })))(
SubscriptionPlansSection,
);

View File

@@ -1,15 +0,0 @@
import React from 'react';
import { Form } from 'formik';
import BillingPlansForm from 'containers/Subscriptions/BillingPlansForm';
/**
* Subscription step of wizard setup.
*/
export default function SetupSubscriptionForm() {
return (
<Form>
<BillingPlansForm />
</Form>
);
}

View File

@@ -5,9 +5,7 @@ import { getSetupWizardSteps } from 'common/registerWizard';
function WizardSetupStep({ label, isActive = false }) {
return (
<li className={classNames({ 'is-active': isActive })}>
<p className={'wizard-info'}>
{ label }
</p>
<p className={'wizard-info'}>{label}</p>
</li>
);
}

View File

@@ -1,7 +1,8 @@
import React, { useEffect } from 'react';
import * as Yup from 'yup';
import { Formik, Form } from 'formik';
import intl from 'react-intl-universal';
import { If, Alert, T } from 'components';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import 'style/pages/Billing/BillingPage.scss';
@@ -10,8 +11,11 @@ import { MasterBillingTabs } from './SubscriptionTabs';
import withBillingActions from './withBillingActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withSubscriptionPlansActions from './withSubscriptionPlansActions';
import { compose } from 'utils';
import { getBillingFormValidationSchema } from './utils';
import withSubscriptions from './withSubscriptions';
/**
* Billing form.
@@ -20,26 +24,30 @@ function BillingForm({
// #withDashboardActions
changePageTitle,
//#withBillingActions
// #withBillingActions
requestSubmitBilling,
initSubscriptionPlans,
// #withSubscriptions
isSubscriptionInactive,
}) {
useEffect(() => {
changePageTitle(intl.get('billing'));
}, [changePageTitle]);
const validationSchema = Yup.object().shape({
plan_slug: Yup.string()
.required(),
period: Yup.string().required(),
license_code: Yup.string().trim(),
});
React.useEffect(() => {
initSubscriptionPlans();
}, [initSubscriptionPlans]);
// Initial values.
const initialValues = {
plan_slug: 'free',
period: 'month',
license_code: '',
};
// Handle form submitting.
const handleSubmit = (values, { setSubmitting }) => {
requestSubmitBilling(values)
.then((response) => {
@@ -53,20 +61,34 @@ function BillingForm({
return (
<DashboardInsider name={'billing-page'}>
<div className={'billing-page'}>
<If condition={isSubscriptionInactive}>
<Alert
intent={'danger'}
title={<T id={'billing.suspend_message.title'} />}
description={<T id={'billing.suspend_message.description'} />}
/>
</If>
<Formik
validationSchema={validationSchema}
validationSchema={getBillingFormValidationSchema()}
onSubmit={handleSubmit}
initialValues={initialValues}
>
{({ isSubmitting, handleSubmit }) => (
<Form>
<MasterBillingTabs />
</Form>
)}
<Form>
<MasterBillingTabs />
</Form>
</Formik>
</div>
</DashboardInsider>
);
}
export default compose(withDashboardActions, withBillingActions)(BillingForm);
export default compose(
withDashboardActions,
withBillingActions,
withSubscriptionPlansActions,
withSubscriptions(
({ isSubscriptionInactive }) => ({ isSubscriptionInactive }),
'main',
),
)(BillingForm);

View File

@@ -15,12 +15,14 @@ import { saveInvoke } from 'utils';
function BillingPeriod({
// #ownProps
label,
currencyCode,
value,
selectedOption,
onSelected,
period,
// #withPlan
price,
currencyCode,
}) {
const handlePeriodClick = () => {
saveInvoke(onSelected, value);

View File

@@ -1,42 +1,46 @@
import React from 'react';
import { Field } from 'formik';
import BillingPeriod from './BillingPeriod';
import * as R from 'ramda';
import withPlans from './withPlans';
import { T, SubscriptionPeriods } from 'components';
import { compose } from 'utils';
import withPlan from './withPlan';
/**
* Sunscription periods enhanced.
*/
const SubscriptionPeriodsEnhanced = R.compose(
withPlan(({ plan }) => ({ plan })),
)(({ plan, ...restProps }) => {
return <SubscriptionPeriods periods={plan.periods} {...restProps} />;
});
/**
* Billing periods.
*/
function BillingPeriods({ title, description, plansPeriods }) {
export default function BillingPeriods() {
return (
<section class="billing-plans__section">
<h1 class="title">{title}</h1>
<h1 class="title">
<T id={'setup.plans.select_period.title'} />
</h1>
<div class="description">
<p className="paragraph">{description}</p>
<p className="paragraph">
<T id={'setup.plans.select_period.description'} />
</p>
</div>
<Field name={'period'}>
{({ form: { setFieldValue, values } }) => (
<div className={'plan-periods'}>
{plansPeriods.map((period) => (
<BillingPeriod
planSlug={values.plan_slug}
period={period.slug}
label={period.label}
value={period.slug}
onSelected={(value) => setFieldValue('period', value)}
selectedOption={values.period}
/>
))}
</div>
{({ field: { value }, form: { values, setFieldValue } }) => (
<SubscriptionPeriodsEnhanced
selectedPeriod={value}
planSlug={values.plan_slug}
onPeriodSelect={(period) => {
setFieldValue('period', period);
}}
/>
)}
</Field>
</section>
);
}
export default compose(withPlans(({ plansPeriods }) => ({ plansPeriods })))(
BillingPeriods,
);

View File

@@ -3,8 +3,6 @@ import classNames from 'classnames';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
import 'style/pages/Subscription/PlanRadio.scss';
import { saveInvoke } from 'utils';
/**
@@ -32,9 +30,7 @@ export default function BillingPlan({
onClick={handlePlanClick}
>
<div className={'plan-radio__header'}>
<div className={'plan-radio__name'}>
{intl.get('plan_radio_name', { name: name })}
</div>
<div className={'plan-radio__name'}>{name}</div>
</div>
<div className={'plan-radio__description'}>

View File

@@ -1,12 +1,10 @@
import React from 'react';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
import 'style/pages/Subscription/BillingPlans.scss';
import BillingPlansInput from 'containers/Subscriptions/BillingPlansInput';
import BillingPeriodsInput from 'containers/Subscriptions/BillingPeriodsInput';
import BillingPaymentMethod from 'containers/Subscriptions/BillingPaymentMethod';
import BillingPlansInput from './BillingPlansInput';
import BillingPeriodsInput from './BillingPeriodsInput';
import BillingPaymentMethod from './BillingPaymentMethod';
/**
* Billing plans form.
@@ -14,18 +12,9 @@ import BillingPaymentMethod from 'containers/Subscriptions/BillingPaymentMethod'
export default function BillingPlansForm() {
return (
<div class="billing-plans">
<BillingPlansInput
title={intl.get('select_a_plan')}
description={<T id={'please_enter_your_preferred_payment_method'} />}
/>
<BillingPeriodsInput
title={intl.get('choose_your_billing')}
description={<T id={'please_enter_your_preferred_payment_method'} />}
/>
<BillingPaymentMethod
title={intl.get('payment_methods')}
description={<T id={'please_enter_your_preferred_payment_method'} />}
/>
<BillingPlansInput />
<BillingPeriodsInput />
<BillingPaymentMethod />
</div>
);
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { FastField, Field } from 'formik';
import BillingPlan from './BillingPlan';
import { Field } from 'formik';
import { T, SubscriptionPlans } from 'components';
import withPlans from './withPlans';
import { compose } from 'utils';
@@ -11,27 +11,24 @@ import { compose } from 'utils';
function BillingPlans({ plans, title, description, selectedOption }) {
return (
<section class="billing-plans__section">
<h1 class="title">{title}</h1>
<h1 class="title">
<T id={'setup.plans.select_plan.title'} />
</h1>
<div class="description">
<p className="paragraph">{description}</p>
<p className="paragraph">
<T id={'setup.plans.select_plan.description'} />
</p>
</div>
<Field name={'plan_slug'}>
{({ form: { setFieldValue }, field: { value } }) => (
<div className={'plan-radios'}>
{plans.map((plan) => (
<BillingPlan
name={plan.name}
description={plan.description}
slug={plan.slug}
price={plan.price.month}
currencyCode={plan.currencyCode}
value={plan.slug}
onSelected={(value) => setFieldValue('plan_slug', value)}
selectedOption={value}
/>
))}
</div>
<SubscriptionPlans
plans={plans}
value={value}
onSelect={(value) => {
setFieldValue('plan_slug', value);
}}
/>
)}
</Field>
</section>

View File

@@ -1,10 +1,6 @@
import React from 'react';
import BillingPlansForm from 'containers/Subscriptions/BillingPlansForm';
import BillingPlansForm from './BillingPlansForm';
export default function BillingTab() {
return (
<div>
<BillingPlansForm />
</div>
);
return (<BillingPlansForm />);
}

View File

@@ -16,11 +16,7 @@ export const MasterBillingTabs = ({ formik }) => {
id={'billing'}
panel={<BillingTab formik={formik} />}
/>
<Tab
title={intl.get('usage')}
id={'usage'}
disabled={true}
/>
<Tab title={intl.get('usage')} id={'usage'} disabled={true} />
</Tabs>
</div>
);
@@ -43,11 +39,7 @@ export const PaymentMethodTabs = ({ formik }) => {
id={'credit_card'}
disabled={true}
/>
<Tab
title={intl.get('paypal')}
id={'paypal'}
disabled={true}
/>
<Tab title={intl.get('paypal')} id={'paypal'} disabled={true} />
</Tabs>
</div>
);

View File

@@ -1,11 +1,13 @@
import React from 'react';
import { T } from 'components';
import { PaymentMethodTabs } from './SubscriptionTabs';
export default ({ formik, title, description }) => {
return (
<section class="billing-plans__section">
<h1 className="title">{ title }</h1>
<p className="paragraph">{ description }</p>
<h1 className="title"><T id={'setup.plans.payment_methods.title'} /></h1>
<p className="paragraph"><T id={'setup.plans.payment_methods.description' } /></p>
<PaymentMethodTabs formik={formik} />
</section>

View File

@@ -0,0 +1,8 @@
import * as Yup from 'yup';
export const getBillingFormValidationSchema = () =>
Yup.object().shape({
plan_slug: Yup.string().required(),
period: Yup.string().required(),
license_code: Yup.string().trim(),
});

View File

@@ -1,17 +1,14 @@
import { connect } from 'react-redux';
import {
getPlansSelector,
getPlansPeriodsSelector,
} from 'store/plans/plans.selectors';
export default (mapState) => {
const mapStateToProps = (state, props) => {
const getPlans = getPlansSelector();
const getPlansPeriods = getPlansPeriodsSelector();
const mapped = {
plans: getPlans(state, props),
plansPeriods: getPlansPeriods(state, props),
};
return mapState ? mapState(mapped, state, props) : mapped;
};

View File

@@ -0,0 +1,19 @@
import { connect } from 'react-redux';
import {
isSubscriptionsInactiveFactory,
isSubscriptionsActiveFactory
} from 'store/subscription/subscription.selectors';
export default (mapState) => {
const isSubscriptionsInactive = isSubscriptionsInactiveFactory();
const isSubscriptionsActive = isSubscriptionsActiveFactory();
const mapStateToProps = (state, props) => {
const mapped = {
isSubscriptionsInactive: isSubscriptionsInactive(state, props),
isSubscriptionsActive: isSubscriptionsActive(state, props),
};
return (mapState) ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};

View File

@@ -23,23 +23,18 @@ export function useSaveSettings(props) {
function useSettingsQuery(key, query, props) {
const setSettings = useSetSettings();
const state = useRequestQuery(
return useRequestQuery(
key,
{ method: 'get', url: 'settings', params: query },
{
select: (res) => res.data.settings,
defaultData: [],
onSuccess: (settings) => {
setSettings(settings);
},
...props,
},
);
useEffect(() => {
if (state.isSuccess) {
setSettings(state.data);
}
}, [state.data, state.isSuccess, setSettings]);
return state.data;
}
/**

View File

@@ -584,7 +584,7 @@
"payment_mades": "سندات الموردين",
"subscription": "الاشتراك",
"plan_slug": "سبيكة خطة",
"billing": "الفواتير",
"billing": "الاشتراك",
"the_billing_has_been_created_successfully": "تم إنشاء الفواتير بنجاح.",
"select_a_plan": "حدد الباقة",
"choose_your_billing": "اختر مدة الدفع",
@@ -1114,12 +1114,10 @@
"filter_": "البحث...",
"all_rights_reserved": "© {pre}-{current} كل الحقوق محفوظة.",
"congrats_your_account_has_been_created_and_invited": "مبروك! تم إنشاء حسابك ودعوتك إلى <strong>{organization_name} </strong> بنجاح.",
"it_s_time_to_make_your_accounting_really_simple": "حان الوقت لجعل عملية المحاسبة الخاصة بك بسيطة واكتر دقة!",
"while_we_set_up_your_account_please_remember_to_verify_your_account": "أثناء قيامنا بإعداد حسابك ، يرجى تذكر التحقق من حسابك بالنقر فوق الارتباط الذي أرسلناه إلى عنوان بريدك الإلكتروني المسجل",
"something_went_wrong": "هناك خطأ ما!",
"please_refresh_the_page": "يرجى تحديث الصفحة",
"waiting_to_redirect": "في انتظار إعادة التوجيه",
"refresh_the_page_if_redirect_not_worked": "قم بتحديث الصفحة إذا لم تنجح إعادة التوجيه.",
"blog": "المدونة",
"support": "الدعم النفي",
"service_status": "حالة الخدمة",
@@ -1220,6 +1218,67 @@
"pdf_preview.preview.button": "معاينة",
"invoice_preview.dialog.title": "معاينة فاتورة PDF",
"estimate_preview.dialog.title": "معاينة عرض PDF",
"receipt_preview.dialog.title":"معاينة إيصال PDF"
"receipt_preview.dialog.title":"معاينة إيصال PDF",
"edit_contact": "Edit {name}",
"item.sell_description": "Sell description",
"item.purchase_description": "Purchase description",
"closed_date": "Closed date",
"payment_made.details.payment_number": "Payment #",
"payment_receive.details.payment_number": "Payment #",
"estimate.details.estimate_number": "Estimate #",
"receipt.details.receipt_number": "Receipt #",
"bill.details.bill_number": "Bill #",
"setup.left_side.title": "سجل منشأة جديدة الأن.",
"setup.left_side.description": "حسابك علي Bigcapital",
"setup.left_side.footer_help": "نحن هنا للمساعدة!",
"setup.plan.plans": "الخطط والدفع",
"setup.plan.initializing": "التهيئة",
"setup.plan.getting_started": "البدء",
"setup.plan.congrats": "تهانينا",
"setup.plans.select_plan.description": "الرجاء إدخال طريقة الدفع المفضلة لديك أدناه. يمكنك استخدام بطاقة الائتمان / الخصم أو الدفع المسبق من خلال PayPal. ",
"setup.plans.select_plan.title": "اختر الخطة",
"setup.plans.select_period.title": "اختر الفترة",
"setup.plans.select_period.description": "يمكن تسديد الخطة شهرياً او سنوياً.",
"setup.plans.payment_methods.title": "طرق الدفع",
"setup.plans.payment_methods.description": "",
"setup.initializing.title": "حان الوقت لجعل عملية المحاسبة الخاصة بك بسيطة واكتر دقة!",
"setup.initializing.description": "أثناء قيامنا بإعداد حسابك ، يرجى تذكر التحقق من حسابك بالنقر فوق الارتباط الذي أرسلناه إلى عنوان بريدك الإلكتروني المسجل",
"setup.initializing.waiting_to_redirect": "في انتظار إعادة التوجيه",
"setup.initializing.refresh_the_page_if_redirect_not_worked": "قم بتحديث الصفحة إذا لم تنجح إعادة التوجيه.",
"setup.initializing.something_went_wrong": "هناك خطأ ما!",
"setup.initializing.please_refresh_the_page": "يرجى تحديث الصفحة",
"setup.organization.title": "دعنا نبدأ",
"setup.organization.description": "أخبر النظام قليلاً عن مؤسستك.",
"plan.essential.title": "الاساسية",
"plan.plus.title": "الاضافية",
"plan.professional.title": "الاحترافية",
"plan.feature.sale_purchase_invoice": "فواتير البيع والشراء.",
"plan.feature.receivable_payable_accounts": "حسابات العملاء والموردين.",
"plan.feature.expenses_tracking": "تتبع المصروفات",
"plan.feature.manual_journal": "القيود اليدوية",
"plan.feature.financial_reports": "القوائم المالية",
"plan.feature.one_user_with_accountant": "لمستخدم واحد والمحاسب",
"plan.feature.all_capital_essential": "جميع مميزات الباقة الاساسية",
"plan.feature.multi_currency": "تعدد العملات",
"plan.feature.purchase_sell_orders": "أوامر الشراء والبيع.",
"plan.feature.multi_inventory_managment": "تعدد المخازن.",
"plan.feature.three_users": "ثلاثة مستخدمين مع المحاسب",
"plan.feature.advanced_financial_reports": "تقارير المالية المتقدمة",
"plan.feature.tracking_multi_locations": "تتبع الفروع والمواقع",
"plan.feture.all_capital_professional_features": "جميع مميزات الباقة الاحترافية",
"plan.feature.projects_accounting": "محاسبة المشاريع والجداول الزمنية",
"plan.feature.accounting_dimensions": "محاسبة ثلاثية الابعاد",
"plan.monthly": "شهريا",
"plan.yearly": "سنوياً",
"payment_via_voucher.success_message": "تم الدفع وتجديد واشتراكك بنجاح.",
"payment_via_voucher.license_code_not_valid": "رقم الرخصة غير صالح ، يرجي المحاولة مرة أخرى",
"payment_via_voucher.dialog.description": "الرجاء إدخال رقم الرخصة التي استلمتها عند تجديد او طلب الاشتراك .",
"setup.organization.note_you_can_change_your_preferences": "ملاحظة: يمكنك تغيير تفضيلاتك لاحقًا في لوحة التحكم ، إذا لزم الأمر.",
"setup.congrats.title": "تهانينا! حسابك جاهز للعمل",
"setup.congrats.description": "تهانينا ، تم إعداد مؤسستك بنجاح. يمكنك البدء في العمل بمجرد الانتقال إلى لوحة التحكم.",
"setup.congrats.go_to_dashboard": "اذهب إلي لوحة التحكم",
"billing.suspend_message.title": "حسابك معلق :(",
"billing.suspend_message.description": "تم تعليق حسابك بسبب انتهاء فترة الاشتراك. الرجاء تجديد الاشتراك لتفعيل الحساب.",
"dashboard.subscription_msg.period_over": "انتهت فترة الاشتراك"
}

View File

@@ -578,7 +578,6 @@
"plan_slug": "Plan slug",
"billing": "Billing",
"the_billing_has_been_created_successfully": "The billing has been created successfully.",
"select_a_plan": "Select a plan",
"choose_your_billing": "Choose your billing",
"payment_methods": "Payment methods",
"usage": "Usage",
@@ -591,7 +590,6 @@
"yearly": "Yearly",
"license_code": "License Code",
"year": "Year",
"please_enter_your_preferred_payment_method": "Please enter your preferred payment method below. You can use a credit / debit card or prepay through PayPal. ",
"cards_will_be_charged": "Cards will be charged either at the end of the month or whenever your balance exceeds the usage threshold. All major credit / debit cards accepted.",
"license_number": "License number",
"subscribe": "Subscribe",
@@ -616,20 +614,15 @@
"purchasable": "Purchasable",
"sell_account": "Sell Account",
"cost_account": "Cost Account",
"register_a_new_organization_now": "Register a New Organization now!.",
"you_have_a_bigcapital_account": "You have a Bigcapital account ",
"contact_us_technical_support": "Contact us - Technical Support",
"let_s_get_started": "Lets Get Started",
"tell_the_system_a_little_bit_about_your_organization": "Tell the system a little bit about your organization.",
"organization_details": "Organization details",
"financial_starting_date": "Financial starting date ",
"note_you_can_change_your_preferences_later_in_dashboard_if_needed": "Note: You can change your preferences later in dashboard, if needed.",
"save_continue": "Save & Continue",
"organization_register": "Organization Register",
"fiscal_year_": "Fiscal year",
"welcome": "Welcome ",
"sign_out": "Sign out",
"we_re_here_to_help": "Were Here to Help!",
"date_start_": "Date start",
"something_wentwrong": "Something went wrong.",
"license_code_": "License code",
@@ -906,7 +899,6 @@
"accounting": "Accounting",
"system": "SYSTEM",
"it_s_time_to_send_estimates_to_your_customers": "It's time to send estimates to your customers",
"it_is_a_long_established_fact_that_a_reader": "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.",
"new_sale_estimate": "New sale estimate",
"learn_more": "Learn more",
"back_to_list": "Back to list.",
@@ -1068,8 +1060,7 @@
"products_you_buy_and_or_sell": "<strong> Inventory :</strong> Products you buy and/or sell and that you track quantities of.",
"products_you_buy_and_or_sell_but_don_t_need": "<strong> Non-Inventory:</strong> Products you buy and/or sell but dont need to (or cant) track quantities of, for example, nuts and bolts used in an installation.",
"there_is_no_items_in_the_table_yet": "There is no items in the table yet.",
"congrats_you_are_ready_to_go": "Congrats! You are ready to go",
"go_to_dashboard": "Go to dashboard",
"mr": "Mr.",
"mrs": "Mrs.",
"ms": "Ms.",
@@ -1101,25 +1092,13 @@
"filter_": "Filter...",
"all_rights_reserved": "© {pre}-{current} All Rights Reserved.",
"congrats_your_account_has_been_created_and_invited": "Congrats! Your account has been created and invited to <strong>{organization_name} </strong> organization successfully.",
"it_s_time_to_make_your_accounting_really_simple": "It's time to make your accounting really simple!",
"while_we_set_up_your_account_please_remember_to_verify_your_account": "while we set up your account, please remember to verify your account by clicking on the link we sent to your registered email address",
"something_went_wrong": "Something went wrong!",
"please_refresh_the_page": "Please refresh the page",
"waiting_to_redirect": "Waiting to redirect",
"refresh_the_page_if_redirect_not_worked": "Refresh the page if redirect not worked.",
"blog": "Blog",
"support": "Support",
"service_status": "Service Status",
"pricing": "Pricing",
"reseller_partner": "Reseller Partner",
"docs": "Docs",
"Pleasse enter your voucher number that you received from reseller.": "Pleasse enter your voucher number that you received from reseller.",
"Sale and purchase invoices.": "Sale and purchase invoices.",
"Customers/vendors accounts.": "Customers/vendors accounts.",
"Expenses tracking.": "Expenses tracking.",
"Manual journals.": "Manual journals.",
"Financial reports.": "Financial reports.",
"All Capital Starter features.": "All Capital Starter features.",
"Multi-currency.": "Multi-currency.",
"Purchase and sell orders.": "Purchase and sell orders.",
"Inventory management.": "Inventory management.",
@@ -1129,14 +1108,13 @@
"Track multi-branches and locations.": "Track multi-branches and locations.",
"Projects accounting and timesheets.": "Projects accounting and timesheets.",
"Accounting dimensions.": "Accounting dimensions.",
"For one user and accountant.": "For one user and accountant.",
"Monthly": "Monthly",
"Yearly": "Yearly",
"Plans & Payment": "Plans & Payment",
"Initializing": "Initializing",
"Getting started": "Getting started",
"Congratulations": "Congratulations",
"payment_has_been_done_successfully": "Payment has been done successfully.",
"manual_journal_number": "Manual journal {number}",
"conditions_and_terms": "Conditions and terms",
"allocate_landed_coast": "Allocate landed cost",
@@ -1167,12 +1145,12 @@
"Landed": "Landed",
"This options allows you to be able to add additional cost eg. freight then allocate cost to the items in your bills.": "This options allows you to be able to add additional cost eg. freight then allocate cost to the items in your bills.",
"Once your delete this located landed cost, you won't be able to restore it later, Are your sure you want to delete this transaction?": "Once your delete this located landed cost, you won't be able to restore it later, Are your sure you want to delete this transaction?",
"journal_entries":"Journal entries",
"contact":"Contact",
"invoice_details":"Invoice details",
"receipt_details":"Receipt details",
"payment_receive_details":"Payment receive details",
"payment_made_details":"Payment made details",
"journal_entries": "Journal entries",
"contact": "Contact",
"invoice_details": "Invoice details",
"receipt_details": "Receipt details",
"payment_receive_details": "Payment receive details",
"payment_made_details": "Payment made details",
"New item category": "New item category",
"New service": "New service",
"New inventory item": "New inventory item",
@@ -1182,10 +1160,10 @@
"New tasks": "New tasks",
"sorry_about_that_something_went_wrong": "Sorry about that! Something went wrong",
"if_the_problem_stuck_please_contact_us_as_soon_as_possible": "If the problem stuck, please contact us as soon as possible.",
"non-inventory":"Non-Inventory",
"terms_conditions":"Terms conditions",
"your_invoice_numbers_are_set_on_auto_increment_mod_are_you_sure_changing_this_setting":"Your invoice numbers are set on auto-increment mod. Are you sure changing this setting?",
"auto_incrementing_number":"Auto-incrementing number",
"non-inventory": "Non-Inventory",
"terms_conditions": "Terms conditions",
"your_invoice_numbers_are_set_on_auto_increment_mod_are_you_sure_changing_this_setting": "Your invoice numbers are set on auto-increment mod. Are you sure changing this setting?",
"auto_incrementing_number": "Auto-incrementing number",
"inventory_adjustment.publish.success_message": "The inventory adjustment has been published successfully.",
"inventory_adjustment.publish.alert_message": "Are you sure you want to publish this inventory adjustment?",
"the_contact_has_been_activated_successfully": "The contact has been inactivated successfully.",
@@ -1207,15 +1185,67 @@
"pdf_preview.download.button": "Download",
"pdf_preview.preview.button": "Preview",
"invoice_preview.dialog.title": "Invoice PDF Preview",
"estimate_preview.dialog.title":"Estimate PDF Preview",
"receipt_preview.dialog.title":"Receipt PDF Preview",
"edit_contact":"Edit {name}",
"estimate_preview.dialog.title": "Estimate PDF Preview",
"receipt_preview.dialog.title": "Receipt PDF Preview",
"edit_contact": "Edit {name}",
"item.sell_description": "Sell description",
"item.purchase_description": "Purchase description",
"closed_date":"Closed date",
"closed_date": "Closed date",
"payment_made.details.payment_number": "Payment #",
"payment_receive.details.payment_number": "Payment #",
"estimate.details.estimate_number": "Estimate #",
"receipt.details.receipt_number": "Receipt #",
"bill.details.bill_number": "Bill #"
"bill.details.bill_number": "Bill #",
"setup.left_side.title": "Register a New Organization now!.",
"setup.left_side.description": "You have a Bigcapital account",
"setup.left_side.footer_help": "Were Here to Help!",
"setup.plan.plans": "Plans & Payment",
"setup.plan.initializing": "Initializing",
"setup.plan.getting_started": "Getting started",
"setup.plan.congrats": "Congratulations",
"setup.plans.select_plan.description": "Please enter your preferred payment method below. You can use a credit / debit card or prepay through PayPal. ",
"setup.plans.select_plan.title": "Select a plan",
"setup.plans.select_period.title": "Plan period",
"setup.plans.select_period.description": "The plan could be billed monthly or annually.",
"setup.plans.payment_methods.title": "Payment methods",
"setup.plans.payment_methods.description": "",
"setup.initializing.title": "It's time to make your accounting really simple!",
"setup.initializing.description": "while we set up your account, please remember to verify your account by clicking on the link we sent to your registered email address",
"setup.initializing.waiting_to_redirect": "Waiting to redirect",
"setup.initializing.refresh_the_page_if_redirect_not_worked": "Refresh the page if redirect not worked.",
"setup.initializing.something_went_wrong": "Something went wrong!",
"setup.initializing.please_refresh_the_page": "Please refresh the page",
"setup.organization.title": "Lets Get Started",
"setup.organization.description": "Tell the system a little bit about your organization.",
"plan.professional.title": "Pro",
"plan.essential.title": "Essential",
"plan.plus.title": "Plus+",
"plan.feature.sale_purchase_invoice": "Sale and purchase invoices.",
"plan.feature.receivable_payable_accounts": "Customers/vendors accounts.",
"plan.feature.expenses_tracking": "Expenses tracking",
"plan.feature.manual_journal": "Manual journals.",
"plan.feature.financial_reports": "Financial reports.",
"plan.feature.one_user_with_accountant": "For one user and accountant",
"plan.feture.all_capital_professional_features": "All Capital Pro features.",
"plan.feature.multi_currency": "Multi-currency.",
"plan.feature.purchase_sell_orders": "Purchase and sell orders.",
"plan.feature.multi_inventory_managment": "Mutli-inventory managment.",
"plan.feature.three_users": "Three users with your accountant",
"plan.feature.advanced_financial_reports": "Advanced financial reports",
"plan.feature.tracking_multi_locations": "Track multi-branches and locations",
"plan.feature.all_capital_essential": "All Capital Essential features.",
"plan.feature.projects_accounting": "Projects accounting and timesheets",
"plan.feature.accounting_dimensions": "Accounting dimensions.",
"plan.monthly": "Monthly",
"plan.yearly": "Yearly",
"payment_via_voucher.success_message": "Payment has been done successfully.",
"payment_via_voucher.license_code_not_valid": "The license code is not valid, please try agin.",
"payment_via_voucher.dialog.description": "Pleasse enter your voucher number that you received from reseller.",
"setup.organization.note_you_can_change_your_preferences": "Note: You can change your preferences later in dashboard, if needed.",
"setup.congrats.title": "Congrats! You are ready to go",
"setup.congrats.description": "Congrats, Your organization is set up successfully. you can start working once go to dashboard.",
"setup.congrats.go_to_dashboard": "Go to dashboard",
"billing.suspend_message.title": "Your account is suspended :(",
"billing.suspend_message.description": "Your account has been suspended due to the expiration of the subscription period. Please renew the subscription to activate the account.",
"dashboard.subscription_msg.period_over": "Subscription period is over"
}

View File

@@ -3,10 +3,14 @@ import { lazy } from 'react';
import intl from 'react-intl-universal';
import { RESOURCES_TYPES } from '../common/resourcesTypes';
const SUBSCRIPTION_TYPE = {
MAIN: 'main',
}
// const BASE_URL = '/dashboard';
export const getDashboardRoutes = () => [
// // Accounts.
// Accounts.
{
path: `/accounts`,
component: lazy(() => import('containers/Accounts/AccountsChart')),
@@ -14,6 +18,7 @@ export const getDashboardRoutes = () => [
hotkey: 'shift+a',
pageTitle: intl.get('accounts_chart'),
defaultSearchResource: RESOURCES_TYPES.ACCOUNT,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
// Accounting.
{
@@ -27,6 +32,7 @@ export const getDashboardRoutes = () => [
sidebarExpand: false,
backLink: true,
defaultSearchResource: RESOURCES_TYPES.MANUAL_JOURNAL,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/manual-journals/:id/edit`,
@@ -38,6 +44,7 @@ export const getDashboardRoutes = () => [
sidebarExpand: false,
backLink: true,
defaultSearchResource: RESOURCES_TYPES.MANUAL_JOURNAL,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/manual-journals`,
@@ -48,6 +55,7 @@ export const getDashboardRoutes = () => [
hotkey: 'shift+m',
pageTitle: intl.get('manual_journals'),
defaultSearchResource: RESOURCES_TYPES.MANUAL_JOURNAL,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/items/categories`,
@@ -57,6 +65,7 @@ export const getDashboardRoutes = () => [
breadcrumb: intl.get('categories'),
pageTitle: intl.get('category_list'),
defaultSearchResource: RESOURCES_TYPES.ITEM,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
// Items.
{
@@ -67,6 +76,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('edit_item'),
backLink: true,
defaultSearchResource: RESOURCES_TYPES.ITEM,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/items/new?duplicate=/:id`,
@@ -75,6 +85,7 @@ export const getDashboardRoutes = () => [
}),
breadcrumb: intl.get('duplicate_item'),
defaultSearchResource: RESOURCES_TYPES.ITEM,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/items/new`,
@@ -85,6 +96,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('new_item'),
backLink: true,
defaultSearchResource: RESOURCES_TYPES.ITEM,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/items`,
@@ -93,6 +105,7 @@ export const getDashboardRoutes = () => [
hotkey: 'shift+w',
pageTitle: intl.get('items_list'),
defaultSearchResource: RESOURCES_TYPES.ITEM,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
// Inventory adjustments.
@@ -104,6 +117,7 @@ export const getDashboardRoutes = () => [
breadcrumb: intl.get('inventory_adjustments'),
pageTitle: intl.get('inventory_adjustment_list'),
defaultSearchResource: RESOURCES_TYPES.ITEM,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
// Financial Reports.
@@ -119,6 +133,7 @@ export const getDashboardRoutes = () => [
backLink: true,
sidebarExpand: false,
defaultSearchResource: RESOURCES_TYPES.INVENTORY_ADJUSTMENT,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/financial-reports/balance-sheet`,
@@ -131,6 +146,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('balance_sheet'),
backLink: true,
sidebarExpand: false,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/financial-reports/trial-balance-sheet`,
@@ -145,6 +161,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('trial_balance_sheet'),
backLink: true,
sidebarExpand: false,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/financial-reports/profit-loss-sheet`,
@@ -157,6 +174,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('profit_loss_sheet'),
backLink: true,
sidebarExpand: false,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: '/financial-reports/receivable-aging-summary',
@@ -168,6 +186,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('receivable_aging_summary'),
backLink: true,
sidebarExpand: false,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: '/financial-reports/payable-aging-summary',
@@ -179,6 +198,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('payable_aging_summary'),
backLink: true,
sidebarExpand: false,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/financial-reports/journal-sheet`,
@@ -191,6 +211,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('journal_sheet'),
sidebarExpand: false,
backLink: true,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/financial-reports/purchases-by-items`,
@@ -204,6 +225,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('purchases_by_items'),
backLink: true,
sidebarExpand: false,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/financial-reports/sales-by-items`,
@@ -217,6 +239,7 @@ export const getDashboardRoutes = () => [
),
backLink: true,
sidebarExpand: false,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/financial-reports/inventory-valuation`,
@@ -230,6 +253,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('inventory_valuation'),
backLink: true,
sidebarExpand: false,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/financial-reports/customers-balance-summary`,
@@ -243,6 +267,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('customers_balance_summary'),
backLink: true,
sidebarExpand: false,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/financial-reports/vendors-balance-summary`,
@@ -256,6 +281,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('vendors_balance_summary'),
backLink: true,
sidebarExpand: false,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/financial-reports/transactions-by-customers`,
@@ -269,6 +295,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('customers_transactions'),
backLink: true,
sidebarExpand: false,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/financial-reports/transactions-by-vendors`,
@@ -282,6 +309,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('vendors_transactions'),
backLink: true,
sidebarExpand: false,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/financial-reports/cash-flow`,
@@ -295,6 +323,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('cash_flow_statement'),
backLink: true,
sidebarExpand: false,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/financial-reports/inventory-item-details`,
@@ -308,6 +337,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('inventory_item_details'),
backLink: true,
sidebarExpand: false,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: '/financial-reports',
@@ -316,6 +346,7 @@ export const getDashboardRoutes = () => [
),
breadcrumb: intl.get('financial_reports'),
pageTitle: intl.get('all_financial_reports'),
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
// Exchange Rates
{
@@ -323,6 +354,7 @@ export const getDashboardRoutes = () => [
component: lazy(() => import('containers/ExchangeRates/ExchangeRatesList')),
breadcrumb: intl.get('exchange_rates_list'),
pageTitle: intl.get('exchange_rates_list'),
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
// Expenses.
{
@@ -336,6 +368,7 @@ export const getDashboardRoutes = () => [
sidebarExpand: false,
backLink: true,
defaultSearchResource: RESOURCES_TYPES.EXPENSE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/expenses/:id/edit`,
@@ -347,6 +380,7 @@ export const getDashboardRoutes = () => [
sidebarExpand: false,
backLink: true,
defaultSearchResource: RESOURCES_TYPES.EXPENSE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/expenses`,
@@ -357,6 +391,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('expenses_list'),
hotkey: 'shift+x',
defaultSearchResource: RESOURCES_TYPES.EXPENSE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
// Customers
@@ -370,6 +405,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('edit_customer'),
backLink: true,
defaultSearchResource: RESOURCES_TYPES.CUSTOMER,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/customers/new`,
@@ -382,6 +418,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('new_customer'),
backLink: true,
defaultSearchResource: RESOURCES_TYPES.CUSTOMER,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/customers`,
@@ -392,6 +429,7 @@ export const getDashboardRoutes = () => [
hotkey: 'shift+c',
pageTitle: intl.get('customers_list'),
defaultSearchResource: RESOURCES_TYPES.CUSTOMER,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/customers/contact_duplicate=/:id`,
@@ -403,6 +441,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('new_customer'),
backLink: true,
defaultSearchResource: RESOURCES_TYPES.CUSTOMER,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
// Vendors
@@ -416,6 +455,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('edit_vendor'),
backLink: true,
defaultSearchResource: RESOURCES_TYPES.VENDOR,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/vendors/new`,
@@ -428,6 +468,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('new_vendor'),
backLink: true,
defaultSearchResource: RESOURCES_TYPES.VENDOR,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/vendors`,
@@ -438,6 +479,7 @@ export const getDashboardRoutes = () => [
hotkey: 'shift+v',
pageTitle: intl.get('vendors_list'),
defaultSearchResource: RESOURCES_TYPES.VENDOR,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/vendors/contact_duplicate=/:id`,
@@ -449,6 +491,7 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('new_vendor'),
backLink: true,
defaultSearchResource: RESOURCES_TYPES.VENDOR,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
// Estimates
@@ -463,6 +506,7 @@ export const getDashboardRoutes = () => [
backLink: true,
sidebarExpand: false,
defaultSearchResource: RESOURCES_TYPES.ESTIMATE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/invoices/new?from_estimate_id=/:id`,
@@ -475,6 +519,7 @@ export const getDashboardRoutes = () => [
backLink: true,
sidebarExpand: false,
defaultSearchResource: RESOURCES_TYPES.INVOICE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/estimates/new`,
@@ -488,6 +533,7 @@ export const getDashboardRoutes = () => [
backLink: true,
sidebarExpand: false,
defaultSearchResource: RESOURCES_TYPES.ESTIMATE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/estimates`,
@@ -499,6 +545,7 @@ export const getDashboardRoutes = () => [
hotkey: 'shift+e',
pageTitle: intl.get('estimates_list'),
defaultSearchResource: RESOURCES_TYPES.ESTIMATE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
// Invoices.
@@ -513,6 +560,7 @@ export const getDashboardRoutes = () => [
sidebarExpand: false,
backLink: true,
defaultSearchResource: RESOURCES_TYPES.INVOICE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/invoices/new`,
@@ -526,6 +574,7 @@ export const getDashboardRoutes = () => [
sidebarExpand: false,
backLink: true,
defaultSearchResource: RESOURCES_TYPES.INVOICE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/invoices`,
@@ -536,6 +585,7 @@ export const getDashboardRoutes = () => [
hotkey: 'shift+i',
pageTitle: intl.get('invoices_list'),
defaultSearchResource: RESOURCES_TYPES.INVOICE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
// Sales Receipts.
@@ -550,6 +600,7 @@ export const getDashboardRoutes = () => [
backLink: true,
sidebarExpand: false,
defaultSearchResource: RESOURCES_TYPES.RECEIPT,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/receipts/new`,
@@ -563,6 +614,7 @@ export const getDashboardRoutes = () => [
backLink: true,
sidebarExpand: false,
defaultSearchResource: RESOURCES_TYPES.RECEIPT,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/receipts`,
@@ -573,6 +625,7 @@ export const getDashboardRoutes = () => [
hotkey: 'shift+r',
pageTitle: intl.get('receipts_list'),
defaultSearchResource: RESOURCES_TYPES.RECEIPT,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
// Payment receives
@@ -589,6 +642,7 @@ export const getDashboardRoutes = () => [
backLink: true,
sidebarExpand: false,
defaultSearchResource: RESOURCES_TYPES.PAYMENT_RECEIVE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/payment-receives/new`,
@@ -603,6 +657,7 @@ export const getDashboardRoutes = () => [
backLink: true,
sidebarExpand: false,
defaultSearchResource: RESOURCES_TYPES.PAYMENT_RECEIVE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/payment-receives`,
@@ -614,6 +669,7 @@ export const getDashboardRoutes = () => [
breadcrumb: intl.get('payment_receives_list'),
pageTitle: intl.get('payment_receives_list'),
defaultSearchResource: RESOURCES_TYPES.PAYMENT_RECEIVE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
// Bills
@@ -628,6 +684,7 @@ export const getDashboardRoutes = () => [
sidebarExpand: false,
backLink: true,
defaultSearchResource: RESOURCES_TYPES.BILL,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/bills/new`,
@@ -641,6 +698,7 @@ export const getDashboardRoutes = () => [
sidebarExpand: false,
backLink: true,
defaultSearchResource: RESOURCES_TYPES.BILL,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/bills`,
@@ -651,6 +709,7 @@ export const getDashboardRoutes = () => [
hotkey: 'shift+b',
pageTitle: intl.get('bills_list'),
defaultSearchResource: RESOURCES_TYPES.BILL,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
// Subscription billing.
@@ -658,6 +717,7 @@ export const getDashboardRoutes = () => [
path: `/billing`,
component: lazy(() => import('containers/Subscriptions/BillingForm')),
breadcrumb: intl.get('new_billing'),
subscriptionInactive: [SUBSCRIPTION_TYPE.MAIN]
},
// Payment modes.
{
@@ -673,6 +733,7 @@ export const getDashboardRoutes = () => [
sidebarExpand: false,
backLink: true,
defaultSearchResource: RESOURCES_TYPES.PAYMENT_MADE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/payment-mades/new`,
@@ -687,6 +748,7 @@ export const getDashboardRoutes = () => [
sidebarExpand: false,
backLink: true,
defaultSearchResource: RESOURCES_TYPES.PAYMENT_MADE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/payment-mades`,
@@ -698,11 +760,13 @@ export const getDashboardRoutes = () => [
breadcrumb: intl.get('payment_made_list'),
pageTitle: intl.get('payment_made_list'),
defaultSearchResource: RESOURCES_TYPES.PAYMENT_MADE,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
// Homepage
{
path: `/`,
component: lazy(() => import('containers/Homepage/Homepage')),
breadcrumb: intl.get('homepage'),
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
];

View File

@@ -1,69 +1,95 @@
import React from 'react';
import { createReducer } from '@reduxjs/toolkit';
import intl from 'react-intl-universal';
import t from 'store/types';
const getSubscriptionPlans = () => [
{
name: intl.get('Starter'),
slug: 'free',
// slug: 'starter',
description: [
intl.get('Sale and purchase invoices.'),
intl.get('Customers/vendors accounts.'),
intl.get('Expenses tracking'),
intl.get('Manual journals.'),
intl.get('Financial reports.'),
intl.get('For one user and accountant.'),
],
price: {
month: '100',
year: '1,200',
},
currencyCode: 'LYD',
},
{
name: intl.get('Essential'),
slug: 'plus',
description: [
intl.get('All Capital Starter features.'),
intl.get('Multi-currency.'),
intl.get('Purchase and sell orders.'),
intl.get('Inventory management.'),
intl.get('Three users with your accountant.'),
intl.get('Advanced financial reports.'),
],
price: {
month: '200',
year: '2,400',
},
currencyCode: 'LYD',
},
{
name: intl.get('Enterprise'),
slug: 'enterprise',
description: [
intl.get('All Capital Essential features.'),
intl.get('Track multi-branches and locations.'),
intl.get('Projects accounting and timesheets.'),
intl.get('Accounting dimensions.'),
],
price: {
month: '300',
year: '3,400',
},
currencyCode: 'LYD',
},
];
const getSubscriptionPeriods = () => [
{
slug: 'month',
label: intl.get('Monthly'),
label: intl.get('plan.monthly'),
},
{
slug: 'year',
label: intl.get('Yearly'),
label: intl.get('plan.yearly'),
},
];
const getSubscriptionPlans = () => [
{
name: intl.get('plan.essential.title'),
slug: 'free',
description: [
intl.get('plan.feature.sale_purchase_invoice'),
intl.get('plan.feature.receivable_payable_accounts'),
intl.get('plan.feature.expenses_tracking'),
intl.get('plan.feature.manual_journal'),
intl.get('plan.feature.financial_reports'),
intl.get('plan.feature.one_user_with_accountant'),
],
price: '100',
periods: [
{
slug: 'month',
label: intl.get('plan.monthly'),
price: '100'
},
{
slug: 'year',
label: intl.get('plan.yearly'),
price: '1,200',
},
],
currencyCode: 'LYD',
},
{
name: intl.get('plan.professional.title'),
slug: 'plus',
description: [
intl.get('plan.feature.all_capital_essential'),
intl.get('plan.feature.multi_currency'),
intl.get('plan.feature.purchase_sell_orders'),
intl.get('plan.feature.multi_inventory_managment'),
intl.get('plan.feature.three_users'),
intl.get('plan.feature.advanced_financial_reports'),
],
price: '200',
currencyCode: 'LYD',
periods: [
{
slug: 'month',
label: intl.get('plan.monthly'),
price: '200'
},
{
slug: 'year',
label: intl.get('plan.yearly'),
price: '1,200',
},
],
},
{
name: intl.get('plan.plus.title'),
slug: 'enterprise',
description: [
intl.get('plan.feture.all_capital_professional_features'),
intl.get('plan.feature.tracking_multi_locations'),
intl.get('plan.feature.projects_accounting'),
intl.get('plan.feature.accounting_dimensions'),
],
price: '300',
currencyCode: 'LYD',
periods: [
{
slug: 'month',
label: intl.get('plan.monthly'),
price: '300'
},
{
slug: 'year',
label: intl.get('plan.yearly'),
price: '1,200',
},
],
},
];

View File

@@ -4,8 +4,6 @@ const plansSelector = (state) => state.plans.plans;
const planSelector = (state, props) => state.plans.plans
.find((plan) => plan.slug === props.planSlug);
const plansPeriodsSelector = (state) => state.plans.periods;
// Retrieve manual jounral current page results.
export const getPlansSelector = () => createSelector(
plansSelector,
@@ -14,14 +12,6 @@ export const getPlansSelector = () => createSelector(
},
);
// Retrieve manual jounral current page results.
export const getPlansPeriodsSelector = () => createSelector(
plansPeriodsSelector,
(periods) => {
return periods;
},
);
// Retrieve plan details.
export const getPlanSelector = () => createSelector(
planSelector,

View File

@@ -1,23 +1,45 @@
import { createSelector } from '@reduxjs/toolkit';
import { includes } from 'lodash';
const subscriptionSelector = (slug) => (state, props) => {
const subscriptionSelector = (slug) => (state, props) => {
const subscriptions = Object.values(state.subscriptions.data);
return subscriptions.find((subscription) => subscription.slug === slug);
return subscriptions.find(
(subscription) => subscription.slug === (slug || props.subscriptionType),
);
};
export const isSubscriptionOnTrialFactory = (slug) => createSelector(
subscriptionSelector(slug),
(subscription) => !!subscription?.on_trial,
);
const subscriptionsSelector = (state, props) => {
const subscriptions = Object.values(state.subscriptions.data);
return subscriptions.filter(
(subscription) =>
includes(props.subscriptionTypes, subscription.slug) ||
!props.subscriptionTypes,
);
};
export const isSubscriptionActiveFactory = (slug) => createSelector(
subscriptionSelector(slug),
(subscription) => {
export const isSubscriptionOnTrialFactory = (slug) =>
createSelector(
subscriptionSelector(slug),
(subscription) => !!subscription?.on_trial,
);
export const isSubscriptionActiveFactory = (slug) =>
createSelector(subscriptionSelector(slug), (subscription) => {
return !!subscription?.active;
}
);
});
export const isSubscriptionInactiveFactory = (slug) => createSelector(
subscriptionSelector(slug),
(subscription) => !!subscription?.inactive,
);
export const isSubscriptionInactiveFactory = (slug) =>
createSelector(
subscriptionSelector(slug),
(subscription) => !!subscription?.inactive,
);
export const isSubscriptionsInactiveFactory = () =>
createSelector(subscriptionsSelector, (subscriptions) =>
subscriptions.some((subscription) => subscription?.inactive),
);
export const isSubscriptionsActiveFactory = () =>
createSelector(subscriptionsSelector, (subscriptions) =>
subscriptions.some((subscription) => subscription?.active),
);

View File

@@ -24,7 +24,7 @@
}
}
.ScrollbarsCustom-Content{
.ScrollbarsCustom-Content {
display: flex;
flex-direction: column;
height: 100%;
@@ -50,7 +50,7 @@
opacity: 0;
visibility: hidden;
svg{
svg {
opacity: $sidebar-logo-opacity;
}
}
@@ -69,12 +69,14 @@
&:not([class*="bp3-intent-"]):not(.bp3-minimal) {
color: rgb(255, 255, 255);
}
&:hover,
&:focus,
&:active,
&.bp3-active {
background: transparent;
}
.bp3-button-text {
margin-right: 4px;
text-overflow: ellipsis;
@@ -82,13 +84,14 @@
white-space: nowrap;
display: block;
}
svg {
fill: rgba(255, 255, 255, 0.3);
}
}
.bp3-popover-wrapper,
.bp3-popover-target{
.bp3-popover-target {
max-width: 100%;
display: inline-block;
}
@@ -104,20 +107,24 @@
.sidebar__head-logo {
transition: transform 0.05s ease-in-out;
}
.is-subscription-inactive:not(.sidebar--mini-sidebar) & {
opacity: 0.6;
}
}
&__scroll-wrapper {
height: 100%;
}
&__version{
&__version {
margin-top: auto;
padding: 0 20px 20px;
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
}
&__inner{
&__inner {
height: 100%;
display: flex;
flex-direction: column;
@@ -170,7 +177,7 @@
font-weight: 500;
letter-spacing: 1px;
html[lang^="ar"] &{
html[lang^="ar"] & {
font-size: 12px;
letter-spacing: 0;
}
@@ -188,6 +195,7 @@
padding-bottom: 6px;
padding-top: 6px;
}
.#{$ns}-menu-item {
padding: 8px 20px;
font-size: 15px;
@@ -242,11 +250,11 @@
opacity: 1;
visibility: visible;
}
// Hide text of bigcapital logo.
&-logo {
}
&-organization{
// Hide text of bigcapital logo.
&-logo {}
&-organization {
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
@@ -263,7 +271,7 @@
transition: min-width 0.15s ease-in-out;
min-width: 50px;
.ScrollbarsCustom-Scroller{
.ScrollbarsCustom-Scroller {
overflow: hidden !important;
}
@@ -274,14 +282,14 @@
opacity: 0;
transition-delay: 0s;
}
.sidebar__head-organization,
.sidebar__menu,
.sidebar__version {
opacity: 1;
}
.ScrollbarsCustom-Scroller{
.ScrollbarsCustom-Scroller {
overflow: scroll !important;
}
}
@@ -326,12 +334,14 @@
}
}
}
.menu--dashboard-organization{
.menu--dashboard-organization {
padding: 10px;
.org-item {
display: flex;
align-items: center;
&__logo {
height: 40px;
width: 40px;
@@ -343,16 +353,16 @@
font-size: 16px;
color: #fff;
}
&__name {
margin-left: 12px;
font-weight: 600;
}
&__divider{
&__divider {
margin: 4px 0;
height: 1px;
background: #ebebeb;
}
}
}
}

View File

@@ -2,7 +2,7 @@
.billing-page{
padding: 0 60px;
margin-top: 20px;
margin-top: 40px;
max-width: 820px;
.bp3-tab-list{

View File

@@ -95,6 +95,17 @@ $dashboard-views-bar-height: 44px;
}
}
}
&-subscription-msg{
display: flex;
flex-direction: row;
padding-right: 6px;
font-size: 12px;
span{
margin: auto;
}
}
}
&__breadcrumbs {

View File

@@ -1,5 +1,5 @@
.setup-congrats {
width: 600px;
width: 500px;
margin: 0 auto;
text-align: center;
padding-top: 80px;
@@ -15,7 +15,7 @@
margin-bottom: 12px;
}
.paragraph {
font-size: 15px;
font-size: 16px;
opacity: 0.85;
margin-bottom: 14px;
}

View File

@@ -19,7 +19,9 @@
form {
h3 {
margin-bottom: 1rem;
color: #4e5764;
margin-bottom: 1.2rem;
font-weight: 500;
}
}

View File

@@ -1,3 +1,5 @@
@import "../../Base.scss";
.setup-page {
max-width: 1600px;
@@ -9,15 +11,21 @@
&:before {
content: '';
display: block;
width: 30%;
width: 100%;
height: 1px;
min-width: 300px;
max-width: 350px;
min-width: 350px;
@media only screen and (max-width: 1200px) {
min-width: 300px;
max-width: 350px;
}
}
h1 {
font-size: 22px;
}
h1,
h3 {
font-weight: 500;
@@ -36,11 +44,14 @@
overflow: auto;
z-index: 1;
height: 100%;
width: 30%;
width: 100%;
left: 0;
top: 0;
max-width: 350px;
min-width: 300px;
width: 350px;
@media only screen and (max-width: 1200px) {
width: 300px;
}
.content {
display: flex;
@@ -76,7 +87,7 @@
font-size: 16px;
opacity: 0.75;
span > a {
span>a {
text-decoration: underline;
color: #ffffff;
margin-top: 6px;
@@ -101,7 +112,8 @@
opacity: 0.75;
padding-bottom: 5px;
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
p > span {
p>span {
unicode-bidi: plaintext;
}
}
@@ -110,7 +122,7 @@
text-align: left;
opacity: 0.55;
> div {
>div {
font-size: 13px;
margin-right: 15px;
display: inline;
@@ -133,9 +145,11 @@
margin: 0 auto;
padding: 50px 0 0;
}
ul {
display: flex;
}
li {
position: relative;
list-style-type: none;
@@ -155,6 +169,7 @@
border-radius: 50%;
background-color: #75859c;
}
&::after {
width: 100%;
height: 2px;
@@ -165,24 +180,29 @@
left: -50%;
z-index: -1;
}
&:first-child::after {
display: none;
}
&.is-active {
text-decoration: underline;
&::before {
background-color: #75859c;
}
~ li {
~li {
&:before,
&:after {
background: #e0e0e0;
}
}
p.wizard-info {
color: #004dd0;
}
}
}
}
}