Created
August 24, 2022 00:40
-
-
Save jaisonrobson/bbce72d8f406e0eeb2017ef6d6fe1e51 to your computer and use it in GitHub Desktop.
Modern Full Stack ECommerce Next.js Tutorial Solution for ShoppingCart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState } from 'react' | |
import { AiOutlineMinus, AiOutlinePlus, AiFillStar, AiOutlineStar } from 'react-icons/ai' | |
import { client, urlFor } from '../../lib/client' | |
import { Product } from '../../components' | |
import { useShoppingCartContext } from '../../contexts/ShoppingCartContext' | |
const ProductDetails = ({ | |
data: { | |
selectedProduct, | |
products, | |
} = {}, | |
}) => { | |
const { | |
image, | |
name, | |
details, | |
price | |
} = selectedProduct | |
const [selectedImageIndex, setSelectedImageIndex] = useState(0) | |
const [quantity, setQuantity] = useState(1) | |
const increaseQuantity = () => setQuantity(prev => prev + 1) | |
const decreaseQuantity = () => setQuantity(prev => (prev-1 < 1) ? prev : prev - 1) | |
const { onAddCartItem } = useShoppingCartContext() | |
return ( | |
<div> | |
<div className="product-detail-container"> | |
<div> | |
<div className="image-container"> | |
<img | |
className="product-detail-image" | |
src={urlFor(image && image[selectedImageIndex])} | |
/> | |
</div> | |
<div className="small-images-container"> | |
{image?.map((item, index) => ( | |
<img | |
className={ | |
index === selectedImageIndex | |
? "small-image selected-image" | |
: "small-image" | |
} | |
src={urlFor(item)} | |
onMouseEnter={() => setSelectedImageIndex(index)} | |
key={item._id} | |
/> | |
))} | |
</div> | |
</div> | |
<div className="product-detail-desc"> | |
<h1>{name}</h1> | |
<div className="reviews"> | |
<div> | |
<AiFillStar /> | |
<AiFillStar /> | |
<AiFillStar /> | |
<AiFillStar /> | |
<AiOutlineStar /> | |
</div> | |
<p>(20)</p> | |
</div> | |
<h4>Detalhes: </h4> | |
<p>{details}</p> | |
<p className="price">R${price}</p> | |
<div className="quantity"> | |
<h3>Quantidade:</h3> | |
<p className="quantity-desc"> | |
<span | |
className="minus" | |
onClick={decreaseQuantity} | |
> | |
<AiOutlineMinus /> | |
</span> | |
<span | |
className="num" | |
onClick="" | |
> | |
{quantity} | |
</span> | |
<span | |
className="plus" | |
onClick={increaseQuantity} | |
> | |
<AiOutlinePlus /> | |
</span> | |
</p> | |
</div> | |
<div className="buttons"> | |
<button | |
className="add-to-cart" | |
type="button" | |
onClick={() => onAddCartItem(selectedProduct._id, quantity)} | |
> | |
Adicionar ao carrinho | |
</button> | |
<button | |
className="buy-now" | |
type="button" | |
onClick="" | |
> | |
Comprar agora | |
</button> | |
</div> | |
</div> | |
</div> | |
<div className="maylike-products-wrapper"> | |
<h2>Itens relacionados</h2> | |
<div className="marquee"> | |
<div className="maylike-products-container track"> | |
{products.map((product) => ( | |
<Product key={product._id} data={product} /> | |
))} | |
</div> | |
</div> | |
</div> | |
</div> | |
) | |
} | |
export const getStaticPaths = async () => { | |
const productsPathNames = await client.fetch( | |
`*[_type == "product"] { | |
slug { | |
current | |
} | |
}` | |
) | |
const paths = productsPathNames.map((product) => ({ | |
params: { | |
slug: product.slug.current | |
} | |
})) | |
return { | |
paths, | |
fallback: 'blocking' | |
} | |
} | |
export const getStaticProps = async ({ | |
params: { | |
slug, | |
} = {} | |
}) => { | |
const selectedProduct = await client.fetch( | |
`*[_type == "product" && slug.current == "${slug}"][0]` | |
) | |
const products = await client.fetch(`*[_type == "product"]`) | |
return { | |
props: { data: { products, selectedProduct } }, | |
} | |
} | |
export default ProductDetails |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useRef } from 'react' | |
import Link from 'next/link' | |
import { AiOutlineMinus, AiOutlinePlus, AiOutlineLeft, AiOutlineShopping } from 'react-icons/ai' | |
import { TiDeleteOutline } from 'react-icons/ti' | |
import toast from 'react-hot-toast' | |
import { useShoppingCartContext } from '../contexts/ShoppingCartContext' | |
import { urlFor } from '../lib/client' | |
const Cart = () => { | |
const cartRef = useRef() | |
const { | |
totalPrice, | |
totalItemsQuantity, | |
cartItems, | |
onHideCart, | |
increaseCartItemQuantity, | |
decreaseCartItemQuantity, | |
onRemoveCartItem, | |
} = useShoppingCartContext() | |
return ( | |
<div className="cart-wrapper" ref={cartRef}> | |
<div className="cart-container"> | |
<button | |
className="cart-heading" | |
type="button" | |
onClick={() => onHideCart()} | |
> | |
<AiOutlineLeft /> | |
<span className="heading">Seu Carrinho</span> | |
<span className="cart-num-items">({totalItemsQuantity} itens)</span> | |
</button> | |
{cartItems.length < 1 && ( | |
<div className="empty-cart"> | |
<AiOutlineShopping size={150} /> | |
<h3>Seu carrinho está vazio</h3> | |
<Link href="/"> | |
<button | |
className="btn" | |
type="button" | |
onClick={() => onHideCart()} | |
> | |
Continuar comprando | |
</button> | |
</Link> | |
</div> | |
)} | |
<div className="product-container"> | |
{cartItems.length >= 1 && cartItems.map(item => ( | |
<div className="product" key={item._id}> | |
<img | |
className="cart-product-image" | |
src={urlFor(item?.image[0])} | |
/> | |
<div className="item-desc"> | |
<div className="flex top"> | |
<h5>{item.name}</h5> | |
<h4>R${item.price}</h4> | |
</div> | |
<div className="flex bottom"> | |
<div> | |
<p className="quantity-desc"> | |
<span | |
className="minus" | |
onClick={() => decreaseCartItemQuantity(item._id)} | |
> | |
<AiOutlineMinus /> | |
</span> | |
<span | |
className="num" | |
onClick="" | |
> | |
{item.quantity} | |
</span> | |
<span | |
className="plus" | |
onClick={() => increaseCartItemQuantity(item._id)} | |
> | |
<AiOutlinePlus /> | |
</span> | |
</p> | |
</div> | |
<button | |
className="remove-item" | |
type="button" | |
onClick={() => onRemoveCartItem(item._id)} | |
> | |
<TiDeleteOutline /> | |
</button> | |
</div> | |
</div> | |
</div> | |
))} | |
</div> | |
{cartItems.length >= 1 && ( | |
<div className="cart-bottom"> | |
<div className="total"> | |
<h3>Subtotal:</h3> | |
<h3>R${totalPrice}</h3> | |
</div> | |
<div className="btn-container"> | |
<button | |
className="btn" | |
type="button" | |
onClick="" | |
> | |
Prosseguir com pagamento | |
</button> | |
</div> | |
</div> | |
)} | |
</div> | |
</div> | |
) | |
} | |
export default Cart |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//This file was called "StateContext.js", you need to make the due changes | |
//I didn't continue the code after the stripe part, so u might need to change the others files manually according to this | |
import React, { createContext, useContext, useState, useEffect } from 'react' | |
import _ from 'lodash' | |
import { toast } from 'react-hot-toast' | |
import { client } from '../lib/client' | |
const Context = createContext() | |
export const useShoppingCartContext = () => useContext(Context) | |
export const ShoppingCartContext = ({ children }) => { | |
const [showCart, setShowCart] = useState(false) | |
const [cartItems, setCartItems] = useState([]) | |
const [totalPrice, setTotalPrice] = useState(0) | |
const [totalItemsQuantity, setTotalItemsQuantity] = useState(0) | |
useEffect(() => { | |
calculateCart() | |
}, [cartItems]) | |
/* INTERNAL METHODS */ | |
const calculateCart = () => { | |
calculateCartTotalPrice() | |
calculateCartTotalItemsQuantity() | |
} | |
const calculateCartTotalPrice = () => | |
setTotalPrice( | |
_.reduce( | |
cartItems, | |
(result, item) => result += item.price * item.quantity, | |
0 | |
) | |
) | |
const calculateCartTotalItemsQuantity = () => | |
setTotalItemsQuantity( | |
_.reduce( | |
cartItems, | |
(result, item) => result += item.quantity, | |
0 | |
) | |
) | |
const onChangeCartItemQuantity = async (productId, quantity) => { | |
const product = await client.fetch(`*[_type == "product" && _id == "${productId}"]`) | |
const cartItemSearch = _.find(cartItems, (item) => item._id == productId) | |
if (cartItemSearch) { | |
const newCartItems = _.map(cartItems, (item) => { | |
if (item._id == productId) { | |
const newQuantity = item.quantity + quantity | |
return { | |
...item, | |
quantity: newQuantity > 0 ? newQuantity : 1 | |
} | |
} | |
return item | |
}) | |
setCartItems(newCartItems) | |
} | |
else if (quantity > 0) { | |
const newCartItem = { | |
...product[0], | |
quantity, | |
} | |
setCartItems((prevItems) => [...prevItems, newCartItem]) | |
} | |
} | |
/* EXTERNAL METHODS */ | |
const increaseCartItemQuantity = (productId) => onChangeCartItemQuantity(productId, 1) | |
const decreaseCartItemQuantity = (productId) => onChangeCartItemQuantity(productId, -1) | |
const onAddCartItem = (productId, quantity) => onChangeCartItemQuantity(productId, quantity) | |
const onRemoveCartItem = (productId) => { | |
const newCartItems = _.filter(cartItems, item => item._id != productId) | |
setCartItems(newCartItems) | |
} | |
const onShowCart = () => setShowCart(true) | |
const onHideCart = () => setShowCart(false) | |
return ( | |
<Context.Provider | |
value={{ | |
showCart, | |
onShowCart, | |
onHideCart, | |
cartItems, | |
onAddCartItem, | |
onRemoveCartItem, | |
increaseCartItemQuantity, | |
decreaseCartItemQuantity, | |
totalPrice, | |
totalItemsQuantity, | |
}} | |
> | |
{children} | |
</Context.Provider> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment