1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

feat: Add admin-invoice section (#299)

* feat: Add admin-invoice section

* fix: do not show invoice table if list is empty
This commit is contained in:
Ivar Conradi Østhus 2021-05-21 08:56:22 +02:00 committed by GitHub
parent 8b8cfd7130
commit 998cdf98ab
10 changed files with 202 additions and 8 deletions

View File

@ -31,6 +31,7 @@ import AddonsEdit from '../../page/addons/edit';
import Admin from '../../page/admin';
import AdminApi from '../../page/admin/api';
import AdminUsers from '../../page/admin/users';
import AdminInvoice from '../../page/admin/invoice';
import AdminAuth from '../../page/admin/auth';
import Reporting from '../../page/reporting';
import Login from '../user/Login';
@ -327,6 +328,15 @@ export const routes = [
type: 'protected',
layout: 'main',
},
{
path: '/admin-invoices',
title: 'Invoices',
icon: 'money',
component: AdminInvoice,
hidden: true,
type: 'protected',
layout: 'main',
},
{
path: '/admin',
title: 'Admin',

View File

@ -0,0 +1,36 @@
import { useContext } from 'react';
import PropTypes from 'prop-types';
import InvoiceList from './invoice-container';
import AccessContext from '../../../contexts/AccessContext';
import { ADMIN } from '../../../component/AccessProvider/permissions';
import ConditionallyRender from '../../../component/common/ConditionallyRender';
import { Alert } from '@material-ui/lab';
const InvoiceAdminPage = ({ history }) => {
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>
);
};
InvoiceAdminPage.propTypes = {
match: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
};
export default InvoiceAdminPage;

View File

@ -0,0 +1,11 @@
import { connect } from 'react-redux';
import Component from './invoice-list';
import { fetchInvoices } from '../../../store/e-admin-invoice/actions';
export default connect(
state => ({
location: state.settings.toJS().location || {},
invoices: state.invoiceAdmin.toJS(),
}),
{ fetchInvoices }
)(Component);

View File

@ -0,0 +1,88 @@
import { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import {
Table,
TableHead,
TableBody,
TableRow,
TableCell,
Button,
} from '@material-ui/core';
import OpenInNew from '@material-ui/icons/OpenInNew';
import { formatDateWithLocale } from '../../../component/common/util';
import PageContent from '../../../component/common/PageContent';
import HeaderTitle from '../../../component/common/HeaderTitle';
import ConditionallyRender from '../../../component/common/ConditionallyRender';
import { formatApiPath } from '../../../utils/format-path';
const PORTAL_URL = formatApiPath('api/admin/invoices/portal');
function InvoiceList({
location,
fetchInvoices,
invoices,
}) {
const [isLoaded, setLoaded] = useState(false);
useEffect(() => {
fetchInvoices().finally(() => setLoaded(true));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<ConditionallyRender condition={invoices.length > 0}
show={
<PageContent headerContent={<HeaderTitle 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 => (
<TableRow key={item.invoiceURL} style={{backgroundColor: item.status === 'past-due' ? '#ff9194' : 'inherit'}}>
<TableCell style={{ textAlign: 'left' }}>
{item.amountFomratted}
</TableCell>
<TableCell style={{ textAlign: 'left' }}>
{item.status}
</TableCell>
<TableCell style={{ textAlign: 'left' }}>
{formatDateWithLocale(
item.dueDate,
location.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>} />
);
}
InvoiceList.propTypes = {
location: PropTypes.object,
fetchInvoices: PropTypes.func.isRequired,
invoices: PropTypes.array.isRequired,
};
export default InvoiceList;

View File

@ -0,0 +1,20 @@
import api from './api';
import { dispatchError } from '../util';
export const RECEIVE_INVOICES = 'RECEIVE_INVOICES';
export const ERROR_FETCH_INVOICES = 'ERROR_FETCH_INVOICES';
const debug = require('debug')('unleash:e-admin-invoice-actions');
export function fetchInvoices() {
debug('Start fetching invoices for hosted customer');
return dispatch =>
api
.fetchAll()
.then(value =>
dispatch({
type: RECEIVE_INVOICES,
invoices: value.invoices,
})
)
.catch(dispatchError(dispatch, ERROR_FETCH_INVOICES));
}

View File

@ -0,0 +1,14 @@
import { formatApiPath } from '../../utils/format-path';
import { throwIfNotSuccess, headers } from '../api-helper';
const URI = formatApiPath('api/admin/invoices');
function fetchAll() {
return fetch(URI, { headers, credentials: 'include' })
.then(throwIfNotSuccess)
.then(response => response.json());
}
export default {
fetchAll
};

View File

@ -0,0 +1,13 @@
import { List } from 'immutable';
import { RECEIVE_INVOICES } from './actions';
const store = (state = new List(), action) => {
switch (action.type) {
case RECEIVE_INVOICES:
return new List(action.invoices);
default:
return state;
}
};
export default store;

View File

@ -19,6 +19,7 @@ import addons from './addons';
import apiAdmin from './e-api-admin';
import authAdmin from './e-admin-auth';
import apiCalls from './api-calls';
import invoiceAdmin from './e-admin-invoice';
const unleashStore = combineReducers({
features,
@ -41,6 +42,7 @@ const unleashStore = combineReducers({
apiAdmin,
authAdmin,
apiCalls,
invoiceAdmin,
});
export default unleashStore;

View File

@ -7,10 +7,10 @@ Object {
"headerBackground": undefined,
"links": Array [
Object {
"href": "https://unleash.github.io?source=oss",
"href": "https://docs.getunleash.io/docs?source=oss",
"icon": "library_books",
"title": "User documentation",
"value": "User documentation",
"value": "Documentation",
},
Object {
"href": "https://github.com/Unleash",
@ -32,10 +32,10 @@ Object {
"headerBackground": "red",
"links": Array [
Object {
"href": "https://unleash.github.io?source=oss",
"href": "https://docs.getunleash.io/docs?source=oss",
"icon": "library_books",
"title": "User documentation",
"value": "User documentation",
"value": "Documentation",
},
Object {
"href": "https://github.com/Unleash",
@ -57,10 +57,10 @@ Object {
"headerBackground": "black",
"links": Array [
Object {
"href": "https://unleash.github.io?source=oss",
"href": "https://docs.getunleash.io/docs?source=oss",
"icon": "library_books",
"title": "User documentation",
"value": "User documentation",
"value": "Documentation",
},
Object {
"href": "https://github.com/Unleash",

View File

@ -20,9 +20,9 @@ const DEFAULT = new $Map({
flags: {},
links: [
{
value: 'User documentation',
value: 'Documentation',
icon: 'library_books',
href: 'https://unleash.github.io?source=oss',
href: 'https://docs.getunleash.io/docs?source=oss',
title: 'User documentation',
},
{