diff --git a/frontend/src/component/admin/api-token/ApiTokenList/ApiTokenList.tsx b/frontend/src/component/admin/api-token/ApiTokenList/ApiTokenList.tsx
index cbdfc0a8e7..146ea60f05 100644
--- a/frontend/src/component/admin/api-token/ApiTokenList/ApiTokenList.tsx
+++ b/frontend/src/component/admin/api-token/ApiTokenList/ApiTokenList.tsx
@@ -86,7 +86,7 @@ const ApiTokenList = ({ location }: IApiTokenList) => {
refetch();
setToastData({
type: 'success',
- show: true,
+ title: 'Deleted successfully',
text: 'Successfully deleted API token.',
});
};
diff --git a/frontend/src/component/admin/invoice/index.js b/frontend/src/component/admin/invoice/InvoiceAdminPage.tsx
similarity index 75%
rename from frontend/src/component/admin/invoice/index.js
rename to frontend/src/component/admin/invoice/InvoiceAdminPage.tsx
index 53f3a37bcf..0eca248673 100644
--- a/frontend/src/component/admin/invoice/index.js
+++ b/frontend/src/component/admin/invoice/InvoiceAdminPage.tsx
@@ -1,14 +1,12 @@
import { useContext } from 'react';
-import PropTypes from 'prop-types';
-import InvoiceList from './invoice-container';
+import InvoiceList from './InvoiceList';
import AccessContext from '../../../contexts/AccessContext';
import { ADMIN } from '../../providers/AccessProvider/permissions';
import ConditionallyRender from '../../common/ConditionallyRender';
import { Alert } from '@material-ui/lab';
-const InvoiceAdminPage = ({ history }) => {
+const InvoiceAdminPage = () => {
const { hasAccess } = useContext(AccessContext);
-
return (
{
);
};
-InvoiceAdminPage.propTypes = {
- match: PropTypes.object.isRequired,
- history: PropTypes.object.isRequired,
-};
-
export default InvoiceAdminPage;
diff --git a/frontend/src/component/admin/invoice/InvoiceList.tsx b/frontend/src/component/admin/invoice/InvoiceList.tsx
new file mode 100644
index 0000000000..f9a8c78c8d
--- /dev/null
+++ b/frontend/src/component/admin/invoice/InvoiceList.tsx
@@ -0,0 +1,122 @@
+import { useEffect, useState } from 'react';
+import {
+ Table,
+ TableHead,
+ TableBody,
+ TableRow,
+ TableCell,
+ Button,
+} from '@material-ui/core';
+import OpenInNew from '@material-ui/icons/OpenInNew';
+import { formatDateWithLocale } from '../../common/util';
+import PageContent from '../../common/PageContent';
+import HeaderTitle from '../../common/HeaderTitle';
+import ConditionallyRender from '../../common/ConditionallyRender';
+import { formatApiPath } from '../../../utils/format-path';
+import useInvoices from '../../../hooks/api/getters/useInvoices/useInvoices';
+import { useLocation } from 'react-router-dom';
+import { IInvoice } from '../../../interfaces/invoice';
+
+const PORTAL_URL = formatApiPath('api/admin/invoices/portal');
+
+const InvoiceList = () => {
+ const { refetchInvoices, invoices } = useInvoices();
+ const [isLoaded, setLoaded] = useState(false);
+ const location = useLocation();
+
+ useEffect(() => {
+ refetchInvoices();
+ setLoaded(true);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return (
+ 0}
+ show={
+ }
+ >
+ Billing portal
+
+ }
+ />
+ }
+ >
+
+
+
+
+ Amount
+ Status
+ Due date
+ PDF
+ Link
+
+
+
+ {invoices.map((item: IInvoice) => (
+
+
+ {item.amountFomratted}
+
+
+ {item.status}
+
+
+ {item.dueDate &&
+ formatDateWithLocale(
+ item.dueDate,
+ location.locale
+ )}
+
+
+ PDF
+
+
+
+ Payment link
+
+
+
+ ))}
+
+
+
+
+ }
+ elseShow={{isLoaded && 'No invoices to show.'}
}
+ />
+ );
+};
+export default InvoiceList;
diff --git a/frontend/src/component/admin/invoice/invoice-container.js b/frontend/src/component/admin/invoice/invoice-container.js
deleted file mode 100644
index 8064dadc48..0000000000
--- a/frontend/src/component/admin/invoice/invoice-container.js
+++ /dev/null
@@ -1,11 +0,0 @@
-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);
diff --git a/frontend/src/component/admin/invoice/invoice-list.jsx b/frontend/src/component/admin/invoice/invoice-list.jsx
deleted file mode 100644
index 76b51f60de..0000000000
--- a/frontend/src/component/admin/invoice/invoice-list.jsx
+++ /dev/null
@@ -1,88 +0,0 @@
-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 '../../common/util';
-import PageContent from '../../common/PageContent';
-import HeaderTitle from '../../common/HeaderTitle';
-import ConditionallyRender from '../../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 (
- 0}
- show={
- }>
- Billing portal
- } />}>
-
-
-
-
-
- Amount
- Status
- Due date
- PDF
- Link
-
-
-
- {invoices.map(item => (
-
-
- {item.amountFomratted}
-
-
- {item.status}
-
-
- { item.dueDate && formatDateWithLocale(
- item.dueDate,
- location.locale
- )}
-
-
- PDF
-
-
- Payment link
-
-
- ))}
-
-
-
- } elseShow={{isLoaded && "No invoices to show."}
} />
- );
-}
-
-InvoiceList.propTypes = {
- location: PropTypes.object,
- fetchInvoices: PropTypes.func.isRequired,
- invoices: PropTypes.array.isRequired,
-};
-
-export default InvoiceList;
diff --git a/frontend/src/component/menu/routes.js b/frontend/src/component/menu/routes.js
index 9f48947b11..3560476aa8 100644
--- a/frontend/src/component/menu/routes.js
+++ b/frontend/src/component/menu/routes.js
@@ -14,8 +14,8 @@ import AddonsCreate from '../../page/addons/create';
import AddonsEdit from '../../page/addons/edit';
import Admin from '../admin';
import AdminApi from '../admin/api';
+import AdminInvoice from '../admin/invoice/InvoiceAdminPage';
import AdminUsers from '../admin/users/UsersAdmin';
-import AdminInvoice from '../admin/invoice';
import AdminAuth from '../admin/auth';
import Login from '../user/Login/Login';
import { P, C, E, EEA, RE } from '../common/flags';
diff --git a/frontend/src/hooks/api/getters/useInvoices/useInvoices.ts b/frontend/src/hooks/api/getters/useInvoices/useInvoices.ts
new file mode 100644
index 0000000000..4f12c5a850
--- /dev/null
+++ b/frontend/src/hooks/api/getters/useInvoices/useInvoices.ts
@@ -0,0 +1,37 @@
+import useSWR, { mutate, SWRConfiguration } from 'swr';
+import { useState, useEffect } from 'react';
+import { formatApiPath } from '../../../../utils/format-path';
+import handleErrorResponses from '../httpErrorResponseHandler';
+
+const KEY = `api/admin/invoices`;
+const path = formatApiPath(KEY);
+
+const useInvoices = (options: SWRConfiguration = {}) => {
+ const fetcher = () => {
+ return fetch(path, {
+ method: 'GET',
+ })
+ .then(handleErrorResponses('Invoices'))
+ .then(res => res.json());
+ };
+
+ const { data, error } = useSWR(KEY, fetcher, options);
+ const [loading, setLoading] = useState(!error && !data);
+
+ const refetchInvoices = () => {
+ mutate(KEY);
+ };
+
+ useEffect(() => {
+ setLoading(!error && !data);
+ }, [data, error]);
+
+ return {
+ invoices: data?.invoices || [],
+ error,
+ loading,
+ refetchInvoices,
+ };
+};
+
+export default useInvoices;
diff --git a/frontend/src/interfaces/invoice.ts b/frontend/src/interfaces/invoice.ts
new file mode 100644
index 0000000000..db49e34d9b
--- /dev/null
+++ b/frontend/src/interfaces/invoice.ts
@@ -0,0 +1,8 @@
+export interface IInvoice {
+ amountFomratted: string;
+ invoicePDF: string;
+ invoiceURL: string;
+ paid: boolean;
+ status: string;
+ dueDate?: Date;
+}