mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-31 13:47:02 +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 Admin from '../../page/admin';
|
||||||
import AdminApi from '../../page/admin/api';
|
import AdminApi from '../../page/admin/api';
|
||||||
import AdminUsers from '../../page/admin/users';
|
import AdminUsers from '../../page/admin/users';
|
||||||
|
import AdminInvoice from '../../page/admin/invoice';
|
||||||
import AdminAuth from '../../page/admin/auth';
|
import AdminAuth from '../../page/admin/auth';
|
||||||
import Reporting from '../../page/reporting';
|
import Reporting from '../../page/reporting';
|
||||||
import Login from '../user/Login';
|
import Login from '../user/Login';
|
||||||
@ -327,6 +328,15 @@ export const routes = [
|
|||||||
type: 'protected',
|
type: 'protected',
|
||||||
layout: 'main',
|
layout: 'main',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/admin-invoices',
|
||||||
|
title: 'Invoices',
|
||||||
|
icon: 'money',
|
||||||
|
component: AdminInvoice,
|
||||||
|
hidden: true,
|
||||||
|
type: 'protected',
|
||||||
|
layout: 'main',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/admin',
|
path: '/admin',
|
||||||
title: '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 apiAdmin from './e-api-admin';
|
||||||
import authAdmin from './e-admin-auth';
|
import authAdmin from './e-admin-auth';
|
||||||
import apiCalls from './api-calls';
|
import apiCalls from './api-calls';
|
||||||
|
import invoiceAdmin from './e-admin-invoice';
|
||||||
|
|
||||||
const unleashStore = combineReducers({
|
const unleashStore = combineReducers({
|
||||||
features,
|
features,
|
||||||
@ -41,6 +42,7 @@ const unleashStore = combineReducers({
|
|||||||
apiAdmin,
|
apiAdmin,
|
||||||
authAdmin,
|
authAdmin,
|
||||||
apiCalls,
|
apiCalls,
|
||||||
|
invoiceAdmin,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default unleashStore;
|
export default unleashStore;
|
||||||
|
@ -7,10 +7,10 @@ Object {
|
|||||||
"headerBackground": undefined,
|
"headerBackground": undefined,
|
||||||
"links": Array [
|
"links": Array [
|
||||||
Object {
|
Object {
|
||||||
"href": "https://unleash.github.io?source=oss",
|
"href": "https://docs.getunleash.io/docs?source=oss",
|
||||||
"icon": "library_books",
|
"icon": "library_books",
|
||||||
"title": "User documentation",
|
"title": "User documentation",
|
||||||
"value": "User documentation",
|
"value": "Documentation",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"href": "https://github.com/Unleash",
|
"href": "https://github.com/Unleash",
|
||||||
@ -32,10 +32,10 @@ Object {
|
|||||||
"headerBackground": "red",
|
"headerBackground": "red",
|
||||||
"links": Array [
|
"links": Array [
|
||||||
Object {
|
Object {
|
||||||
"href": "https://unleash.github.io?source=oss",
|
"href": "https://docs.getunleash.io/docs?source=oss",
|
||||||
"icon": "library_books",
|
"icon": "library_books",
|
||||||
"title": "User documentation",
|
"title": "User documentation",
|
||||||
"value": "User documentation",
|
"value": "Documentation",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"href": "https://github.com/Unleash",
|
"href": "https://github.com/Unleash",
|
||||||
@ -57,10 +57,10 @@ Object {
|
|||||||
"headerBackground": "black",
|
"headerBackground": "black",
|
||||||
"links": Array [
|
"links": Array [
|
||||||
Object {
|
Object {
|
||||||
"href": "https://unleash.github.io?source=oss",
|
"href": "https://docs.getunleash.io/docs?source=oss",
|
||||||
"icon": "library_books",
|
"icon": "library_books",
|
||||||
"title": "User documentation",
|
"title": "User documentation",
|
||||||
"value": "User documentation",
|
"value": "Documentation",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"href": "https://github.com/Unleash",
|
"href": "https://github.com/Unleash",
|
||||||
|
@ -20,9 +20,9 @@ const DEFAULT = new $Map({
|
|||||||
flags: {},
|
flags: {},
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
value: 'User documentation',
|
value: 'Documentation',
|
||||||
icon: 'library_books',
|
icon: 'library_books',
|
||||||
href: 'https://unleash.github.io?source=oss',
|
href: 'https://docs.getunleash.io/docs?source=oss',
|
||||||
title: 'User documentation',
|
title: 'User documentation',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user