Preorder: Sales flow allows pre-order and has adequate warnings

Still need to update payment so it can make proper stockchange records
main
Ashelyn Dawn 3 years ago
parent bfddebfe23
commit f9d62847ba

@ -18,6 +18,14 @@ export default function Card({item}) {
setCart(newCart)
}
const canPurchase = item.number_in_stock > 0 || item.preorder_availability_date !== undefined
const availabilityText =
item.number_in_stock > 0
? `${item.number_in_stock} in stock`
: item.preorder_availability_date && (-1 * item.number_in_stock < item.preorder_maximum)
? 'Available for pre-order'
: 'Currently out of stock'
return (
<div className={styles.card}>
<h3><Link href={`/store/item/${item.urlslug}`}><a>{item.name}</a></Link></h3>
@ -32,15 +40,11 @@ export default function Card({item}) {
<div className={styles['card-text']}>{item.description}</div>
<ul className={styles['card-details'] + (item.number_in_stock > 0 ? '' : ' ' + styles['out-of-stock'])}>
<li className={styles['number-in-stock']}>
{
item.number_in_stock > 0
? `${item.number_in_stock} in stock`
: 'Currently out of stock'
}
{availabilityText}
</li>
<li><Link href={`/store/item/${item.urlslug}`}><a>Details</a></Link></li>
{
item.number_in_stock > 0 && (
canPurchase && (
<li>
<button className="buttonLink" onClick={addToCart}>Add to Cart</button>
</li>

@ -3,17 +3,30 @@ import Router from 'next/router'
import Link from 'next/link'
import axios from 'axios'
import Head from 'next/head'
import {DateTime} from 'luxon'
import useCart, {useSetCart} from '~/hooks/useCart'
import {FormController, Input, Button} from '~/components/form'
import {FormController, Button} from '~/components/form'
import Table from '~/components/table'
export default function Cart(){
const cart = useCart()
const setCart = useSetCart()
const numItems = (cart?.items) ? cart.items.length : 0
const allInStock = !cart?.items?.some(item => !item.item.number_in_stock || item.item.number_in_stock < 1)
const allHaveEnough = !cart?.items?.some(item => item.count > item.item.number_in_stock)
cart?.items?.forEach(({item, count}) => {
const stock = item.stock = {}
stock.is_preorder = item.preorder_availability_date && count > item.number_in_stock
stock.available = stock.is_preorder ? item.preorder_maximum - -Math.min(0, item.number_in_stock) : item.number_in_stock
stock.has_enough = count <= stock.available
})
const allInStock = !cart?.items?.some(item => !(item.item.number_in_stock > 0 || item.item.preorder_availability_date) )
const allHaveEnough = !cart?.items?.some(item => !item.item.stock.has_enough)
const preorderItems = cart?.items?.filter(({item, count}) => count > item.number_in_stock && item.preorder_availability_date).map(({item}) => DateTime.fromISO(item.preorder_availability_date)) || []
preorderItems.sort((b, a) => a < b ? -1 : a > b ? 1 : 0)
const latestPreorder = preorderItems[0]?.toLocaleString(DateTime.DATE_FULL)
const handleRemove = id => async ev => {
if(ev) ev.preventDefault()
@ -42,8 +55,23 @@ export default function Cart(){
{name: 'Item', extractor: row => (
<>
<Link href={`/store/item/${row.item.urlslug}`}><a>{row.item.name}</a></Link>
{(!row.item.number_in_stock || row.item.number_in_stock < 1) && <strong style={{marginLeft: '6px'}}>Out of stock</strong>}
{(row.item.number_in_stock > 0 && row.count > row.item.number_in_stock) && <strong style={{marginLeft: '6px'}}>Not enough in stock</strong>}
{(() => {
const {stock} = row.item;
if(stock.is_preorder && !stock.has_enough)
return <strong style={{marginLeft: '6px'}}>Not enough expected stock</strong>
if(stock.is_preorder && stock.has_enough)
return <strong style={{marginLeft: '6px'}}>(Pre-order: available {DateTime.fromISO(row.item.preorder_availability_date).toLocaleString(DateTime.DATE_SHORT)})</strong>
if(!row.item.number_in_stock || row.item.number_in_stock < 1)
return <strong style={{marginLeft: '6px'}}>Out of stock</strong>
if (!stock.has_enough)
return <strong style={{marginLeft: '6px'}}>Not enough in stock</strong>
return null
})()}
</>
)},
{name: 'Quantity in Cart', extractor: row => row.count},
@ -92,6 +120,13 @@ export default function Cart(){
return <Button type="submit">Proceed to Checkout</Button>
})()}
{latestPreorder && allHaveEnough && (
<p className="warning">
<strong>Note: </strong>
Your cart contains one or more pre-order items. If you place an order with these items in your cart, it will not ship
until at least {latestPreorder}
</p>
)}
</FormController>
</>
)

@ -16,6 +16,17 @@ export default function CheckoutComplete({order}){
const items = order.transactions.map(transaction => transaction.cart.items).flat()
const latestTransaction = order.transactions.sort(sortTransactions)[0]
const preorderItems = items.filter(({item}) => item.number_in_stock < 0 && item.preorder_availability_date).map(({item}) => DateTime.fromISO(item.preorder_availability_date)) || []
preorderItems.sort((b, a) => a < b ? -1 : a > b ? 1 : 0)
const latestPreorder = preorderItems[0]?.toLocaleString(DateTime.DATE_FULL)
let shippingEstimate = 'Orders typically are shipped within 3-5 business days of purchase.'
if (latestPreorder) {
shippingEstimate = `Your order will be shipped when all of its items become available (estimated ${latestPreorder.toLocaleString(DateTime.DATE_SHORT)}).`
}
let email = null
const stripePayment = latestTransaction.payments.find(p => p.stripe !== null)
@ -55,8 +66,7 @@ export default function CheckoutComplete({order}){
? (
<>
<p>
Orders typically are shipped within 3-5 business days of purchase. You
will receive an email with a tracking number when your order ships.
{shippingEstimate} You will receive an email with a tracking number when your order ships.
</p>
<p>
Your tracking number will be sent to: <strong>
@ -64,7 +74,7 @@ export default function CheckoutComplete({order}){
</strong>
</p>
<p>
If you do not receive an email within 1 week,
If you do not receive an email {latestPreorder ? 'within a week of the shipping estimate' : 'within 1 week'},
please <Link href="/contact"><a>contact us</a></Link>. In any
correspondence please be sure to include your order number
(#{order.number}).
@ -72,9 +82,7 @@ export default function CheckoutComplete({order}){
</>
)
: (
<p>
Orders typically are shipped within 3-5 business days of purchase.
</p>
<p>{shippingEstimate}</p>
)
}
</>

@ -8,6 +8,7 @@ import {Input, Button} from '~/components/form'
import useUser from '~/hooks/useUser'
import axios from 'axios'
import { loadStripe } from '@stripe/stripe-js';
import {DateTime} from 'luxon'
const { publicRuntimeConfig: {STRIPE_PUBLIC_KEY} } = getConfig()
const stripePromise = loadStripe(STRIPE_PUBLIC_KEY);
@ -36,6 +37,10 @@ export default function CheckoutSummary({order: _order, addresses}){
? item_total_price + (free_shipping ? 0 : shipping_price) + tax_price - (coupon_effective_discount || 0)
: null
const preorderItems = currentTransaction.cart.items.filter(({item, count}) => count > item.number_in_stock && item.preorder_availability_date).map(({item}) => DateTime.fromISO(item.preorder_availability_date)) || []
preorderItems.sort((b, a) => a < b ? -1 : a > b ? 1 : 0)
const latestPreorder = preorderItems[0]?.toLocaleString(DateTime.DATE_FULL)
const formatMoney = money => {
if (money === undefined || money === null) return null;
@ -245,6 +250,16 @@ export default function CheckoutSummary({order: _order, addresses}){
</div>
</div>
</div>
{latestPreorder && (
<div className={styles.checkoutSection}>
<p className="warning">
<strong>Note: </strong>
Your cart contains one or more pre-order items. If you complete this order, it will not ship
until at least {latestPreorder}
</p>
</div>
)}
</>
)
}

@ -3,6 +3,7 @@ import Head from 'next/head'
import Router from 'next/router'
import {FormController, IntegerInput, Button} from '~/components/form'
import isNum from 'validator/lib/isNumeric'
import {DateTime} from 'luxon'
import useCart, {useSetCart} from '~/hooks/useCart'
import styles from './style.module.css'
@ -16,6 +17,10 @@ Item.getInitialProps = async function({ctx: {axios, query: {slug}}}){
throw err;
}
if(item.preorder_availability_date) {
item.preorder_availability_date = DateTime.fromISO(item.preorder_availability_date).toLocaleString(DateTime.DATE_FULL)
}
return {item}
}
@ -29,6 +34,18 @@ export default function Item({item}){
const numInCart = cart?.items?.find(i => i.item.uuid === item.uuid)?.count || 0
const numberInStock = item.number_in_stock
const numberPreorderSlots = (item.preorder_maximum || 0) - (-Math.min(0, item.number_in_stock))
const availabilityText =
numberInStock > 0
? `${numberInStock} in stock`
: item.preorder_availability_date && numberPreorderSlots > 0
? `${numberPreorderSlots} available for pre-order`
: 'Currently out of stock'
const totalAvailable = (numberInStock > 0 ? numberInStock : numberPreorderSlots) - numInCart
return (
<>
<Head>
@ -65,9 +82,9 @@ export default function Item({item}){
{ item.description.split('\n').map(p=>p.trim()).filter(p=>p !== '').map((p,i)=><p key={i}>{p}</p>) }
<FormController className={styles.form} url={`/api/cart/add/${item.uuid}`} afterSubmit={(cart)=>{setCart(cart); Router.push('/store/cart')}}>
<IntegerInput label="Add to cart" name="count" initialValue="1" minimum="1" validate={value=>isNum(value, {no_symbols: true})} />
<IntegerInput label="Add to cart" name="count" initialValue="1" minimum="1" maximum={totalAvailable.toFixed(0)} validate={value=>isNum(value, {no_symbols: true}) && parseInt(value) <= totalAvailable} />
{
item.number_in_stock > 0
totalAvailable > 0
? <Button type="submit">Add to cart</Button>
: <Button enabled={false} type="submit">Out of stock</Button>
}
@ -77,14 +94,18 @@ export default function Item({item}){
<div className={styles.notes}>
<span>Price: ${(parseInt(item.price_cents) / 100).toFixed(2)} each</span>
<span>
{
item.number_in_stock
? item.number_in_stock + ' in stock'
: 'Out of stock'
}
{availabilityText}
</span>
<span>{numInCart} in cart</span>
<span>{numInCart} in <button style={{color: "white"}} className="buttonLink" onClick={() => Router.push(`/store/cart`)}>cart</button></span>
</div>
{numberInStock < 1 && item.preorder_availability_date && numberPreorderSlots > 0 && (
<p className="warning">
<strong>Note: </strong>
This is a pre-order item. If you place an order with this item in your cart, it will not ship
until at least {item.preorder_availability_date}
</p>
)}
</div>
</div>
<div className={styles.moreDetails}>

Loading…
Cancel
Save