Cart protections

main
Ashelyn Dawn 5 years ago
parent 5ccda75fff
commit a8543d11df

@ -1,12 +1,23 @@
import React from 'react' import React from 'react'
import Link from 'next/link' import Link from 'next/link'
import useCart from '../../hooks/useCart'
import styles from './style.module.css' import styles from './style.module.css'
import axios from 'axios'
export default function Card({item, numberInCart}) { export default function Card({item}) {
const [cart, setCart] = useCart()
const numInCart = cart?.items?.find(i => i.item.uuid === item.uuid)?.count || 0
let featuredImage = item.images.filter(i=>i.featured)[0] let featuredImage = item.images.filter(i=>i.featured)[0]
if(!featuredImage) featuredImage = item.images[0] if(!featuredImage) featuredImage = item.images[0]
const addToCart = async ev => {
if(ev) ev.preventDefault()
const {data: newCart} = await axios.post(`/api/cart/add/${item.uuid}`, {count: 1})
setCart(newCart)
}
return ( return (
<div className={styles.card}> <div className={styles.card}>
<h3><Link href={`/store/item/${item.urlslug}`}><a>{item.name}</a></Link></h3> <h3><Link href={`/store/item/${item.urlslug}`}><a>{item.name}</a></Link></h3>
@ -24,14 +35,19 @@ export default function Card({item, numberInCart}) {
: 'Currently out of stock' : 'Currently out of stock'
} }
</li> </li>
<li><Link href={`/store/sock/${item.urlSlug}`}><a>Details</a></Link></li> <li><Link href={`/store/item/${item.urlslug}`}><a>Details</a></Link></li>
{ {
item.number_in_stock > 0 && ( item.number_in_stock > 0 && (
<li> <li>
<a disabled={!(item.number_in_stock > 0)}>Add to Cart</a> <button className="buttonLink" onClick={addToCart}>Add to Cart</button>
</li> </li>
) )
} }
{numInCart > 0 && (
<li>
({numInCart} in <Link href="/store/cart"><a>cart</a></Link>)
</li>
)}
</ul> </ul>
</div> </div>
) )

@ -38,7 +38,7 @@
border-bottom:solid 1px black; border-bottom:solid 1px black;
} }
.card a:hover{ .card a:hover, .card button:global(.buttonLink):hover{
color: #760c88; color: #760c88;
border-color: #760c88; border-color: #760c88;
transition: .2s ease-in-out; transition: .2s ease-in-out;
@ -80,7 +80,8 @@
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
} }
.card p, .card a{ .card p, .card a, .card button:global(.buttonLink){
font-family: inherit;
color:black; color:black;
opacity:.87; opacity:.87;
} }

@ -2,10 +2,12 @@ import Link from "next/link"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import React from "react" import React from "react"
import useCart from '../../hooks/useCart'
import logo from './sos-logo.png' import logo from './sos-logo.png'
import styles from './style.module.css' import styles from './style.module.css'
const Header = ({user, cart}) => { const Header = ({user}) => {
const [cart] = useCart()
const cartSize = (cart && cart.items.length) ? cart.items.map(item => item.count).reduce((sum, current) => (sum + current)) : 0 const cartSize = (cart && cart.items.length) ? cart.items.map(item => item.count).reduce((sum, current) => (sum + current)) : 0
return ( return (

@ -14,8 +14,8 @@ create or replace view sos.v_item as
( (
select select
stockchange_item_uuid, stockchange_item_uuid,
sum(case when stockchange_direction = 'added' then stockchange_change end)::int4 as num_added, coalesce(sum(case when stockchange_direction = 'added' then stockchange_change end)::int4, 0) as num_added,
sum(case when stockchange_direction = 'subtracted' then stockchange_change end)::int4 as num_removed coalesce(sum(case when stockchange_direction = 'subtracted' then stockchange_change end)::int4, 0) as num_removed
from sos."item_stockchange" from sos."item_stockchange"
group by stockchange_item_uuid group by stockchange_item_uuid
) stock_counts ) stock_counts

@ -0,0 +1,6 @@
import React, {useContext} from 'react'
const CartContext = React.createContext(null);
export const CartContextProvider = CartContext.Provider;
export default () => useContext(CartContext)

@ -2,6 +2,7 @@ import React, {useState} from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import axios from 'axios' import axios from 'axios'
import {CartContextProvider} from '../hooks/useCart'
import Header from '~/components/header' import Header from '~/components/header'
import Footer from '~/components/footer' import Footer from '~/components/footer'
import "../styles/layout.css" import "../styles/layout.css"
@ -30,14 +31,14 @@ Layout.getInitialProps = async ({Component, ctx}) => {
} }
function Layout({ Component, pageProps, user, cart: _cart }){ function Layout({ Component, pageProps, user, cart: _cart }){
const [cart, setCart] = useState(_cart) const cartState = useState(_cart)
return ( return (
<> <CartContextProvider value={cartState}>
<Header user={user} cart={cart} /> <Header user={user} />
<main><Component {...{user, cart, setCart, ...pageProps}} /></main> <main><Component {...{user, ...pageProps}} /></main>
<Footer/> <Footer/>
</> </CartContextProvider>
) )
} }

@ -1,12 +1,16 @@
import React from 'react' import React from 'react'
import Link from 'next/link'
import axios from 'axios' import axios from 'axios'
import Head from 'next/head' import Head from 'next/head'
import useCart from '../../hooks/useCart'
import {FormController, Input, Button} from '~/components/form' import {FormController, Input, Button} from '~/components/form'
import Table from '~/components/table' import Table from '~/components/table'
export default function Cart({cart, setCart}){ export default function Cart(){
const [cart, setCart] = useCart()
const numItems = (cart?.items) ? cart.items.length : 0 const numItems = (cart?.items) ? cart.items.length : 0
const allInStock = !cart?.items.some(item => !item.number_in_stock || item.number_in_stock < 1)
const handleRemove = id => async ev => { const handleRemove = id => async ev => {
if(ev) ev.preventDefault() if(ev) ev.preventDefault()
@ -28,7 +32,12 @@ export default function Cart({cart, setCart}){
numItems > 0 numItems > 0
?<Table ?<Table
columns={[ columns={[
{name: 'Item', extractor: row => row.item.name}, {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>}
</>
)},
{name: 'Quantity in Cart', extractor: row => row.count}, {name: 'Quantity in Cart', extractor: row => row.count},
{name: 'Price Each', extractor: row => '$' + (row.item.price_cents / 100).toFixed(2)}, {name: 'Price Each', extractor: row => '$' + (row.item.price_cents / 100).toFixed(2)},
{name: 'Total Price', extractor: row => '$' + (row.count * row.item.price_cents / 100).toFixed(2)}, {name: 'Total Price', extractor: row => '$' + (row.count * row.item.price_cents / 100).toFixed(2)},
@ -63,7 +72,7 @@ export default function Cart({cart, setCart}){
} }
<FormController> <FormController>
<Button enabled={!!(cart?.items?.length)} type="submit">Proceed to Checkout</Button> <Button enabled={!!(cart?.items?.length) && allInStock} type="submit">Proceed to Checkout</Button>
</FormController> </FormController>
</> </>
) )

@ -4,6 +4,7 @@ import Router from 'next/router'
import {FormController, NumInput, Button} from '~/components/form' import {FormController, NumInput, Button} from '~/components/form'
import isNum from 'validator/lib/isNumeric' import isNum from 'validator/lib/isNumeric'
import useCart from '../../../hooks/useCart'
import styles from './style.module.css' import styles from './style.module.css'
Item.getInitialProps = async function({ctx: {axios, query: {slug}}}){ Item.getInitialProps = async function({ctx: {axios, query: {slug}}}){
@ -19,7 +20,8 @@ Item.getInitialProps = async function({ctx: {axios, query: {slug}}}){
} }
// TODO: Modal with full image size on clicking preview // TODO: Modal with full image size on clicking preview
export default function Item({item, cart, setCart}){ export default function Item({item}){
const [cart, setCart] = useCart()
// Pick first one with featured flag or 0 // Pick first one with featured flag or 0
const featuredIndex = item.images.reduce((p, im, i) => ((p !== undefined) ? p : (im.featured ? i : undefined)), undefined) || 0 const featuredIndex = item.images.reduce((p, im, i) => ((p !== undefined) ? p : (im.featured ? i : undefined)), undefined) || 0
const [selectedIndex, setSelected] = useState(featuredIndex); const [selectedIndex, setSelected] = useState(featuredIndex);
@ -55,7 +57,12 @@ export default function Item({item, cart, setCart}){
<FormController className={styles.form} url={`/api/cart/add/${item.uuid}`} afterSubmit={(cart)=>{setCart(cart); Router.push('/store/cart')}}> <FormController className={styles.form} url={`/api/cart/add/${item.uuid}`} afterSubmit={(cart)=>{setCart(cart); Router.push('/store/cart')}}>
<NumInput label="Add to cart" name="count" initialValue="1" minimum="1" validate={value=>isNum(value, {no_symbols: true})} /> <NumInput label="Add to cart" name="count" initialValue="1" minimum="1" validate={value=>isNum(value, {no_symbols: true})} />
<Button type="submit">Add to cart</Button> {
item.number_in_stock > 0
? <Button type="submit">Add to cart</Button>
: <Button enabled={false} type="submit">Out of stock</Button>
}
</FormController> </FormController>
<div className={styles.notes}> <div className={styles.notes}>

Loading…
Cancel
Save