diff --git a/api/middleware/ensureAdmin.js b/api/middleware/ensureAdmin.js
new file mode 100644
index 0000000..12d829c
--- /dev/null
+++ b/api/middleware/ensureAdmin.js
@@ -0,0 +1,15 @@
+module.exports = async (req, res) => {
+ if(!req.user) {
+ const err = new Error('Unauthorized')
+ err.status = 401
+ throw err;
+ }
+
+ if(!req.user.is_admin) {
+ const err = new Error('Forbidden')
+ err.status = 401
+ throw err;
+ }
+
+ return 'next'
+}
diff --git a/api/orders.js b/api/orders.js
index 23c6cc2..f649ea5 100644
--- a/api/orders.js
+++ b/api/orders.js
@@ -2,6 +2,7 @@ const router = module.exports = require('express-promise-router')()
const parseJSON = require('body-parser').json()
const db = require('../db')
const stripe = require('stripe')(process.env.STRIPE_PRIVATE_KEY);
+const ensureAdmin = require('./middleware/ensureAdmin')
const validate = require('./middleware/validators')
@@ -13,6 +14,16 @@ router.get('/', async (req, res) => {
return res.json(await db.order.findAllForSession(req.session.uuid))
})
+router.get('/all', ensureAdmin, async (req, res) => {
+ const orders = await db.order.findAllComplete()
+ res.json(orders)
+})
+
+router.get('/:uuid', ensureAdmin, async (req, res) => {
+ const order = await db.order.findByUUID(req.params.uuid)
+ res.json(order)
+})
+
router.use(require('./middleware/ensureCart'))
router.put('/', async (req, res) => {
diff --git a/components/admin/actionBar/actionBar.js b/components/admin/actionBar/actionBar.js
index d4aa907..751b1e8 100644
--- a/components/admin/actionBar/actionBar.js
+++ b/components/admin/actionBar/actionBar.js
@@ -2,9 +2,12 @@ import styles from './actionBar.module.css'
export default function AdminActionBar({title, children}) {
return (
-
-
{title}
- {children}
-
+ <>
+
+
{title}
+ {children}
+
+
+ >
)
}
diff --git a/components/admin/actionBar/actionBar.module.css b/components/admin/actionBar/actionBar.module.css
index 34dc742..86d0dd4 100644
--- a/components/admin/actionBar/actionBar.module.css
+++ b/components/admin/actionBar/actionBar.module.css
@@ -1,10 +1,11 @@
.actionBar {
+ position: fixed;
+ width: 100%;
display: flex;
flex-direction: row;
height: 60px;
align-items: center;
margin-top: -15px;
- margin-bottom: 24px;
background: rgb(66, 66, 66);
color: white;
padding: 0 15px;
@@ -24,3 +25,9 @@
.actionBar button:global(.mdc-button--outlined):not(:disabled) {
border-color: rgba(255,255,255,.44);
}
+
+.spacer {
+ display: block;
+ height: 60px;
+ margin-bottom: 24px;
+}
diff --git a/db/models/order.js b/db/models/order.js
index e1af367..470c2bd 100644
--- a/db/models/order.js
+++ b/db/models/order.js
@@ -57,6 +57,24 @@ order.findForTransaction = transaction_uuid =>
single: true
})
+// TODO: Perhaps pagination?
+order.findAllComplete = () =>
+ dbUtil.executeQuery({
+ query: 'select * from sos.v_order where transaction_payment_state = \'completed\'',
+ returnType: 'order',
+ single: false
+ })
+
+order.findByUUID = uuid =>
+ dbUtil.executeQuery({
+ query: {
+ text: 'select * from sos.v_order where order_uuid = $1',
+ values: [uuid]
+ },
+ returnType: 'order',
+ single: true
+ })
+
order.addAddress = async function (transaction, address){
// Get parcel size
const parcel = {
diff --git a/pages/admin/orders/[id].js b/pages/admin/orders/[id].js
new file mode 100644
index 0000000..f2cbc01
--- /dev/null
+++ b/pages/admin/orders/[id].js
@@ -0,0 +1,19 @@
+import React from 'react'
+import Router from 'next/router'
+import ActionBar from '~/components/admin/actionBar'
+import Table from '~/components/table'
+import {DateTime} from 'luxon'
+
+Order.getInitialProps = async ({ctx: {axios, query: {id}}}) => {
+ const {data: order} = await axios.get(`/api/orders/${id}`)
+ return {order}
+}
+
+export default function Order({order}){
+ return (
+ <>
+
+ {JSON.stringify(order, null, 2)}
+ >
+ )
+}
diff --git a/pages/admin/orders/index.js b/pages/admin/orders/index.js
new file mode 100644
index 0000000..5b38f9c
--- /dev/null
+++ b/pages/admin/orders/index.js
@@ -0,0 +1,78 @@
+import React from 'react'
+import Router from 'next/router'
+import ActionBar from '~/components/admin/actionBar'
+import Table from '~/components/table'
+import {DateTime} from 'luxon'
+
+Orders.getInitialProps = async ({ctx}) => {
+ const {data: orders} = await ctx.axios.get('/api/orders/all')
+ return {orders}
+}
+
+export default function Orders({orders}){
+ return (
+ <>
+
+
+
+ }
+ ]}
+ rows={orders.map(order => ({id: order.uuid, ...order}))}
+ />
+ >
+ )
+}
+
+function getPurchaseTime(order){
+ const mostRecentTransaction = order.transactions.sort(sortTransactions)[0]
+ const time = parsePaymentTime(mostRecentTransaction)
+
+ return time.setZone('local').toFormat('LLLL dd, h:mm a')
+}
+
+function getNumberItems(order){
+ return order.transactions.map(transaction =>
+ transaction.cart.items.map(item => item.count)
+ ).reduce((a,b)=>(a+b))
+}
+
+function getItemPrice(order){
+ return formatMoney(order.transactions.map(transaction => transaction.item_total_price)
+ .reduce((a,b)=>(a+b)))
+}
+
+function getShippingEstimate(order){
+ return formatMoney(order.transactions.map(transaction => transaction.shipping_price)
+ .reduce((a,b)=>(a+b)))
+}
+
+function getAmountPaid(order){
+ return formatMoney(order.transactions.map(({payment}) => payment.value_cents)
+ .reduce((a,b)=>(a+b)))
+}
+
+function parsePaymentTime({payment}){
+ if(typeof payment.time === 'string')
+ payment.time = DateTime.fromISO(payment.time)
+ return payment.time
+}
+
+function sortTransactions(a,b){
+ const timeA = parsePaymentTime(a)
+ const timeB = parsePaymentTime(b)
+
+ return timeB.diff(timeA).as('seconds')
+}
+
+const formatMoney = money => {
+ if (money === undefined || money === null) return null;
+
+ return '$' + (money / 100).toFixed(2)
+}