Cart page, app passes cart update

main
Ashelyn Dawn 5 years ago
parent 30c6ae98a4
commit 9ce70cefe2

@ -29,3 +29,11 @@ router.post('/add/:item_uuid', parseJSON, cartAddValidators, async (req, res) =>
res.json(cart) res.json(cart)
}) })
router.post('/remove/:item_uuid', async (req, res) => {
if(!req.cart) return res.json(null)
const cart = await db.cart.removeItemFromCart(req.cart.uuid, req.params.item_uuid);
res.json(cart)
})

@ -4,7 +4,8 @@ const sessionMiddleware = (req, res, next)=>{
(async ()=>{ (async ()=>{
let session = await db.session.validate(req.session.uuid); let session = await db.session.validate(req.session.uuid);
if(!session) return; if(!session)
return req.session.uuid = null;
// Update last active // Update last active
session = await db.session.update(req.session.uuid); session = await db.session.update(req.session.uuid);

@ -85,7 +85,9 @@ export const FormController = function FormController({children, className, url
name: child.props.name, name: child.props.name,
validate: child.props.validate, validate: child.props.validate,
value: child.props.initialValue || "", value: child.props.initialValue || "",
isValid: child.props.initialValue ? child.props.validate(child.props.initialValue): false, isValid: child.props.validate
?(child.props.initialValue ? child.props.validate(child.props.initialValue): false)
:true,
touched: false touched: false
} }
}) })
@ -106,7 +108,8 @@ export const FormController = function FormController({children, className, url
if(url) if(url)
try { try {
await axios({ method, url, data }) const {data: response} = await axios({ method, url, data })
return afterSubmit(response)
} catch (err) { } catch (err) {
if(!err.response || err.response.status !== 422) throw err; if(!err.response || err.response.status !== 422) throw err;
@ -123,7 +126,8 @@ export const FormController = function FormController({children, className, url
const _children = React.Children.map(children, child => { const _children = React.Children.map(children, child => {
if(child.type === Button && child.props.type.toLowerCase() === 'submit') if(child.type === Button && child.props.type.toLowerCase() === 'submit')
return React.cloneElement(child, { return React.cloneElement(child, {
enabled: !Object.values(state.fields).some(field=>!field.isValid), // Allow enabled prop to disable, but not solely enable
enabled: child.props.enabled !== false && !Object.values(state.fields).some(field=>!field.isValid),
onClick: handleSubmit onClick: handleSubmit
}) })

@ -7,7 +7,7 @@ export default function Input({label, error, hint, type, name, value, onChange,
<div className={styles.formElementContainer}> <div className={styles.formElementContainer}>
<label htmlFor={name}>{label}:</label> <label htmlFor={name}>{label}:</label>
<input className={(isValid && !error)?'':styles.invalid} type={type} name={name} value={value} onChange={onChange} onBlur={onBlur} /> <input className={(isValid && !error)?'':styles.invalid} type={type} name={name} value={value} onChange={onChange} onBlur={onBlur} />
<span className={styles.hint}>{error || (isValid ? '' : hint)}</span> {hint && <span className={styles.hint}>{error || (isValid ? '' : hint)}</span>}
</div> </div>
) )
} }

@ -18,7 +18,7 @@ export default function Input({label, minimum = 0, maximum, error, hint, name, v
<input type="text" name={name} value={value} onChange={onChange} onBlur={onBlur} /> <input type="text" name={name} value={value} onChange={onChange} onBlur={onBlur} />
<button type="button" disabled={value===maximum} onClick={valueUp}>+</button> <button type="button" disabled={value===maximum} onClick={valueUp}>+</button>
</div> </div>
<span className={styles.hint}>{error || (isValid ? '' : hint)}</span> {hint && <span className={styles.hint}>{error || (isValid ? '' : hint)}</span>}
</div> </div>
) )
} }

@ -0,0 +1,33 @@
import styles from './table.module.css'
export default function Table({columns, rows, foot}) {
return (
<table className={styles.table}>
<thead>
<tr>
{columns.map(column=>
<th key={column.name}>{column.name}</th>
)}
</tr>
</thead>
<tbody>
{rows.map(row=>
<tr key={row.id} className={row.class}>
{columns.map(column=>
<td key={column.name}>{column.extractor(row)}</td>
)}
</tr>
)}
</tbody>
{foot && <tfoot>
<tr>
{foot.map((item, i) =>
<th key={i}>
{item}
</th>
)}
</tr>
</tfoot>}
</table>
)
}

@ -0,0 +1,24 @@
.table {
width: calc(100% - 100px);
max-width: 800px;
margin: 0 auto;
background: white;
border-collapse: collapse;
border: solid 1px gray;
}
.table td, .table th {
border-top: solid 1px gray;
border-bottom: solid 1px gray;
text-align: left;
}
.table th {
padding: 5px 15px;
font-family: 'Cormorant SC',serif;
font-weight: bold;
}
.table td {
padding: 15px;
}

@ -35,13 +35,12 @@ cart.addItemToCart = async (cart_uuid, item_uuid, amount) => {
return joinjs.map(rows, mappings, 'cartMap', 'cart_')[0]; return joinjs.map(rows, mappings, 'cartMap', 'cart_')[0];
} }
cart.removeItemFromCart = async (cart_uuid, item_uuid, amount) => { cart.removeItemFromCart = async (cart_uuid, item_uuid) => {
const query = { const query = {
text: 'select * from sos.remove_item_from_cart($1, $2, $3)', text: 'select * from sos.remove_item_from_cart($1, $2)',
values: [ values: [
cart_uuid, cart_uuid,
item_uuid, item_uuid
amount
] ]
} }

@ -284,3 +284,33 @@ begin
-- Return cart -- Return cart
return query select * from sos.v_cart where cart_uuid = _cart_uuid; return query select * from sos.v_cart where cart_uuid = _cart_uuid;
end; $function$; end; $function$;
create or replace function sos.remove_item_from_cart(_cart_uuid uuid, _item_uuid uuid)
returns setof sos.v_cart
language plpgsql
as $function$
declare
_row_count integer;
begin
-- Check if we already have a row for this combination
select
count(*)
into _row_count
from sos.cart_item
where cart_item_cart_uuid = _cart_uuid
and cart_item_item_uuid = _item_uuid
group by cart_item_uuid, cart_item_count;
-- If we do not have a row, just exit
if _row_count < 1 then
return query select * from sos.v_cart where cart_uuid = _cart_uuid;
end if;
-- If we have a row, then delete it
delete from sos.cart_item
where cart_item_cart_uuid = _cart_uuid
and cart_item_item_uuid = _item_uuid;
-- Return cart
return query select * from sos.v_cart where cart_uuid = _cart_uuid;
end; $function$;

@ -1,4 +1,4 @@
import React from "react" import React, {useState} from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import axios from 'axios' import axios from 'axios'
@ -29,11 +29,13 @@ Layout.getInitialProps = async ({Component, ctx}) => {
return {pageProps, user, cart} return {pageProps, user, cart}
} }
function Layout({ Component, pageProps, user, cart }){ function Layout({ Component, pageProps, user, cart: _cart }){
const [cart, setCart] = useState(_cart)
return ( return (
<> <>
<Header user={user} cart={cart} /> <Header user={user} cart={cart} />
<main><Component {...{user, cart, ...pageProps}} /></main> <main><Component {...{user, cart, setCart, ...pageProps}} /></main>
<Footer/> <Footer/>
</> </>
) )

@ -1,18 +1,51 @@
import React, {useState} from 'react' import React from 'react'
import axios from 'axios'
import Head from 'next/head' import Head from 'next/head'
// import styles from './cart.module.css' import {FormController, Input, Button} from '~/components/form'
import Table from '~/components/table'
export default function Cart({cart, setCart}){
const handleRemove = id => async ev => {
if(ev) ev.preventDefault()
const {data} = await axios.post(`/api/cart/remove/${id}`)
setCart(data)
}
// TODO: Cart page
export default function Cart({cart}){
return ( return (
<> <>
<Head><title>Cart</title></Head> <Head><title>Cart</title></Head>
<div> <>
<pre> <h2>Cart</h2>
{JSON.stringify(cart, null, 2)} <Table
</pre> columns={[
</div> {name: 'Item', extractor: row => row.item.name},
{name: 'Quantity in Cart', extractor: row => row.count},
{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: '', extractor: row =>
<button className="buttonLink" onClick={handleRemove(row.item.uuid)}>Remove</button>
}
]}
rows={cart?.items?.map(row=>({
...row,
id: row.item.uuid
}))}
foot={[
'Total:',
cart?.items.map(r=>r.count).reduce((a,b) => (a+b), 0) || 0,
'',
'$' + ((cart?.items.map(r=>r.count * r.item.price_cents).reduce((a,b) => (a+b), 0) || 0) / 100).toFixed(2),
''
]}
/>
<FormController>
<Input label="Coupon Code" type="text" name="coupon"/>
<Button enabled={!!(cart?.items?.length)} type="submit">Proceed to Checkout</Button>
</FormController>
</>
</> </>
) )
} }

@ -19,12 +19,12 @@ 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}){ export default function Item({item, cart, setCart}){
// 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);
const numInCart = cart?.items.find(i => i.item.uuid === item.uuid)?.count || 0 const numInCart = cart?.items?.find(i => i.item.uuid === item.uuid)?.count || 0
return ( return (
<> <>
@ -53,8 +53,8 @@ export default function Item({item, cart}){
<h2>{item.name}</h2> <h2>{item.name}</h2>
{ item.description.split('\n').map(p=>p.trim()).filter(p=>p !== '').map((p,i)=><p key={i}>{p}</p>) } { 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={()=>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})} hint="Enter the number of socks to add to your cart" /> <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> <Button type="submit">Add to cart</Button>
</FormController> </FormController>

@ -29,6 +29,11 @@ main {
main h2 { main h2 {
font-family: 'Cormorant SC',serif; font-family: 'Cormorant SC',serif;
font-size: 36px; font-size: 36px;
font-weight: normal;
}
main > h2:first-child {
text-align: center;
} }
main { main {
@ -46,6 +51,21 @@ main {
column-count: 3; column-count: 3;
} }
button.buttonLink {
background: none;
border: none;
color: blue;
text-decoration: underline;
font-family: 'Cormorant Infant', serif;
font-size: 16px;
cursor: pointer;
outline: none;
}
button.buttonLink:focus {
color: rgb(0, 0, 175);
}
@media (max-width:1200px){ @media (max-width:1200px){
.cardContainer{ .cardContainer{
padding:0 2%; padding:0 2%;

Loading…
Cancel
Save