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:
parent
8b8cfd7130
commit
998cdf98ab
@ -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',
|
||||
|
36
frontend/src/page/admin/invoice/index.js
Normal file
36
frontend/src/page/admin/invoice/index.js
Normal 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;
|
11
frontend/src/page/admin/invoice/invoice-container.js
Normal file
11
frontend/src/page/admin/invoice/invoice-container.js
Normal 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);
|
88
frontend/src/page/admin/invoice/invoice-list.jsx
Normal file
88
frontend/src/page/admin/invoice/invoice-list.jsx
Normal 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;
|
20
frontend/src/store/e-admin-invoice/actions.js
Normal file
20
frontend/src/store/e-admin-invoice/actions.js
Normal 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));
|
||||
}
|
14
frontend/src/store/e-admin-invoice/api.js
Normal file
14
frontend/src/store/e-admin-invoice/api.js
Normal 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
|
||||
};
|
13
frontend/src/store/e-admin-invoice/index.js
Normal file
13
frontend/src/store/e-admin-invoice/index.js
Normal 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;
|
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -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',
|
||||
},
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user