Skip to content

Commit d175042

Browse files
authored
Merge pull request #18 from imranhsayed/feature/add-cart-qty-to-bag
Add Product To Bag
2 parents 318c992 + 2934ac6 commit d175042

File tree

11 files changed

+182
-34
lines changed

11 files changed

+182
-34
lines changed

pages/cart.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const Cart = () => {
2+
return <h1>Cart</h1>;
3+
}
4+
5+
export default Cart

pages/index.js

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,22 @@
11
/**
22
* Internal Dependencies.
33
*/
4-
import Header from '../src/components/layouts/header';
5-
import Footer from '../src/components/layouts/footer';
64
import Products from '../src/components/products';
7-
import { GET_PRODUCTS_ENDPOINT, HEADER_FOOTER_ENDPOINT } from '../src/utils/constants/endpoints';
5+
import { HEADER_FOOTER_ENDPOINT } from '../src/utils/constants/endpoints';
86

97
/**
108
* External Dependencies.
119
*/
1210
import axios from 'axios';
1311
import { getProductsData } from '../src/utils/products';
12+
import Layout from '../src/components/layout';
1413

1514
export default function Home({ headerFooter, products }) {
1615

17-
const { header, footer } = headerFooter || {};
18-
1916
return (
20-
<div >
21-
<Header header={header}/>
22-
<main className="container mx-auto py-4">
23-
<Products products={products}/>
24-
</main>
25-
26-
<Footer footer={footer}/>
27-
</div>
17+
<Layout headerFooter={headerFooter || {}}>
18+
<Products products={products}/>
19+
</Layout>
2820
)
2921
}
3022

src/components/cart/add-to-cart.js

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,46 @@
11
import { isEmpty } from 'lodash';
2-
import {addToCart} from '../../utils/cart';
2+
import { addToCart } from '../../utils/cart';
3+
import { useContext, useState } from 'react';
4+
import { AppContext } from '../context';
5+
import Link from 'next/link';
6+
import cx from 'classnames';
37

48
const AddToCart = ( { product } ) => {
59

10+
const [ cart, setCart ] = useContext( AppContext );
11+
const [ isAddedToCart, setIsAddedToCart ] = useState( false );
12+
const [ loading, setLoading ] = useState( false );
13+
const addToCartBtnClasses = cx(
14+
'text-gray-800 font-semibold py-2 px-4 border border-gray-400 rounded shadow',
15+
{
16+
'bg-white hover:bg-gray-100': ! loading,
17+
'bg-gray-200': loading,
18+
},
19+
);
20+
621
if ( isEmpty( product ) ) {
722
return null;
823
}
924

1025
return (
11-
<button
12-
className="bg-white hover:bg-gray-100 text-gray-800 font-semibold py-2 px-4 border border-gray-400 rounded shadow"
13-
onClick={ () => addToCart( product?.id ?? 0 ) }>
14-
Add to cart
15-
</button>
26+
<>
27+
<button
28+
className={ addToCartBtnClasses }
29+
onClick={ () => addToCart( product?.id ?? 0, 1, setCart, setIsAddedToCart, setLoading ) }
30+
disabled={ loading }
31+
>
32+
{ loading ? 'Adding...' : 'Add to cart' }
33+
</button>
34+
{ isAddedToCart && ! loading ? (
35+
<Link href="/cart">
36+
<a
37+
className="bg-white hover:bg-gray-100 text-gray-800 font-semibold ml-4 py-11px px-4 border border-gray-400 rounded shadow"
38+
>
39+
View cart
40+
</a>
41+
</Link>
42+
) : null }
43+
</>
1644
);
1745
};
1846

src/components/context/index.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React, { useState, useEffect } from 'react';
2+
export const AppContext = React.createContext([
3+
{},
4+
() => {}
5+
]);
6+
7+
export const AppProvider = ( props ) => {
8+
9+
const [ cart, setCart ] = useState( null );
10+
11+
/**
12+
* This will be called once on initial load ( component mount ).
13+
*
14+
* Sets the cart data from localStorage to `cart` in the context.
15+
*/
16+
useEffect( () => {
17+
18+
if ( process.browser ) {
19+
let cartData = localStorage.getItem( 'next-cart' );
20+
cartData = null !== cartData ? JSON.parse( cartData ) : '';
21+
setCart( cartData );
22+
}
23+
24+
}, [] );
25+
26+
/**
27+
* 1.When setCart() is called that changes the value of 'cart',
28+
* this will set the new data in the localStorage.
29+
*
30+
* 2.The 'cart' will anyways have the new data, as setCart()
31+
* would have set that.
32+
*/
33+
useEffect( () => {
34+
35+
if ( process.browser ) {
36+
localStorage.setItem('next-cart', JSON.stringify(cart));
37+
}
38+
39+
}, [ cart ] );
40+
41+
return (
42+
<AppContext.Provider value={ [ cart, setCart ] }>
43+
{ props.children }
44+
</AppContext.Provider>
45+
);
46+
};

src/components/layouts/header/index.js renamed to src/components/layout/header/index.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import Head from 'next/head';
22
import Link from 'next/link';
3-
import { useState } from 'react';
3+
import { useContext, useState } from 'react';
44
import { isEmpty } from 'lodash';
55

66
import { BurgerIcon, TailwindIcon, Bag, User, Wishlist } from '../../icons';
7+
import { AppContext } from '../../context';
78

89
const Header = ( { header } ) => {
910

11+
const [ cart, setCart ] = useContext( AppContext );
1012
const { headerMenuItems, siteDescription, siteLogoUrl, siteTitle, favicon } = header || {};
1113

1214
const [ isMenuVisible, setMenuVisibility ] = useState( false );
@@ -41,12 +43,13 @@ const Header = ( { header } ) => {
4143
</div>
4244
<div className="block lg:hidden">
4345
<button
44-
onClick={() => setMenuVisibility( ! isMenuVisible )}
46+
onClick={ () => setMenuVisibility( ! isMenuVisible ) }
4547
className="flex items-center px-3 py-2 border rounded text-black border-black hover:text-black hover:border-black">
4648
<BurgerIcon className="fill-current h-3 w-3"/>
4749
</button>
4850
</div>
49-
<div className={`${ isMenuVisible ? 'max-h-full' : 'h-0' } overflow-hidden w-full lg:h-full block flex-grow lg:flex lg:items-center lg:w-auto`}>
51+
<div
52+
className={ `${ isMenuVisible ? 'max-h-full' : 'h-0' } overflow-hidden w-full lg:h-full block flex-grow lg:flex lg:items-center lg:w-auto` }>
5053
<div className="text-sm font-medium uppercase lg:flex-grow">
5154
{ ! isEmpty( headerMenuItems ) && headerMenuItems.length ? headerMenuItems.map( menuItem => (
5255
<Link key={ menuItem?.ID } href={ menuItem?.url ?? '/' }>
@@ -70,13 +73,14 @@ const Header = ( { header } ) => {
7073
Wishlist
7174
</span>
7275
</a>
73-
<a className="flex mt-4 lg:inline-block lg:mt-0 text-black hover:text-black mr-10"
74-
href="/cart/">
76+
<Link href="/cart">
77+
<a className="flex mt-4 lg:inline-block lg:mt-0 text-black hover:text-black mr-10">
7578
<span className="flex flex-row items-center lg:flex-col">
7679
<Bag className="mr-1 lg:mr-0"/>
77-
Bag
80+
<span className="ml-1">Bag{ cart?.totalQty ? `(${cart?.totalQty})` : null }</span>
7881
</span>
79-
</a>
82+
</a>
83+
</Link>
8084
</div>
8185
</div>
8286
</div>

src/components/layout/index.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { AppProvider } from '../context';
2+
import Header from './header';
3+
import Footer from './footer';
4+
5+
const Layout = ({children, headerFooter}) => {
6+
const { header, footer } = headerFooter || {};
7+
return (
8+
<AppProvider>
9+
<div >
10+
<Header header={header}/>
11+
<main className="container mx-auto py-4">
12+
{children}
13+
</main>
14+
<Footer footer={footer}/>
15+
</div>
16+
</AppProvider>
17+
)
18+
}
19+
20+
export default Layout

src/components/products/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const Products = ({ products }) => {
88
}
99

1010
return (
11-
<div className="flex flex-wrap -mx-2 overflow-hidden">
11+
<div className="flex flex-wrap -mx-3 overflow-hidden">
1212

1313
{ products.length ? products.map( product => {
1414
return (

src/components/products/product.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const Product = ( { product } ) => {
1414
const productType = product?.type ?? '';
1515

1616
return (
17-
<div className="my-2 px-2 w-full overflow-hidden sm:w-1/2 md:w-1/3 xl:w-1/4">
17+
<div className="mt-4 mb-8 px-3 w-full overflow-hidden sm:w-1/2 md:w-1/3 xl:w-1/4">
1818
<Link href={product?.permalink ?? '/'}>
1919
<a>
2020
<Image

src/utils/cart/index.js

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@ import { getSession, storeSession } from './session';
22
import { getAddOrViewCartConfig } from './api';
33
import axios from 'axios';
44
import { CART_ENDPOINT } from '../constants/endpoints';
5-
import { isEmpty } from 'lodash';
5+
import { isEmpty, isArray } from 'lodash';
66

77
/**
88
* Add To Cart Request Handler.
99
*
1010
* @param {int} productId Product Id.
1111
* @param {int} qty Product Quantity.
12+
* @param {Function} setCart Sets The New Cart Value
13+
* @param {Function} setIsAddedToCart Sets A Boolean Value If Product Is Added To Cart.
14+
* @param {Function} setLoading Sets A Boolean Value For Loading State.
1215
*/
13-
export const addToCart = ( productId, qty = 1 ) => {
14-
16+
export const addToCart = ( productId, qty = 1, setCart, setIsAddedToCart, setLoading ) => {
1517
const storedSession = getSession();
1618
const addOrViewCartConfig = getAddOrViewCartConfig();
1719

20+
setLoading(true);
21+
1822
axios.post( CART_ENDPOINT, {
1923
product_id: productId,
2024
quantity: qty,
@@ -26,7 +30,9 @@ export const addToCart = ( productId, qty = 1 ) => {
2630
if ( isEmpty( storedSession ) ) {
2731
storeSession( res?.headers?.[ 'x-wc-session' ] );
2832
}
29-
viewCart();
33+
setIsAddedToCart(true);
34+
setLoading(false);
35+
viewCart( setCart );
3036
} )
3137
.catch( err => {
3238
console.log( 'err', err );
@@ -36,16 +42,58 @@ export const addToCart = ( productId, qty = 1 ) => {
3642
/**
3743
* View Cart Request Handler
3844
*/
39-
export const viewCart = () => {
45+
export const viewCart = ( setCart ) => {
4046

4147
const addOrViewCartConfig = getAddOrViewCartConfig();
4248

4349
axios.get( CART_ENDPOINT, addOrViewCartConfig )
4450
.then( ( res ) => {
45-
console.log( 'res', res );
51+
const formattedCartData = getFormattedCartData( res?.data ?? [] )
52+
setCart( formattedCartData );
4653
} )
4754
.catch( err => {
4855
console.log( 'err', err );
4956
} );
5057
};
5158

59+
/**
60+
* Get Formatted Cart Data.
61+
*
62+
* @param cartData
63+
* @return {null|{cartTotal: {totalQty: number, totalPrice: number}, cartItems: ({length}|*|*[])}}
64+
*/
65+
const getFormattedCartData = ( cartData ) => {
66+
if ( ! cartData.length ) {
67+
return null;
68+
}
69+
const cartTotal = calculateCartQtyAndPrice( cartData || [] );
70+
return {
71+
cartItems: cartData || [],
72+
...cartTotal,
73+
};
74+
};
75+
76+
/**
77+
* Calculate Cart Qty And Price.
78+
*
79+
* @param cartItems
80+
* @return {{totalQty: number, totalPrice: number}}
81+
*/
82+
const calculateCartQtyAndPrice = ( cartItems ) => {
83+
const qtyAndPrice = {
84+
totalQty: 0,
85+
totalPrice: 0,
86+
}
87+
88+
if ( !isArray(cartItems) || !cartItems?.length ) {
89+
return qtyAndPrice;
90+
}
91+
92+
cartItems.forEach( (item, index) => {
93+
qtyAndPrice.totalQty += item?.quantity ?? 0;
94+
qtyAndPrice.totalPrice += item?.line_total ?? 0;
95+
} )
96+
97+
return qtyAndPrice;
98+
}
99+

tailwind.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ module.exports = {
33
'./src/components/**/*.js',
44
'./pages/**/*.js'],
55
theme: {
6+
extend: {
7+
spacing: {
8+
'11px': '11px'
9+
},
10+
},
611
container: {
712
padding: {
813
DEFAULT: '1rem',

0 commit comments

Comments
 (0)