mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
fix: show previous invoices page if UNLEASH_CLOUD is falsy (#1094)
* fix: restore previous invoices page * fix: show previous invoices page if UNLEASH_CLOUD is falsy * fix: use correct amountFormatted invoice field name
This commit is contained in:
parent
51e5939f68
commit
4fb0be3710
@ -0,0 +1,19 @@
|
|||||||
|
import { Navigate } from 'react-router-dom';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import InvoiceAdminPage from 'component/admin/invoice/InvoiceAdminPage';
|
||||||
|
|
||||||
|
const FlaggedBillingRedirect = () => {
|
||||||
|
const { uiConfig, loading } = useUiConfig();
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uiConfig.flags.UNLEASH_CLOUD) {
|
||||||
|
return <InvoiceAdminPage />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Navigate to="/admin/billing" replace />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FlaggedBillingRedirect;
|
@ -1,7 +0,0 @@
|
|||||||
import { Navigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
const RedirectAdminInvoices = () => {
|
|
||||||
return <Navigate to="/admin/billing" replace />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RedirectAdminInvoices;
|
|
25
frontend/src/component/admin/invoice/InvoiceAdminPage.tsx
Normal file
25
frontend/src/component/admin/invoice/InvoiceAdminPage.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import InvoiceList from './InvoiceList';
|
||||||
|
import AccessContext from 'contexts/AccessContext';
|
||||||
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { Alert } from '@mui/material';
|
||||||
|
|
||||||
|
const InvoiceAdminPage = () => {
|
||||||
|
const { hasAccess } = useContext(AccessContext);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={hasAccess(ADMIN)}
|
||||||
|
show={<InvoiceList />}
|
||||||
|
elseShow={
|
||||||
|
<Alert severity="error">
|
||||||
|
You need to be instance admin to access this section.
|
||||||
|
</Alert>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InvoiceAdminPage;
|
122
frontend/src/component/admin/invoice/InvoiceList.tsx
Normal file
122
frontend/src/component/admin/invoice/InvoiceList.tsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableHead,
|
||||||
|
TableBody,
|
||||||
|
TableRow,
|
||||||
|
TableCell,
|
||||||
|
Button,
|
||||||
|
} from '@mui/material';
|
||||||
|
import OpenInNew from '@mui/icons-material/OpenInNew';
|
||||||
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { formatApiPath } from 'utils/formatPath';
|
||||||
|
import useInvoices from 'hooks/api/getters/useInvoices/useInvoices';
|
||||||
|
import { IInvoice } from 'interfaces/invoice';
|
||||||
|
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||||
|
import { formatDateYMD } from 'utils/formatDate';
|
||||||
|
|
||||||
|
const PORTAL_URL = formatApiPath('api/admin/invoices/portal');
|
||||||
|
|
||||||
|
const InvoiceList = () => {
|
||||||
|
const { refetchInvoices, invoices } = useInvoices();
|
||||||
|
const [isLoaded, setLoaded] = useState(false);
|
||||||
|
const { locationSettings } = useLocationSettings();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refetchInvoices();
|
||||||
|
setLoaded(true);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={invoices.length > 0}
|
||||||
|
show={
|
||||||
|
<PageContent
|
||||||
|
header={
|
||||||
|
<PageHeader
|
||||||
|
title="Invoices"
|
||||||
|
actions={
|
||||||
|
<Button
|
||||||
|
href={PORTAL_URL}
|
||||||
|
rel="noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
endIcon={<OpenInNew />}
|
||||||
|
>
|
||||||
|
Billing portal
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Amount</TableCell>
|
||||||
|
<TableCell>Status</TableCell>
|
||||||
|
<TableCell>Due date</TableCell>
|
||||||
|
<TableCell>PDF</TableCell>
|
||||||
|
<TableCell>Link</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{invoices.map((item: IInvoice) => (
|
||||||
|
<TableRow
|
||||||
|
key={item.invoiceURL}
|
||||||
|
style={{
|
||||||
|
backgroundColor:
|
||||||
|
item.status === 'past-due'
|
||||||
|
? '#ff9194'
|
||||||
|
: 'inherit',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableCell
|
||||||
|
style={{ textAlign: 'left' }}
|
||||||
|
>
|
||||||
|
{item.amountFormatted}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
style={{ textAlign: 'left' }}
|
||||||
|
>
|
||||||
|
{item.status}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
style={{ textAlign: 'left' }}
|
||||||
|
>
|
||||||
|
{item.dueDate &&
|
||||||
|
formatDateYMD(
|
||||||
|
item.dueDate,
|
||||||
|
locationSettings.locale
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
style={{ textAlign: 'left' }}
|
||||||
|
>
|
||||||
|
<a href={item.invoicePDF}>PDF</a>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
style={{ textAlign: 'left' }}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={item.invoiceURL}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Payment link
|
||||||
|
</a>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</PageContent>
|
||||||
|
}
|
||||||
|
elseShow={<div>{isLoaded && 'No invoices to show.'}</div>}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default InvoiceList;
|
@ -23,7 +23,6 @@ import { useAuthPermissions } from 'hooks/api/getters/useAuth/useAuthPermissions
|
|||||||
import { useStyles } from './Header.styles';
|
import { useStyles } from './Header.styles';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useId } from 'hooks/useId';
|
import { useId } from 'hooks/useId';
|
||||||
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
|
|
||||||
import { IRoute } from 'interfaces/route';
|
import { IRoute } from 'interfaces/route';
|
||||||
|
|
||||||
const Header: VFC = () => {
|
const Header: VFC = () => {
|
||||||
@ -37,6 +36,7 @@ const Header: VFC = () => {
|
|||||||
const { permissions } = useAuthPermissions();
|
const { permissions } = useAuthPermissions();
|
||||||
const {
|
const {
|
||||||
uiConfig: { links, name, flags },
|
uiConfig: { links, name, flags },
|
||||||
|
isOss,
|
||||||
} = useUiConfig();
|
} = useUiConfig();
|
||||||
const smallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
const smallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
const { classes: styles } = useStyles();
|
const { classes: styles } = useStyles();
|
||||||
@ -57,15 +57,18 @@ const Header: VFC = () => {
|
|||||||
}
|
}
|
||||||
}, [permissions]);
|
}, [permissions]);
|
||||||
|
|
||||||
const { isBilling } = useInstanceStatus();
|
|
||||||
const routes = getRoutes();
|
const routes = getRoutes();
|
||||||
|
|
||||||
|
const filterByEnterprise = (route: IRoute): boolean => {
|
||||||
|
return !route.menu.isEnterprise || !isOss();
|
||||||
|
};
|
||||||
|
|
||||||
const filteredMainRoutes = {
|
const filteredMainRoutes = {
|
||||||
mainNavRoutes: routes.mainNavRoutes.filter(filterByFlags(flags)),
|
mainNavRoutes: routes.mainNavRoutes.filter(filterByFlags(flags)),
|
||||||
mobileRoutes: routes.mobileRoutes.filter(filterByFlags(flags)),
|
mobileRoutes: routes.mobileRoutes.filter(filterByFlags(flags)),
|
||||||
adminRoutes: routes.adminRoutes
|
adminRoutes: routes.adminRoutes
|
||||||
.filter(filterByFlags(flags))
|
.filter(filterByFlags(flags))
|
||||||
.filter(filterByBilling(isBilling)),
|
.filter(filterByEnterprise),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (smallScreen) {
|
if (smallScreen) {
|
||||||
@ -196,7 +199,4 @@ const Header: VFC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const filterByBilling = (isBilling?: boolean) => (route: IRoute) =>
|
|
||||||
!route.menu.isBilling || isBilling;
|
|
||||||
|
|
||||||
export default Header;
|
export default Header;
|
||||||
|
@ -411,10 +411,7 @@ exports[`returns all baseRoutes 1`] = `
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"component": [Function],
|
"component": [Function],
|
||||||
"menu": {
|
"menu": {},
|
||||||
"adminSettings": true,
|
|
||||||
"isBilling": true,
|
|
||||||
},
|
|
||||||
"parent": "/admin",
|
"parent": "/admin",
|
||||||
"path": "/admin/billing",
|
"path": "/admin/billing",
|
||||||
"title": "Billing",
|
"title": "Billing",
|
||||||
@ -422,7 +419,10 @@ exports[`returns all baseRoutes 1`] = `
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"component": [Function],
|
"component": [Function],
|
||||||
"menu": {},
|
"menu": {
|
||||||
|
"adminSettings": true,
|
||||||
|
"isEnterprise": true,
|
||||||
|
},
|
||||||
"parent": "/admin",
|
"parent": "/admin",
|
||||||
"path": "/admin-invoices",
|
"path": "/admin-invoices",
|
||||||
"title": "Invoices",
|
"title": "Invoices",
|
||||||
|
@ -7,7 +7,6 @@ import Admin from 'component/admin';
|
|||||||
import AdminApi from 'component/admin/api';
|
import AdminApi from 'component/admin/api';
|
||||||
import AdminUsers from 'component/admin/users/UsersAdmin';
|
import AdminUsers from 'component/admin/users/UsersAdmin';
|
||||||
import { AuthSettings } from 'component/admin/auth/AuthSettings';
|
import { AuthSettings } from 'component/admin/auth/AuthSettings';
|
||||||
import { Billing } from 'component/admin/billing/Billing';
|
|
||||||
import Login from 'component/user/Login/Login';
|
import Login from 'component/user/Login/Login';
|
||||||
import { C, EEA, P, RE, SE } from 'component/common/flags';
|
import { C, EEA, P, RE, SE } from 'component/common/flags';
|
||||||
import { NewUser } from 'component/user/NewUser/NewUser';
|
import { NewUser } from 'component/user/NewUser/NewUser';
|
||||||
@ -50,8 +49,9 @@ import { EditSegment } from 'component/segments/EditSegment/EditSegment';
|
|||||||
import { IRoute } from 'interfaces/route';
|
import { IRoute } from 'interfaces/route';
|
||||||
import { EnvironmentTable } from 'component/environments/EnvironmentTable/EnvironmentTable';
|
import { EnvironmentTable } from 'component/environments/EnvironmentTable/EnvironmentTable';
|
||||||
import { SegmentTable } from 'component/segments/SegmentTable/SegmentTable';
|
import { SegmentTable } from 'component/segments/SegmentTable/SegmentTable';
|
||||||
import RedirectAdminInvoices from 'component/admin/billing/RedirectAdminInvoices/RedirectAdminInvoices';
|
import FlaggedBillingRedirect from 'component/admin/billing/FlaggedBillingRedirect/FlaggedBillingRedirect';
|
||||||
import { FeaturesArchiveTable } from '../archive/FeaturesArchiveTable';
|
import { FeaturesArchiveTable } from '../archive/FeaturesArchiveTable';
|
||||||
|
import { Billing } from 'component/admin/billing/Billing';
|
||||||
|
|
||||||
export const routes: IRoute[] = [
|
export const routes: IRoute[] = [
|
||||||
// Splash
|
// Splash
|
||||||
@ -462,15 +462,15 @@ export const routes: IRoute[] = [
|
|||||||
title: 'Billing',
|
title: 'Billing',
|
||||||
component: Billing,
|
component: Billing,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
menu: { adminSettings: true, isBilling: true },
|
menu: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/admin-invoices',
|
path: '/admin-invoices',
|
||||||
parent: '/admin',
|
parent: '/admin',
|
||||||
title: 'Invoices',
|
title: 'Invoices',
|
||||||
component: RedirectAdminInvoices,
|
component: FlaggedBillingRedirect,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
menu: {},
|
menu: { adminSettings: true, isEnterprise: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/admin',
|
path: '/admin',
|
||||||
|
8
frontend/src/interfaces/invoice.ts
Normal file
8
frontend/src/interfaces/invoice.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export interface IInvoice {
|
||||||
|
amountFormatted: string;
|
||||||
|
invoicePDF: string;
|
||||||
|
invoiceURL: string;
|
||||||
|
paid: boolean;
|
||||||
|
status: string;
|
||||||
|
dueDate?: Date;
|
||||||
|
}
|
@ -17,5 +17,5 @@ interface IRouteMenu {
|
|||||||
mobile?: boolean;
|
mobile?: boolean;
|
||||||
advanced?: boolean;
|
advanced?: boolean;
|
||||||
adminSettings?: boolean;
|
adminSettings?: boolean;
|
||||||
isBilling?: boolean;
|
isEnterprise?: boolean;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user