mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 20:30:33 +00:00
feat: ensure to access dashboard user's subscription is active.
This commit is contained in:
17
client/src/components/Alert/index.js
Normal file
17
client/src/components/Alert/index.js
Normal 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>
|
||||
);
|
||||
}
|
||||
32
client/src/components/Alert/style.module.scss
Normal file
32
client/src/components/Alert/style.module.scss
Normal 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;
|
||||
}
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
31
client/src/components/Guards/EnsureSubscriptionIsActive.js
Normal file
31
client/src/components/Guards/EnsureSubscriptionIsActive.js
Normal 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);
|
||||
31
client/src/components/Guards/EnsureSubscriptionsIsActive.js
Normal file
31
client/src/components/Guards/EnsureSubscriptionsIsActive.js
Normal 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);
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
135
client/src/components/Subscriptions/index.js
Normal file
135
client/src/components/Subscriptions/index.js
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user