+
+ {visible && (
+
+
+
+
+
+ )}
+
+ ), window.document.body)
+}
diff --git a/components/modal/modal.module.css b/components/modal/modal.module.css
new file mode 100644
index 0000000..5edc284
--- /dev/null
+++ b/components/modal/modal.module.css
@@ -0,0 +1,74 @@
+.modalContainer, .modalBackground, .modalScroll {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ overflow: hidden;
+ z-index: 10;
+}
+
+.modalContainer {
+ opacity: 0;
+ transition: opacity .1s;
+ pointer-events: none;
+}
+
+.modalShown {
+ opacity: 1;
+ transition: opacity .5s;
+ pointer-events: initial;
+}
+
+.modalBackground {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: rgba(0,0,0,.7);
+}
+
+.modalScroll {
+ overflow-y: auto;
+ cursor: pointer;
+}
+
+.modal {
+ border: none;
+ margin: 100px auto;
+ max-width: 600px;
+ width: calc(100vw - 50px);
+ background: white;
+ min-height: 100px;
+ animation: slidein .4s;
+ box-shadow: 0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12);
+}
+
+@keyframes slidein {
+ 0% {
+ margin-top: 80px;
+ }
+
+ 100% {
+ margin-top: 100px;
+ }
+}
+
+.titleContainer {
+ height: 60px;
+ display: flex;
+ flex-direction: row;
+ border-bottom: solid 1px rgba(0,0,0,.1);
+ padding: 0 8px;
+ color: black;
+}
+
+.titleContainer > h2:first-child {
+ flex: 1;
+}
+
+.titleContainer > button:last-child {
+ color: black;
+}
+
diff --git a/db/mappings/order.js b/db/mappings/order.js
index 20ea632..629770e 100644
--- a/db/mappings/order.js
+++ b/db/mappings/order.js
@@ -7,7 +7,8 @@ module.exports = [{
],
associations: [
{name: 'user', mapId: 'userMap', columnPrefix: 'user_'},
- {name: 'address', mapId: 'addressMap', columnPrefix: 'address_'}
+ {name: 'address', mapId: 'addressMap', columnPrefix: 'address_'},
+ {name: 'delivery', mapId: 'deliveryMap', columnPrefix: 'delivery_'}
],
collections: [
{name: 'transactions', mapId: 'transactionMap', columnPrefix: 'transaction_'}
@@ -76,4 +77,15 @@ module.exports = [{
properties: [
'reciept_email'
]
+},{
+ mapId: 'deliveryMap',
+ idProperty: 'uuid',
+ properties: [
+ 'type',
+ 'tracking_number',
+ 'date_shipped',
+ 'easypost_id',
+ 'description',
+ 'date_delivered'
+ ]
}]
diff --git a/db/models/order.js b/db/models/order.js
index 470c2bd..bdaaa9f 100644
--- a/db/models/order.js
+++ b/db/models/order.js
@@ -242,3 +242,11 @@ order.addPayment = async function(transaction, paymentIntent){
return order
}
+
+order.setTracking = (uuid, trackingCode, shipDate) =>
+ dbUtil.executeFunction({
+ name: 'set_delivery_tracking',
+ params: [uuid, trackingCode, shipDate],
+ returnType: 'order',
+ single: true
+ })
diff --git a/db/sql/1-tables.sql b/db/sql/1-tables.sql
index f3f49b8..2337ca6 100644
--- a/db/sql/1-tables.sql
+++ b/db/sql/1-tables.sql
@@ -116,7 +116,7 @@ create table sos."delivery_hand_shipped" (
foreign key (delivery_uuid, delivery_type) references sos."delivery" (delivery_uuid, delivery_type),
delivery_tracking_number text not null,
- delivery_date_shipped timestamptz not null
+ delivery_date_shipped timestamptz not null default now()
);
create table sos."delivery_easypost" (
diff --git a/db/sql/2-views.sql b/db/sql/2-views.sql
index 610150b..c7d9040 100644
--- a/db/sql/2-views.sql
+++ b/db/sql/2-views.sql
@@ -102,7 +102,19 @@ create or replace view sos.v_transaction_paid as
left join sos.v_payment on transaction_uuid = payment_transaction_uuid
group by transaction_uuid;
--- TODO: add coupon, delivery
+create or replace view sos.v_delivery as
+ select
+ "delivery".*,
+ coalesce("delivery_hand_shipped".delivery_tracking_number, "delivery_easypost".delivery_tracking_number) as delivery_tracking_number,
+ coalesce("delivery_hand_shipped".delivery_date_shipped, "delivery_easypost".delivery_date_shipped) as delivery_date_shipped,
+ delivery_easypost_id,
+ delivery_description,
+ delivery_date_delivered
+ from sos."delivery"
+ left join sos."delivery_hand_shipped" on "delivery".delivery_uuid = "delivery_hand_shipped".delivery_uuid
+ left join sos."delivery_hand_delivered" on "delivery".delivery_uuid = "delivery_hand_delivered".delivery_uuid
+ left join sos."delivery_easypost" on "delivery".delivery_uuid = "delivery_easypost".delivery_uuid;
+
create or replace view sos.v_order as
select
"order".*,
@@ -117,13 +129,15 @@ create or replace view sos.v_order as
"address".*,
v_transaction_paid.transaction_amount_paid_cents,
v_payment.*,
- v_cart.*
+ v_cart.*,
+ v_delivery.*
from sos."order"
left join sos."transaction" on transaction_order_uuid = order_uuid
left join sos."coupon" on transaction_coupon_uuid = coupon_uuid
left join sos."address" on order_address_uuid = address_uuid
left join sos.v_transaction_paid on "transaction".transaction_uuid = v_transaction_paid.transaction_uuid
left join sos.v_payment on "transaction".transaction_uuid = payment_transaction_uuid
+ left join sos.v_delivery on "order".order_delivery_uuid = delivery_uuid
left join sos.v_cart on cart_uuid = transaction_cart_uuid;
create or replace view sos.v_config as
diff --git a/db/sql/3-functions.sql b/db/sql/3-functions.sql
index a73ac6d..25cb61e 100644
--- a/db/sql/3-functions.sql
+++ b/db/sql/3-functions.sql
@@ -896,3 +896,50 @@ begin
return query select * from sos.v_shipment where shipment_uuid = _shipment_uuid;
end; $function$;
+
+create or replace function sos.set_delivery_tracking(_order_uuid uuid, _tracking_number text, _date_shipped timestamptz)
+ returns setof sos.v_order
+ language plpgsql
+as $function$
+declare
+ _delivery_uuid uuid;
+begin
+ -- Ensure order has no delivery
+ select order_delivery_uuid into _delivery_uuid
+ from sos."order" where order_uuid = _order_uuid;
+ if _delivery_uuid is not null then
+ raise 'Order already has a delivery record';
+ end if;
+
+ -- Create delivery
+ insert into sos."delivery" (
+ delivery_type
+ ) values (
+ 'hand_shipped'
+ ) returning delivery_uuid into _delivery_uuid;
+
+ -- Default date
+ if _date_shipped is null then
+ _date_shipped := now();
+ end if;
+
+ -- Create delivery subtype record
+ insert into sos."delivery_hand_shipped" (
+ delivery_uuid,
+ delivery_type,
+ delivery_tracking_number,
+ delivery_date_shipped
+ ) values (
+ _delivery_uuid,
+ 'hand_shipped',
+ _tracking_number,
+ _date_shipped
+ );
+
+ -- Update order
+ update sos."order" set
+ order_delivery_uuid = _delivery_uuid
+ where order_uuid = _order_uuid;
+
+ return query select * from sos.v_order where order_uuid = _order_uuid;
+end; $function$;
diff --git a/package-lock.json b/package-lock.json
index 68c8aad..929f4ca 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2369,6 +2369,11 @@
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001027.tgz",
"integrity": "sha512-7xvKeErvXZFtUItTHgNtLgS9RJpVnwBlWX8jSo/BO8VsF6deszemZSkJJJA1KOKrXuzZH4WALpAJdq5EyfgMLg=="
},
+ "chain-function": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.1.tgz",
+ "integrity": "sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg=="
+ },
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -2379,6 +2384,11 @@
"supports-color": "^5.3.0"
}
},
+ "change-emitter": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz",
+ "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU="
+ },
"chokidar": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz",
@@ -3068,6 +3078,11 @@
"type": "^1.0.1"
}
},
+ "date-fns": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
+ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw=="
+ },
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@@ -3253,6 +3268,14 @@
"randombytes": "^2.0.0"
}
},
+ "dom-helpers": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
+ "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
+ "requires": {
+ "@babel/runtime": "^7.1.2"
+ }
+ },
"domain-browser": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
@@ -3326,6 +3349,14 @@
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
+ "encoding": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+ "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+ "requires": {
+ "iconv-lite": "~0.4.13"
+ }
+ },
"end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@@ -3734,6 +3765,27 @@
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
+ "fbjs": {
+ "version": "0.8.17",
+ "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
+ "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
+ "requires": {
+ "core-js": "^1.0.0",
+ "isomorphic-fetch": "^2.1.1",
+ "loose-envify": "^1.0.0",
+ "object-assign": "^4.1.0",
+ "promise": "^7.1.1",
+ "setimmediate": "^1.0.5",
+ "ua-parser-js": "^0.7.18"
+ },
+ "dependencies": {
+ "core-js": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
+ "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
+ }
+ }
+ },
"figgy-pudding": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz",
@@ -3849,6 +3901,23 @@
"readable-stream": "^2.3.6"
}
},
+ "focus-trap": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-4.0.2.tgz",
+ "integrity": "sha512-HtLjfAK7Hp2qbBtLS6wEznID1mPT+48ZnP2nkHzgjpL4kroYHg0CdqJ5cTXk+UO5znAxF5fRUkhdyfgrhh8Lzw==",
+ "requires": {
+ "tabbable": "^3.1.2",
+ "xtend": "^4.0.1"
+ }
+ },
+ "focus-trap-react": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-6.0.0.tgz",
+ "integrity": "sha512-mvEYxmP75PMx0vOqoIAmJHO/qUEvdTAdz6gLlEZyxxODnuKQdnKea2RWTYxghAPrV+ibiIq2o/GTSgQycnAjcw==",
+ "requires": {
+ "focus-trap": "^4.0.2"
+ }
+ },
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
@@ -4199,6 +4268,11 @@
"minimalistic-crypto-utils": "^1.0.1"
}
},
+ "hoist-non-react-statics": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz",
+ "integrity": "sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs="
+ },
"hosted-git-info": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz",
@@ -4538,6 +4612,26 @@
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
},
+ "isomorphic-fetch": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
+ "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
+ "requires": {
+ "node-fetch": "^1.0.1",
+ "whatwg-fetch": ">=0.10.0"
+ },
+ "dependencies": {
+ "node-fetch": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
+ "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
+ "requires": {
+ "encoding": "^0.1.11",
+ "is-stream": "^1.0.1"
+ }
+ }
+ }
+ },
"jest-worker": {
"version": "24.9.0",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz",
@@ -6775,11 +6869,45 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.6.tgz",
"integrity": "sha512-X1Y+0jR47ImDVr54Ab6V9eGk0Hnu7fVWGeHQSOXHf/C2pF9c6uy3gef8QUeuUiWlNb0i08InPSE5a/KJzNzw1Q=="
},
+ "react-infinite-calendar": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/react-infinite-calendar/-/react-infinite-calendar-2.3.1.tgz",
+ "integrity": "sha1-l/Y5g7PuHsDfR2s2uCJ8fbDtv4U=",
+ "requires": {
+ "classnames": "^2.2.5",
+ "date-fns": "^1.27.2",
+ "dom-helpers": "^3.2.1",
+ "prop-types": "^15.5.7",
+ "react-tiny-virtual-list": "^2.0.0",
+ "react-transition-group": "^1.1.3",
+ "recompose": "^0.22.0"
+ }
+ },
"react-is": {
"version": "16.8.6",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
"integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
},
+ "react-tiny-virtual-list": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/react-tiny-virtual-list/-/react-tiny-virtual-list-2.2.0.tgz",
+ "integrity": "sha512-MDiy2xyqfvkWrRiQNdHFdm36lfxmcLLKuYnUqcf9xIubML85cmYCgzBJrDsLNZ3uJQ5LEHH9BnxGKKSm8+C0Bw==",
+ "requires": {
+ "prop-types": "^15.5.7"
+ }
+ },
+ "react-transition-group": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz",
+ "integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==",
+ "requires": {
+ "chain-function": "^1.0.0",
+ "dom-helpers": "^3.2.0",
+ "loose-envify": "^1.3.1",
+ "prop-types": "^15.5.6",
+ "warning": "^3.0.0"
+ }
+ },
"read-pkg": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
@@ -6823,6 +6951,17 @@
"source-map": "~0.6.1"
}
},
+ "recompose": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.22.0.tgz",
+ "integrity": "sha1-8JnRg4WILKQdnuyJFxja3dwyBO8=",
+ "requires": {
+ "change-emitter": "^0.1.2",
+ "fbjs": "^0.8.1",
+ "hoist-non-react-statics": "^1.0.0",
+ "symbol-observable": "^1.0.4"
+ }
+ },
"reflect.ownkeys": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz",
@@ -7821,6 +7960,16 @@
}
}
},
+ "symbol-observable": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
+ },
+ "tabbable": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-3.1.2.tgz",
+ "integrity": "sha512-wjB6puVXTYO0BSFtCmWQubA/KIn7Xvajw0x0l6eJUudMG/EAiJvIUnyNX6xO4NpGrJ16lbD0eUseB9WxW0vlpQ=="
+ },
"tapable": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
@@ -8139,6 +8288,11 @@
"is-typedarray": "^1.0.0"
}
},
+ "ua-parser-js": {
+ "version": "0.7.21",
+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
+ "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ=="
+ },
"unfetch": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.1.0.tgz",
@@ -8369,6 +8523,14 @@
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
},
+ "warning": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
+ "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"watchpack": {
"version": "2.0.0-beta.5",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.0.0-beta.5.tgz",
diff --git a/package.json b/package.json
index 12f6062..4584c61 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
"express-validator": "^6.4.0",
+ "focus-trap-react": "^6.0.0",
"join-js": "^1.0.2",
"luxon": "^1.22.0",
"multer": "^1.4.2",
@@ -36,6 +37,7 @@
"pg": "^7.18.1",
"react": "^16.12.0",
"react-dom": "^16.12.0",
+ "react-infinite-calendar": "^2.3.1",
"sharp": "^0.24.1",
"stripe": "^8.44.0",
"use-measure": "^0.3.0",
diff --git a/pages/_app.js b/pages/_app.js
index 6d996a0..1b72cb6 100644
--- a/pages/_app.js
+++ b/pages/_app.js
@@ -11,6 +11,7 @@ import AdminNav from '~/components/admin/nav'
import ErrorBoundary from '~/components/errorBoundary'
import "~/styles/layout.css"
+import 'react-infinite-calendar/styles.css'
Layout.getInitialProps = async ({Component, ctx}) => {
// Configure axios instance
diff --git a/pages/admin/orders/[id].js b/pages/admin/orders/[id].js
deleted file mode 100644
index f2cbc01..0000000
--- a/pages/admin/orders/[id].js
+++ /dev/null
@@ -1,19 +0,0 @@
-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/[id]/index.js b/pages/admin/orders/[id]/index.js
new file mode 100644
index 0000000..27df8a8
--- /dev/null
+++ b/pages/admin/orders/[id]/index.js
@@ -0,0 +1,54 @@
+import React from 'react'
+import Router from 'next/router'
+import ActionBar from '~/components/admin/actionBar'
+import Table from '~/components/table'
+import {DateTime} from 'luxon'
+import {Button} from '@rmwc/button'
+
+Order.getInitialProps = async ({ctx: {axios, query: {id}}}) => {
+ const {data: order} = await axios.get(`/api/orders/${id}`)
+ return {order}
+}
+
+export default function Order({order}){
+ const lastTransaction = getLastTransaction(order)
+
+ return (
+ <>
+
+
+
Order for {capitalizeName(order.address.name)}
+
Purchased {DateTime.fromISO(lastTransaction.completion_time).toFormat('LLLL dd, h:mm a')}
+
+
{JSON.stringify(order, null, 2)}
+ >
+ )
+}
+
+function capitalizeName(string){
+ return string
+ .split(' ')
+ .map(word => word[0].toUpperCase() + word.slice(1).toLowerCase())
+ .join(' ')
+}
+
+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')
+}
+
+const formatMoney = money => {
+ if (money === undefined || money === null) return null;
+
+ return '$' + (money / 100).toFixed(2)
+}
diff --git a/pages/admin/orders/[id]/ship/tracking.js b/pages/admin/orders/[id]/ship/tracking.js
new file mode 100644
index 0000000..2246bd5
--- /dev/null
+++ b/pages/admin/orders/[id]/ship/tracking.js
@@ -0,0 +1,24 @@
+import React, {useState} from 'react'
+import Router from 'next/router'
+
+import ActionBar from '~/components/admin/actionBar'
+import {FormController, DateInput, Input, Button} from '~/components/form'
+
+EnterTracking.getInitialProps = async ({ctx: {axios, query: {id}}}) => {
+ const {data: order} = await axios.get(`/api/orders/${id}`)
+ return {order}
+}
+
+export default function EnterTracking({order}){
+ return (
+ <>
+
+
+
Router.push(`/admin/orders/${order.uuid}`)} url={`/api/orders/${order.uuid}/ship/tracking`}>
+ val.length > 0} type="text" name="code" hint="Please enter the USPS Tracking code" />
+
+
+
+ >
+ )
+}
diff --git a/pages/admin/orders/index.js b/pages/admin/orders/index.js
index 7abc502..df905de 100644
--- a/pages/admin/orders/index.js
+++ b/pages/admin/orders/index.js
@@ -10,9 +10,28 @@ Orders.getInitialProps = async ({ctx}) => {
}
export default function Orders({orders}){
+ const unshippedOrders = orders.filter(order => !order.delivery)
+ const shippedOrders = orders.filter(order => order.delivery).reverse()
+
return (
<>
+
Need to ship:
+
+
+ }
+ ]}
+ rows={unshippedOrders.map(order => ({id: order.uuid, ...order}))}
+ />
+
+ Shipped:
Router.push(`/admin/orders/${row.id}`)}>Details
}
]}
- rows={orders.map(order => ({id: order.uuid, ...order}))}
+ rows={shippedOrders.map(order => ({id: order.uuid, ...order}))}
/>
>
)