diff --git a/components/orderSummary/orderSummary.js b/components/orderSummary/orderSummary.js new file mode 100644 index 0000000..d48544c --- /dev/null +++ b/components/orderSummary/orderSummary.js @@ -0,0 +1,137 @@ +import React, {useMemo} from 'react' +import {DateTime} from 'luxon' + +import styles from './style.module.css' + +export default function OrderSummary({order, isAdmin}) { + const items = useMemo(() => coalesceItems(order), [order]) + const totalShipping = useMemo(() => sumShippingPrice(order), [order]) + + return ( + <> +
+ {items.length && ( +
+

Contents:

+
+
    + {items.map(({count, item}) => ( +
  • {count}x - {item.name}
  • + ))} +
+
+
+ )} + + {order.address && ( +
+

Address:

+
+
+                {order.address.name || order.address.company}
+ {order.address.street1}
+ {order.address.street2 && (<>{order.address.street2}
)} + {order.address.city}, {order.address.state}, {order.address.zip}
+ {order.address.country !== 'US' && order.address.country} +
+
+
+ )} + +
+

Shipping:

+
+ +
+
+
+ + ) + +} + +function coalesceItems(order) { + const {transactions} = order + + let items = {} + + for(const trans of transactions) + for(const {count, item} of trans.cart.items) { + if(!items[item.uuid]) + items[item.uuid] = { + count, item + } + else + items[item.uuid].count += count + } + + return Object.values(items) +} + +function sumShippingPrice(order) { + return order.transactions.map(trans => trans.shipping_price).reduce((a,b) => (b+a), 0) +} + +const formatMoney = money => { + if (money === undefined || money === null) return null; + + return '$' + (money / 100).toFixed(2) +} + +function formatTime(time){ + return DateTime.fromISO(time).setZone('local').toFormat('LLLL dd') +} + +function ShippingStatus({totalShipping, delivery, isAdmin}) { + if(!delivery) + return ( + <> + {isAdmin && (

{formatMoney(totalShipping)} charged to customer

)} +

Not yet shipped

+ + ) + + switch (delivery.type) { + case 'easypost': + case 'hand_shipped': + return ( + <> + {isAdmin && ( +

+ {formatMoney(totalShipping)} charged to customer
+ {delivery.easypost && (<>{formatMoney(parseFloat(delivery.easypost.selected_rate.rate) * 100)} paid to EasyPost)} +

+ )} + +

+ Shipped + {isAdmin && delivery.type === 'hand_shipped' && ' by hand '} + {' '} on {formatTime(delivery.date_shipped)} +

+ +

+ Tracking number:
+ {delivery.tracking_number} +

+ + ) + + case 'hand_delivered': + return ( + <> +

Delivered {isAdmin && ' in person '} on {formatTime(delivery.date_delivered)}

+ {isAdmin && ( + <> +

Note:

+
+                {delivery.description}
+              
+ + )} + + ) + + default: + return

Error displaying shipment information

+ } +} diff --git a/components/orderSummary/style.module.css b/components/orderSummary/style.module.css new file mode 100644 index 0000000..7ebfa35 --- /dev/null +++ b/components/orderSummary/style.module.css @@ -0,0 +1,23 @@ +.summaryRow { + display: flex; + flex-direction: row; + justify-content: space-around; +} + +.summaryRowSection { + margin: 8px; + flex: 1; +} + +.summaryRowSection > h3, .summaryRowSection h4 { + margin: 4px 0; +} + +.summaryRowSection > section pre { + margin: 0; + padding: 16px; + background: white; + display: inline-block; + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); + white-space: normal; +} diff --git a/pages/account/index.js b/pages/account/index.js index a99f360..88e1bc4 100644 --- a/pages/account/index.js +++ b/pages/account/index.js @@ -51,7 +51,7 @@ export default function AccountPage({orders}) { {name: 'Shipping', extractor: getShippingEstimate}, {name: 'Total', extractor: getAmountPaid}, {name: '', extractor: order => - + } ]} rows={orders} diff --git a/pages/account/orders/[orderNum].js b/pages/account/orders/[orderNum].js new file mode 100644 index 0000000..9e71421 --- /dev/null +++ b/pages/account/orders/[orderNum].js @@ -0,0 +1,42 @@ +import {DateTime} from 'luxon' + +import redirect from '~/utils/redirectGetInitialProps' +import OrderSummary from '~/components/orderSummary' + +OrderDetails.getInitialProps = async function({ctx, user}) { + const {axios, query: {orderNum}} = ctx; + + if(!user) + return redirect(ctx, 302, '/login') + + if(!user.email_confirmed) + return redirect(ctx, 302, '/account/email/confirm') + + const {data: orders} = await axios.get(`/api/orders`) + + const order = orders.find(({number}) => number === parseInt(orderNum, 10)) + return {order} +} + +export default function OrderDetails({order}) { + const lastTransaction = getLastTransaction(order) + + return ( + <> +

Order #{order.number}: Details

+

Purchased {DateTime.fromISO(lastTransaction.completion_time).toFormat('LLLL dd, h:mm a')}

+ + + ) +} + +function getLastTransaction(order){ + return order.transactions.sort(sortTransactions)[0] +} + +function sortTransactions({completion_time: a}, {completion_time: b}){ + const timeA = DateTime.fromISO(a) + const timeB = DateTime.fromISO(b) + + return timeB.diff(timeA).as('seconds') +} diff --git a/pages/admin/orders/[id]/index.js b/pages/admin/orders/[id]/index.js index 4f61ff6..cdd34e8 100644 --- a/pages/admin/orders/[id]/index.js +++ b/pages/admin/orders/[id]/index.js @@ -4,6 +4,7 @@ import ActionBar from '~/components/admin/actionBar' import Table from '~/components/table' import {DateTime} from 'luxon' import {Button} from '@rmwc/button' +import OrderSummary from '~/components/orderSummary' Order.getInitialProps = async ({ctx: {axios, query: {id}}}) => { const {data: order} = await axios.get(`/api/orders/${id}`) @@ -21,10 +22,10 @@ export default function Order({order}){ {label: 'Mark Delivered', url: `/admin/orders/${order.uuid}/ship/delivery`} ]}/> -

Order for {capitalizeName(order.address.name)}

+

Order #{order.number} for {capitalizeName(order.address.name)}

Purchased {DateTime.fromISO(lastTransaction.completion_time).toFormat('LLLL dd, h:mm a')}

-
{JSON.stringify(order, null, 2)}
+ ) } diff --git a/styles/layout.css b/styles/layout.css index 4ab8e5d..d2c5d85 100644 --- a/styles/layout.css +++ b/styles/layout.css @@ -45,7 +45,7 @@ main { padding-bottom: 15px; } -main > p, main > h3, main > h4, main > h5, main > h6 { +main > p, main > section, main > h3, main > h4, main > h5, main > h6 { width: calc(100% - 100px); max-width: 800px; margin-left: auto;