Skip to content

Instantly share code, notes, and snippets.

@adrianhajdin
Last active July 11, 2024 14:11
Show Gist options
  • Save adrianhajdin/55e019f0ec6a48c0ca6d933d3cf0181c to your computer and use it in GitHub Desktop.
Save adrianhajdin/55e019f0ec6a48c0ca6d933d3cf0181c to your computer and use it in GitHub Desktop.
Build and Deploy a Modern Full Stack ECommerce Application with Stripe
export default {
name: 'banner',
title: 'Banner',
type: 'document',
fields: [
{
name: 'image',
title: 'Image',
type: 'image',
options: {
hotspot: true,
},
},
{
name: 'buttonText',
title: 'ButtonText',
type: 'string',
},
{
name: 'product',
title: 'Product',
type: 'string',
},
{
name: 'desc',
title: 'Desc',
type: 'string',
},
{
name: 'smallText',
title: 'SmallText',
type: 'string',
},
{
name: 'midText',
title: 'MidText',
type: 'string',
},
{
name: 'largeText1',
title: 'LargeText1',
type: 'string',
},
{
name: 'largeText2',
title: 'LargeText2',
type: 'string',
},
{
name: 'discount',
title: 'Discount',
type: 'string',
},
{
name: 'saleTime',
title: 'SaleTime',
type: 'string',
},
],
};
html,
body, * {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
box-sizing: border-box;
}
::-webkit-scrollbar {
width: 0px;
}
a {
color: inherit;
text-decoration: none;
}
.main-container{
max-width: 1400px;
margin: auto;
width: 100%;
}
.layout{
padding: 10px;
}
.navbar-container{
display: flex;
justify-content: space-between;
margin: 6px 18px;
position: relative;
}
.marquee-text{
font-size: 29px;
font-weight: 600;
margin: 60px 0px;
color: #f02d34;
}
.marquee {
position: relative;
height: 400px;
width: 100%;
overflow-x: hidden;
}
.track {
position: absolute;
white-space: nowrap;
will-change: transform;
animation: marquee 15s linear infinite;
width: 180%;
}
.track:hover {
animation-play-state: paused;
}
@keyframes marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
span.text-red {
-webkit-text-stroke: 1px #f02d34;
margin-left: 6px;
}
.logo{
color: gray;
font-size: 18px;
}
.cart-icon{
font-size: 25px;
color: gray;
cursor: pointer;
position: relative;
transition: transform .4s ease;
border: none;
background-color: transparent;
}
.cart-icon:hover{
transform: scale(1.1,1.1);
}
.cart-item-qty{
position: absolute;
right: -8px;
font-size: 12px;
color: #eee;
background-color: #f02d34;
width: 18px;
height: 18px;
border-radius: 50%;
text-align: center;
font-weight: 600;
}
.products-container{
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 15px;
margin-top: 20px;
width: 100%;
}
.product-card{
cursor: pointer;
transform: scale(1, 1);
transition: transform 0.5s ease;
color: #324d67;
}
.product-card:hover{
transform: scale(1.1,1.1)
}
.product-image{
border-radius: 15px;
background-color: #ebebeb;
transform: scale(1, 1);
transition: transform 0.5s ease;
}
.product-name{
font-weight: 500;
}
.product-price{
font-weight: 800;
margin-top: 6px;
color: black;
}
.hero-banner-container{
padding: 100px 40px;
background-color: #dcdcdc;
border-radius: 15px;
position: relative;
height: 500px;
line-height: 0.9;
width: 100%;
}
.hero-banner-container .beats-solo{
font-size: 20px;
}
.hero-banner-container button{
border-radius: 15px;
padding: 10px 16px;
background-color: #f02d34;
color: white;
border: none;
margin-top: 40px;
font-size: 18px;
font-weight: 500;
cursor: pointer;
z-index:10000 !important;
}
.hero-banner-container h3{
font-size: 4rem;
margin-top: 4px;
}
.hero-banner-container h1{
color: white;
font-size: 10em;
margin-left: -20px;
text-transform: uppercase;
}
.hero-banner-image{
position: absolute;
top: 0%;
right:20%;
width: 450px;
height: 450px;
}
.desc{
position: absolute;
right: 10%;
bottom: 5%;
width: 300px;
line-height: 1.3;
display: flex;
flex-direction: column;
color: #324d67;
}
.desc p{
color: #5f5f5f;
font-weight: 100;
text-align: end;
}
.desc h5{
margin-bottom: 12px;
font-weight: 700;
font-size: 16px;
/* color: black; */
align-self: flex-end;
}
.products-heading{
text-align: center;
margin: 40px 0px;
color: #324d67;
}
.products-heading h2{
font-size: 40px;
font-weight: 800;
}
.products-heading p{
font-size: 16px;
font-weight: 200;
}
.footer-banner-container{
padding: 100px 40px;
background-color: #f02d34;
border-radius: 15px;
position: relative;
height: 400px;
line-height: 1;
color: white;
width: 100%;
margin-top: 120px;
}
.banner-desc{
display: flex ;
justify-content: space-between;
}
.banner-desc button{
border-radius: 15px;
padding: 10px 16px;
background-color: white;
color: red;
border: none;
margin-top: 40px;
font-size: 18px;
font-weight: 500;
cursor: pointer;
}
.banner-desc .left h3{
font-weight: 900;
font-size: 80px;
margin-left: 25px;
}
.banner-desc .left p{
margin:18px;
}
.footer-banner-image{
position: absolute;
/* top: -35%;
left: 8%; */
top: -25%;
left: 25%;
}
.banner-desc .right{
line-height: 1.4;
}
.banner-desc .right h3{
font-weight: 800;
font-size: 60px;
}
.banner-desc .right p{
font-size: 18px;
}
.banner-desc .right .company-desc{
font-size: 14px;
font-weight: 300;
}
.cart-wrapper{
width: 100vw;
background: rgba(0, 0, 0, 0.5);
position: fixed;
right: 0;
top: 0;
z-index: 100;
/* will-change: transform; */
transition: all 1s ease-in-out;
}
.cart-container{
height: 100vh;
width: 600px;
background-color: white;
float: right;
padding: 40px 10px;
position: relative;
}
.footer-container{
color: #324d67;
text-align: center;
margin-top: 20px;
padding: 30px 10px;
font-weight: 700;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
justify-content: center;
}
.footer-container .icons{
font-size: 30px;
display: flex;
gap: 10px;
}
.cart-heading{
display: flex;
align-items: center;
font-size: 18px;
font-weight: 500;
cursor: pointer;
gap: 2px;
margin-left: 10px;
border: none;
background-color: transparent;
}
.cart-heading .heading{
margin-left: 10px;
}
.cart-num-items{
margin-left: 10px;
color: #f02d34;
}
.empty-cart{
margin:40px;
text-align:center;
}
.empty-cart h3{
font-weight: 600;
font-size: 20px;
}
.cancel{
cursor: pointer;
}
.product-container{
margin-top: 15px;
overflow: auto;
max-height: 70vh;
padding: 20px 10px;
}
.product{
display: flex;
gap: 30px;
padding: 20px;
}
.product .cart-product-image{
width:180px ;
height: 150px;
border-radius: 15px;
background-color: #ebebeb;
}
.item-desc .flex{
display: flex;
justify-content: space-between;
width: 350px;
color: #324d67;
}
.item-desc .bottom{
margin-top: 60px;
}
.flex h5{
font-size: 24px;
}
.flex h4{
font-size: 20px;
}
.total{
display: flex;
justify-content: space-between;
}
.total h3{
font-size: 22px;
}
.remove-item{
font-size: 24px;
color: #f02d34;
cursor: pointer;
background: transparent;
border: none;
}
.cart-bottom{
position: absolute;
bottom: 12px;
right: 5px;
width: 100%;
padding: 30px 65px;
}
.btn-container{
width: 400px;
margin: auto;
}
.btn{
width: 100%;
max-width: 400px;
padding: 10px 12px;
border-radius: 15px;
border: none;
font-size: 20px;
margin-top: 10px;
margin-top: 40px;
text-transform: uppercase;
background-color: #f02d34;
color: #fff;
cursor: pointer;
transform: scale(1, 1);
transition: transform 0.5s ease;
}
.btn:hover{
transform: scale(1.1,1.1);
}
.product-detail-container{
display: flex;
gap: 40px;
margin: 40px;
margin-top: 60px;
color: #324d67;
}
.product-detail-image{
border-radius: 15px;
background-color: #ebebeb;
width: 400px;
height: 400px;
cursor: pointer;
transition: .3s ease-in-out;
}
.product-detail-image:hover{
background-color: #f02d34;
}
.small-images-container{
display: flex;
gap: 10px;
margin-top: 20px;
}
.small-image{
border-radius: 8px;
background-color: #ebebeb;
width: 70px;
height: 70px;
cursor: pointer;
}
.selected-image{
background-color:#f02d34;
}
.reviews{
color: #f02d34;
margin-top: 10px;
display: flex;
gap: 5px;
align-items: center;
}
.product-detail-desc h4{
margin-top: 10px;
}
.product-detail-desc p{
margin-top: 10px;
}
.reviews p{
color: #324d67;
margin-top: 0px;
}
.product-detail-desc .price{
font-weight: 700 ;
font-size: 26px;
margin-top: 30px;
color:#f02d34;
}
.price .old-price, .product-price .old-price, .price .old-price{
color: gray;
text-decoration: line-through;
}
.product-detail-desc .quantity{
display: flex;
gap: 20px;
margin-top: 10px ;
align-items: center;
}
.product-detail-desc .buttons{
display: flex;
gap: 30px;
}
.buttons .add-to-cart{
padding: 10px 20px;
border: 1px solid #f02d34 ;
margin-top: 40px;
font-size: 18px;
font-weight: 500;
background-color: white;
color: #f02d34;
cursor: pointer;
width: 200px;
transform: scale(1, 1);
transition: transform 0.5s ease;
}
.buttons .add-to-cart:hover{
transform:scale(1.1,1.1)
}
.buttons .buy-now{
width: 200px;
padding: 10px 20px;
background-color: #f02d34;
color: white;
border: none;
margin-top: 40px;
font-size: 18px;
font-weight: 500;
cursor: pointer;
transform: scale(1, 1);
transition: transform 0.5s ease;
}
.buttons .buy-now:hover{
transform:scale(1.1,1.1)
}
.quantity-desc{
border: 1px solid gray;
padding: 6px;
}
.quantity-desc span{
font-size: 16px;
padding: 6px 12px;
cursor: pointer;
}
.quantity-desc .minus{
border-right: 1px solid gray;
color: #f02d34;
}
.quantity-desc .num{
border-right: 1px solid gray;
font-size: 20px;
}
.quantity-desc .plus{
color: rgb(49, 168, 49);
}
.maylike-products-wrapper{
margin-top: 120px;
}
.maylike-products-wrapper h2{
text-align: center;
margin: 50px;
color: #324d67;
font-size: 28px;
}
.maylike-products-container{
display: flex;
justify-content: center;
gap: 15px;
margin-top: 20px;
}
.max-qty{
font-weight: 500;
color: #f02d34;
}
.success-wrapper, .cancel-wrapper{
background-color: white;
min-height: 60vh;
}
.success, .cancel{
width: 1000px;
margin: auto;
margin-top: 160px;
background-color: #dcdcdc;
padding: 50px;
border-radius: 15px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.success .icon {
color: green;
font-size: 40px;
}
.success h2{
text-transform: capitalize;
margin-top: 15px 0px;
font-weight: 900;
font-size: 40px;
color:#324d67;
}
.success .email-msg{
font-size: 16px;
font-weight: 600;
text-align: center;
}
.cancel p{
font-size: 20px;
font-weight: 600;
}
.success .description{
font-size: 16px;
font-weight: 600;
text-align: center;
margin: 10px;
margin-top: 30px;
}
.success .description .email{
margin-left: 5px;
color: #f02d34;
}
.product-max-qty{
margin-top: 10px;
}
@media screen and (max-width:800px) {
.hero-banner-container{
height: 560px;
}
.hero-banner-image{
width: 77%;
height: 62%;
top: -2%;
right: -6%;
}
.footer-banner-container{
height: 560px;
margin-top: 80px;
}
.footer-banner-image{
width: 77%;
left: 30%;
top: 6%;
height: 56%
}
.banner-desc .left h3{
font-weight: 900;
font-size: 50px;
margin-left: 5px;
}
.banner-desc .left p{
margin:18px;
}
.banner-desc .right h3{
font-size: 45px;
}
.banner-desc .right p{
font-size: 18px;
}
.banner-desc .right .company-desc{
font-size: 14px;
}
.banner-desc{
flex-wrap: wrap;
gap: 20px;
}
.hero-banner-container{
line-height: 1.3;
}
.hero-banner-container h1{
font-size: 50px;
}
.hero-banner-container h3{
font-size: 40px;
}
.hero-banner-container button{
margin-top: 90px;
z-index: 10000;
}
.desc{
bottom: 60px;
}
.product-detail-container{
flex-wrap: wrap;
}
.product-detail-container .product-detail-image{
width: 350px;
height: 350px;
}
.cart-container{
width: 415px;
padding: 4px;
}
.cart-heading{
margin-top: 35px;
}
.product-container{
margin-top: 10px;
}
.product{
padding: 20px 5px;
}
.product .cart-product-image{
width: 25%;
height: 25%;
}
.buttons .add-to-cart{
width: 150px;
}
.buttons .buy-now{
width: 150px;
}
.product-detail-container{
margin: 20px;
}
.item-desc .flex{
width: 200px;
}
.top{
flex-wrap: wrap;
gap: 10px;
}
.item-desc .bottom{
margin-top: 30px;
}
.flex h5{
font-size: 16px;
color: #324d67;
}
.flex h4{
font-size: 16px;
color: black;
}
.cart-bottom{
padding: 30px;
}
.total h3{
font-size: 20px;
}
.track {
animation: marquee 10s linear infinite;
width: 550%;
}
.success-wrapper, .cancel-wrapper{
min-height: 69vh;
}
.success, .cancel {
width: 370px;
margin-top: 100px;
padding: 20px;
}
.success{
height: 350px;
}
.success h2{
font-size: 17px;
}
.btn-container{
width: 300px;
margin: auto;
}
}
{
"name": "sanity-ecommerce",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@sanity/client": "^3.2.0",
"@sanity/image-url": "^1.0.1",
"@stripe/stripe-js": "^1.25.0",
"canvas-confetti": "^1.5.1",
"next": "12.1.0",
"next-sanity-image": "^3.2.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-hot-toast": "^2.2.0",
"react-icons": "^4.3.1",
"stripe": "^8.209.0"
},
"devDependencies": {
"eslint": "^8.10.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-next": "^12.1.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-react": "^7.29.3",
"eslint-plugin-react-hooks": "^4.3.0"
}
}
import React, { createContext, useContext, useState, useEffect } from 'react';
import { toast } from 'react-hot-toast';
const Context = createContext();
export const StateContext = ({ children }) => {
const getLocalStorage = (name) => {
if (typeof window !== 'undefined') {
const storage = localStorage.getItem(name);
if (storage) return JSON.parse(localStorage.getItem(name));
if (name === 'cartItems') return [];
return 0;
}
};
const [showCart, setShowCart] = useState(false);
const [cartItems, setCartItems] = useState(getLocalStorage('cartItems'));
const [totalPrice, setTotalPrice] = useState(getLocalStorage('totalPrice'));
const [totalQuantities, setTotalQuantities] = useState(getLocalStorage('totalQuantities'));
const [qty, setQty] = useState(1);
let findProduct;
let index;
useEffect(() => {
localStorage.setItem('cartItems', JSON.stringify(cartItems));
localStorage.setItem('totalPrice', JSON.stringify(totalPrice));
localStorage.setItem('totalQuantities', JSON.stringify(totalQuantities));
}, [cartItems, totalPrice, totalQuantities]);
const onAdd = (product, quantity) => {
const checkProductInCart = cartItems.find(
(cartProduct) => cartProduct._id === product._id,
);
if (checkProductInCart) {
setTotalPrice(totalPrice + product.price * quantity);
setTotalQuantities(totalQuantities + quantity);
const updatedCartItems = cartItems.map((cartProduct) => {
if (cartProduct._id === product._id) {
return { ...cartProduct, quantity: cartProduct.quantity + quantity };
}
return cartProduct;
});
setCartItems(updatedCartItems);
toast.success(`${qty} ${product.name} added`);
} else {
setTotalPrice(totalPrice + product.price * quantity);
setTotalQuantities(totalQuantities + quantity);
// eslint-disable-next-line no-param-reassign
product.quantity = quantity;
setCartItems([...cartItems, { ...product }]);
toast.success(`${qty} ${product.name} added`);
}
};
const onRemove = (product) => {
findProduct = cartItems.find((item) => item._id === product._id);
const tempCart = cartItems.filter((item) => item._id !== product._id);
setTotalPrice(totalPrice - findProduct.price * findProduct.quantity);
setTotalQuantities(totalQuantities - findProduct.quantity);
setCartItems(tempCart);
};
const toggleCartItemQuantity = (id, value) => {
findProduct = cartItems.find((item) => item._id === id);
index = cartItems.findIndex((product) => product._id === id);
if (value === 'inc') {
findProduct.quantity += 1;
cartItems[index] = findProduct;
setTotalPrice(totalPrice + findProduct.price);
setTotalQuantities(totalQuantities + 1);
}
if (value === 'dec') {
if (findProduct.quantity > 1) {
findProduct.quantity -= 1;
cartItems[index] = findProduct;
setTotalPrice(totalPrice - findProduct.price);
setTotalQuantities(totalQuantities - 1);
}
}
};
const incQty = () => {
setQty((oldQty) => {
const tempQty = oldQty + 1;
return tempQty;
});
};
const decQty = () => {
setQty((oldQty) => {
let tempQty = oldQty - 1;
if (tempQty < 1) {
tempQty = 1;
}
return tempQty;
});
};
return (
<Context.Provider
// eslint-disable-next-line react/jsx-no-constructed-context-values
value={{
onAdd,
onRemove,
cartItems,
totalPrice,
totalQuantities,
setShowCart,
setCartItems,
setTotalPrice,
setTotalQuantities,
showCart,
incQty,
decQty,
qty,
toggleCartItemQuantity,
}}
>
{children}
</Context.Provider>
);
};
export const useStateContext = () => useContext(Context);
@responsive-we
Copy link

Thank you for your solution. There was some error in StateContext.js file

@Amir-gabr
Copy link

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 { useStateContext } from '../context/StateContext';
import { urlFor } from '../lib/client';

const Cart = () => {
const cartRef = useRef();
const { totalPrice, totalQuantities, cartItems, setShowCart } = useStateContext();

return (



<button
type="button"
className="cart-heading"
onClick={() => setShowCart(false)}>

Your Cart
({totalQuantities} items)

    {cartItems.length < 1 && (
      <div className="empty-cart">
        <AiOutlineShopping size={150} />
        <h3>Your shopping bag is empty</h3>
        <Link href="/">
          <button
            type="button"
            onClick={() => setShowCart(false)}
            className="btn"
          >
            Continue Shopping
          </button>
        </Link>
      </div>
    )}

    <div className="product-container">
      {cartItems.length >= 1 && cartItems.map((item) => (
        <div className="product" key={item._id}>
          <img src={urlFor(item?.image[0])} className="cart-product-image" />

        </div>
      ))}
    </div>
  </div>
</div>

)
}

export default Cart
I am getting an error saying :"Unhandled Runtime Error TypeError: Cannot read properties of undefined (reading '0')"
275152381-5f51063e-ac8a-4963-b03f-4602b6060b65
275152381-5f51063e-ac8a-4963-b03f-4602b6060b65

@responsive-we
Copy link

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 { useStateContext } from '../context/StateContext'; import { urlFor } from '../lib/client';

const Cart = () => { const cartRef = useRef(); const { totalPrice, totalQuantities, cartItems, setShowCart } = useStateContext();

return (

<button
type="button"
className="cart-heading"
onClick={() => setShowCart(false)}>

Your Cart
({totalQuantities} items)

    {cartItems.length < 1 && (
      <div className="empty-cart">
        <AiOutlineShopping size={150} />
        <h3>Your shopping bag is empty</h3>
        <Link href="/">
          <button
            type="button"
            onClick={() => setShowCart(false)}
            className="btn"
          >
            Continue Shopping
          </button>
        </Link>
      </div>
    )}

    <div className="product-container">
      {cartItems.length >= 1 && cartItems.map((item) => (
        <div className="product" key={item._id}>
          <img src={urlFor(item?.image[0])} className="cart-product-image" />

        </div>
      ))}
    </div>
  </div>
</div>

) }

export default Cart I am getting an error saying :"Unhandled Runtime Error TypeError: Cannot read properties of undefined (reading '0')" 275152381-5f51063e-ac8a-4963-b03f-4602b6060b65 275152381-5f51063e-ac8a-4963-b03f-4602b6060b65

You must see the output coming by logging the item param using console.log. And after getting the output you can debug it further or share it in this comment section.

@lambofil
Copy link

lambofil commented Apr 9, 2024

can somebody share all the command for installing all the dependencies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment