From ee4f291db2c381bb78d9d4527ecbb8f12ee1b6c2 Mon Sep 17 00:00:00 2001 From: Ashelyn Dawn Date: Tue, 21 Apr 2020 22:22:38 -0600 Subject: [PATCH] Admin order list --- api/middleware/ensureAdmin.js | 15 ++++ api/orders.js | 11 +++ components/admin/actionBar/actionBar.js | 11 ++- .../admin/actionBar/actionBar.module.css | 9 ++- db/models/order.js | 18 +++++ pages/admin/orders/[id].js | 19 +++++ pages/admin/orders/index.js | 78 +++++++++++++++++++ 7 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 api/middleware/ensureAdmin.js create mode 100644 pages/admin/orders/[id].js create mode 100644 pages/admin/orders/index.js 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) +}