Add a billing page (manually inserted to db) to help with early enterprise customers

master
Thomas Lynch 3 months ago
parent 5abd820237
commit 87cb5f0c67
  1. 3
      api.js
  2. 6
      components/MenuLinks.js
  3. 26
      controllers/account.js
  4. 111
      pages/billing.js
  5. 18
      router.js

@ -4,6 +4,9 @@ import NProgress from 'nprogress';
export async function getAccount(dispatch, errorCallback, router) {
return ApiCall('/account.json', 'GET', null, dispatch, errorCallback, router);
}
export async function getBilling(dispatch, errorCallback, router) {
return ApiCall('/billing.json', 'GET', null, dispatch, errorCallback, router);
}
export async function updateOnboarding(body, dispatch, errorCallback, router) {
return ApiCall('/forms/onboarding', 'POST', body, dispatch, errorCallback, router);
}

@ -165,6 +165,12 @@ export default withRouter(function MenuLinks({ router }) {
</ul>
<hr />
<ul className='nav nav-pills flex-column'>
<li className='nav-item'>
<Link href='/billing' className={path.startsWith('/billing') ? 'nav-link active' : 'nav-link text-body'} aria-current='page'>
<i className='bi-wallet2 pe-none me-2' width='16' height='16' />
Billing
</Link>
</li>
<li className='nav-item'>
<form action='/forms/logout' method='POST'>
<button className='nav-link text-body' type='submit'>

@ -196,3 +196,29 @@ export async function updateOnboarding(req, res) {
});
return dynamicResponse(req, res, 302, { redirect: '/account' });
};
/**
* GET /billing
* billing page
*/
export async function billingPage(app, req, res, next) {
const [data, invoices] = await Promise.all([
accountData(req, res, next),
db.db().collection('invoices').find({ username: res.locals.user.username }).sort({ _id: -1 }).toArray(),
])
res.locals.data = { ...data, invoices, user: res.locals.user };
return app.render(req, res, '/billing');
}
/**
* GET /billing.json
* billing page json data
*/
export async function billingJson(req, res, next) {
const [data, invoices] = await Promise.all([
accountData(req, res, next),
db.db().collection('invoices').find({ username: res.locals.user.username }).sort({ _id: -1 }).toArray(),
])
return res.json({ ...data, invoices, user: res.locals.user });
}

@ -0,0 +1,111 @@
import React, { useState, useEffect } from 'react';
import Link from 'next/link';
import Head from 'next/head';
import BackButton from '../components/BackButton.js';
import ErrorAlert from '../components/ErrorAlert.js';
import * as API from '../api.js';
import { useRouter } from 'next/router';
import { wildcardCheck, wildcardMatches } from '../util.js';
const statusColors = {
'cancelled': 'secondary',
'pending': 'primary',
'paid': 'success',
'unpaid': 'warning',
'overdue': 'danger',
'other': 'info',
};
export default function Billing(props) {
const router = useRouter();
const [state, dispatch] = useState(props);
const [error, setError] = useState();
useEffect(() => {
if (!state.invoices) {
API.getBilling(dispatch, setError, router);
}
}, []);
if (!state.invoices) {
return (
<div className='d-flex flex-column'>
{error && <ErrorAlert error={error} />}
<div className='text-center mb-4'>
<div className='spinner-border mt-5' role='status'>
<span className='visually-hidden'>Loading...</span>
</div>
</div>
</div>
);
}
const { invoices } = state;
return (
<>
<Head>
<title>Billing</title>
</Head>
<h5 className='fw-bold'>
Invoices:
</h5>
{/* Domains table */}
<div className='table-responsive round-shadow'>
<table className='table text-nowrap'>
<tbody>
<tr className='align-middle'>
<th>
Description
</th>
<th>
Date
</th>
<th>
Amount
</th>
<th>
Status
</th>
</tr>
{invoices.map(inv => (<tr key={inv._id} className='align-middle'>
<td>
{inv.description}
</td>
<td suppressHydrationWarning={true}>
{new Date(inv.date).toLocaleString()}
</td>
<td>
${(inv.amount/100).toFixed(2)}
</td>
<td>
<span className={`badge rounded-pill text-bg-${statusColors[inv.status]} text-uppercase`}>
{inv.status}
</span>
</td>
</tr>))}
</tbody>
</table>
</div>
{error && <span className='mx-2'><ErrorAlert error={error} /></span>}
{/* back to account */}
<BackButton to='/account' />
</>
);
}
export async function getServerSideProps({ req, res, query, resolvedUrl, locale, locales, defaultLocale}) {
return { props: res.locals.data };
}

@ -284,6 +284,24 @@ export default function router(server, app) {
csrfMiddleware,
accountController.accountJson,
);
server.get(
'/billing',
useSession,
fetchSession,
checkSession,
useHaproxy,
csrfMiddleware,
accountController.billingPage.bind(null, app),
);
server.get(
'/billing.json',
useSession,
fetchSession,
checkSession,
useHaproxy,
csrfMiddleware,
accountController.billingJson,
);
server.get(
`/map/:name(${mapNamesOrString})`,
useSession,

Loading…
Cancel
Save