<div id="app">
  <header>
    <div class="container alignment-container">
      <div class="logo">
        <h1 class="logo__title"><a class="logo__title-link" href="#" @click.prevent="goToHomePage">Vue's <br> Bookshop</a></h1>
      </div>
      <div class="cart-header-info">
        <span class="cart-header-info__text">Your cart has {{ cartItems.length }} items</span>
        <a href="#" type="button" class="cart-header-info__link" @click.prevent="displayCartView">View cart</a>
      </div>
    </div>
    <!-- container -->
    <div class="hero">
      <div class="container">
        <h2 class="display-title">
          Make learning your greatest pleasure!
        </h2>
        <button class="hero__btn">Start now!</button>
      </div>
    </div>
  </header>
  <main>
    <h2 class="page-title">{{ pageTitle }}</h2>
    <div v-if="!isCartView" class="container products-container">
      <div class="prod-card" v-for="product in products">
        <img :src="product.imgSrc" alt="" class="prod-card__image">
        <div class="prod-card__info">
          <h4 class="prod-card__title">{{ product.name }}</h4>
          <p class="prod-card__description">{{ product.description }}</p>
          <div class="alignment-container">
            <p class="prod-card__price">${{ product.price }}</p>
            <p :class="{'not-in-stock': product.inStock === 0}" class="prod-card__in-stock">In stock: {{ product.inStock }}</p>
          </div>
          <!--/.alignment-container -->

        </div>
        <!--/card__info-->
        <button :disabled="product.inStock === 0 ? true : false" class="prod-card__btn" id="add-to-cart" type="button" @click.prevent="addProdToCart(product)">Add to Cart</button>
      </div>
      <!--/card-->

    </div>

    <div v-else class="container">
      <!-- cart view -->
      <table v-if="cartItems.length > 0" class="cart">
        <thead>
          <tr>
            <th>Product</th>
            <th>Quantity</th>
            <th>Price</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="prod in cartItems">
            <td>{{ prod.item.name }}</td>
            <td><span class="cart__qty-text">{{ prod.quantity }}</span> <button class="cart__add-qty" @click="increaseQuantity(prod)" :disabled="prod.item.inStock === 0">+</button> <button class="cart__remove-qty" @click="decreaseQuantity(prod)" :disabled="prod.quantity === 0">-</button></td>
            <td>${{ prod.item.price }}</td>
          </tr>
          <tr class="cart__total">
            <td colspan="2">Total:</td>
            <td>${{ calculateTotal }}</td>
          </tr>
          <tr>
            <td colspan="2"></td>
            <td><button class="cart__checkout-btn" @click="checkout">Checkout</button></td>
          </tr>
        </tbody>
      </table>
      
      <div v-else class="empty-cart">
        <h3 class="empty-cart__message">Your cart is empty.</h3>
      </div>
    </div>
  </main>
  <footer>
    <div class="container alignment-container">
      <ul class="footer-links">
        <li class="footer-links__list-item">
          <a href="#" class="footer-links__link">About</a>
        </li>
        <li class="footer-links__list-item">
          <a href="#" class="footer-links__link">Contact</a>
        </li>
        <li class="footer-links__list-item">
          <a href="#" class="footer-links__link">Legal</a>
        </li>
      </ul>

      <p class="footer-credits">&copy;2018 Maria Antonietta Perna</p>
    </div>
  </footer>
</div>
@import url('https://fonts.googleapis.com/css?family=Bevan|Raleway:300i,400,700,800');

// vars
$body-color: #333;
$main-color: #41b883;
$secondary-color: #35495e;
$tertiary-color: #cc4513;

// mixins
@mixin flex-container($xalign, $yalign) {
  display: flex;
  justify-content: $xalign;
  align-items: $yalign;
}

@mixin action-btn {
  background-color: transparent;
  border: 2px solid $secondary-color;
  border-radius: 5px;
  color: $secondary-color;
  padding: 5px 10px;
  text-align: center;
  font-size: 2rem;
  font-weight: 700;
  transition: all 0.3s ease-out;
  
  &:hover,
  &:focus {
    background-color: $tertiary-color;
    border: 3px solid darken($tertiary-color, 15%) inset;
    color: #fff;
    transform: translatey(-3px);
    box-shadow: 0 3px 2px rgba(0,0,0,.5);
  }
}

@mixin disabled-btn {
  color: lighten($secondary-color, 45%);
  border-color: lighten($secondary-color, 45%);
  background-color: transparent;

  
  &:disabled:hover,
  &:disabled:focus {
    background-color: transparent;
    transform: none;
    box-shadow: none;
    cursor: not-allowed;
  }
}

// general
* {
  box-sizing: border-box;
}

*:before,
*:after {
  box-sizing: inherit;
}

html {
  font-size: 62.5%;
}

body {
  font-family: 'Raleway', sans-serif;
  font-size:1.6rem;
  line-height: 1.7;
  color: $body-color;
}

ul {
  list-style: none;
}

img {
  width: 100%;
  height: auto;
  display: block;
}

// reusable styles
.container {
  width: 100%;
  max-width: 992px;
  margin: 0 auto;
  padding: 1em;
}

.alignment-container {
  @include flex-container(space-between, center);  
}

.display-title {
  font-size: 2.8rem;
}

.page-title {
  margin-bottom: 0;
  font-size: 2.8rem;
  font-weight: 400;
  text-align: center;
}

// layout
header .alignment-container {
  border-bottom: 2px dashed $secondary-color;
}

footer {
  margin-top: 2em;
  border-top: 2px dashed $secondary-color;
}

.products-container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  grid-column-gap: 1em;
  grid-row-gap: 2em;
}

// components
.logo {
  margin-right: 1em;
  font-size: 1.2rem;
  line-height: 1;
  color: $main-color;
}
.logo__title,
.logo__title-link {
  font-family: 'Bevan', cursive;
}

.logo__title-link {
  text-decoration: none;
  color: $main-color;
}

// shopping cart in products page
.cart-header-info__text,
.cart-header-info__link {
  display: inline-block;
}

.cart-header-info__text {
  font-size: 1.4rem;
  font-weight: 700;
  margin-right: 10px;
}

.cart-header-info__link {
  text-decoration: none;
  font-size: 1.4rem;
  text-align: center;
  padding: 5px 10px;
  border-radius: 5px;
  color: $secondary-color;
  background-color: lighten($secondary-color, 5%);
  transition: all 0.3s ease-out;
  
  &:hover,
  &:focus {
    background-color: $secondary-color;
    font-weight: bold;
    box-shadow: 0 3px 2px rgba(0,0,0,.5);
    transform: translatey(-3px);
    border: 2px solid darken($main-color, 25%) inset;
  }
}

// shopping cart in cart page
.cart {
  width: 100%;
  border-collapse: collapse;
  
  tbody tr:nth-child(odd) {
    background-color: lighten($main-color, 30%);
  }
  tbody td {
    text-align: center;
    padding: 1em;
  }
  
  .cart__total {
    background-color: $secondary-color;
    color: #fff;
  }
  
  .cart__add-qty,
  .cart__remove-qty {
    width: 30px;
    height: 30px;
    font-size: 1.5rem;
    font-weight: 700;
    padding: 0.5em;
    text-align: center;
    border-radius: 3px;
    background-color: $tertiary-color;
    border: darken($tertiary-color, 15%);
    color: #fff;
    
    &:disabled {
      @include disabled-btn;
    }
  }
  
  .cart__qty-text {
    margin-right: 3px;
    font-weight: 700;
  }
  
  .cart__checkout-btn {
    @include action-btn;
  }
}

// hero block
.hero {
  background-image: linear-gradient(
    to right bottom,
    rgba(126,213,111, .5),
    rgba(40,180,133, .5)),
  url(https://images.unsplash.com/photo-1463320726281-696a485928c7?ixlib=rb-0.3.5&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjE0NTg5fQ&s=d9c9cea1805c99c465dbe9ee3ecbfd34);
 background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  
  text-align: center;
  color: #fff;
  border-bottom: 3px dashed $secondary-color;
  @include flex-container(center, center);
  height: 45vh;
}

.hero__btn {
  @include action-btn;
  @media (min-width: 600px) {
    font-size: 3rem;
  }
}

// cards with products
.prod-card__image {
  border: 2px solid $tertiary-color;
  border-radius: 3px;
  padding: 1em;
  filter: sepia(40%) contrast(200%) opacity(85%);
  transition: filter 0.3s;
}

.prod-card:hover .prod-card__image {
  filter: none; 
}

.prod-card__info {
  padding: 1em;
  line-height: 1;
}

.prod-card__title {
  font-size: 2rem;
  color: $secondary-color;
}

.prod-card__description {
  line-height: 1.5;
}

.prod-card__price,
.prod-card__in-stock {
  background-color: $main-color;
  color: #fff;
  padding: 6px 10px;
  text-align: center;
}

.not-in-stock {
  background-color: $tertiary-color;
}

.prod-card__btn {
  @include action-btn;
  
  &:disabled {
    @include disabled-btn;
  }
}

.empty-cart {
  background-color: $secondary-color;
  color: #fff;
  text-align: center;
  font-size: 2.6rem;
}

// footer components
.footer-links {
  font-size: 1.4rem;
}

.footer-links__link {
  color: $main-color;
  
  &:hover,
  &:focus {
    color: $secondary-color;
  }
}

.footer-credits {
  font-size: 1.5rem;
}
View Compiled
//Vue.config.devtools = true;
new Vue({
  el: "#app",
  data: {
    products: [
      {
        id: 1,
        imgSrc:
          "https://images.unsplash.com/photo-1482062364825-616fd23b8fc1?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ&s=cc37ced2e51f9f58430dfed1192067cd",
        name: "Learn Vue",
        description:
          "Lorem ipsum dolor, sit amet consectetur adipisicing elit. Sequi, nobis minus! Dicta perferendis rem pariatur sapiente nobis.",
        price: 50,
        inStock: 3
      },

      {
        id: 2,
        imgSrc:
          "https://images.unsplash.com/photo-1516101922849-2bf0be616449?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ&s=506cdfe576a1dd5545a2850ac143b2be",
        name: "Vue for Beginners",
        description:
          "Lorem ipsum dolor, sit amet consectetur adipisicing elit. Sequi, nobis minus! Dicta perferendis rem pariatur sapiente nobis.",
        price: 150,
        inStock: 1
      },

      {
        id: 3,
        imgSrc:
          "https://images.unsplash.com/photo-1517694712202-14dd9538aa97?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ&s=c9cebb448c07815ac2a1c4141b4cdd18",
        name: "Advanced Vue Development",
        description:
          "Lorem ipsum dolor, sit amet consectetur adipisicing elit. Sequi, nobis minus! Dicta perferendis rem pariatur sapiente nobis.",
        price: 180,
        inStock: 10
      },

      {
        id: 4,
        imgSrc:
          "https://images.unsplash.com/photo-1485856407642-7f9ba0268b51?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ&s=5a819fb0cd534e9c1af5d38c4983eeb3",
        name: "ES6 for Everybody",
        description:
          "Lorem ipsum dolor, sit amet consectetur adipisicing elit. Sequi, nobis minus! Dicta perferendis rem pariatur sapiente nobis.",
        price: 40,
        inStock: 8
      },

      {
        id: 5,
        imgSrc:
          "https://images.unsplash.com/photo-1489875347897-49f64b51c1f8?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ&s=1174b1ff26fccde5cdfda64087bfc6ac",
        name: "Advanced JavaScript",
        description:
          "Lorem ipsum dolor, sit amet consectetur adipisicing elit. Sequi, nobis minus! Dicta perferendis rem pariatur sapiente nobis.",
        price: 99,
        inStock: 20
      },

      {
        id: 6,
        imgSrc:
          "https://images.unsplash.com/photo-1472437774355-71ab6752b434?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ&s=070ca0d38724c4382129ae8e0004a5ba",
        name: "Vue and Vuex",
        description:
          "Lorem ipsum dolor, sit amet consectetur adipisicing elit. Sequi, nobis minus! Dicta perferendis rem pariatur sapiente nobis.",
        price: 199,
        inStock: 0
      }
    ],

    cartItems: [],

    isCartView: false,
    pageTitle: "Our Books"
  },

  methods: {
    // responsible for showing the cart page
    // when View cart button is clicked
    displayCartView() {
      this.isCartView = true;
      this.pageTitle = "Your Cart";
    },

    // responsible for showing products page
    // when logo is clicked
    goToHomePage() {
      this.isCartView = false;
      this.pageTitle = "Our Books";
    },

    getCartItems(prod) {
      if (this.cartItems.length > 0) {
        for (let i = 0; i < this.cartItems.length; i++) {
          if (this.cartItems[i].item.id === prod.id) {
            this.cartItems[i].quantity++;
          } else {
            this.cartItems.push({
              item: prod,
              quantity: 1
            });
          }
        }
      }
    },

    // if the product is already in the cart,
    // increase its quantity by 1
    // else create a new object with the
    // selected product info and push it
    // into the cartItems array.
    // Decrease stock quantity when item is
    // added to the cart
    addProdToCart(prod) {
      for (let i = 0; i < this.cartItems.length; i++) {
        if (this.cartItems[i].item.id === prod.id) {
          this.cartItems[i].quantity++;
          return;
        }
      }
      this.cartItems.push({
        item: prod,
        quantity: 1
      });

      // decrease stock when product
      // is added to the cart
      //console.log(this.cartItems);
      prod.inStock--;
    },
    
    // + button functionality in the 
    // cart: quantity goes up by 1,
    // in stock goes down by 1 
    increaseQuantity(prod) {
      prod.quantity++;
      prod.item.inStock--;
    },
    
    // when the quantity reaches 0, the item
    // is removed from the cart
    removeProdFromCart(prod) {
      const prodIndex = this.cartItems.indexOf(prod);
      this.cartItems.splice(prodIndex, 1);
    },
    
    // - button functionality in the cart: 
    // quantity is decreased by 1,
    // in stock is increased by 1,
    // if quantity = 0, product is removed
    // from the cart
    decreaseQuantity(prod) {
      prod.quantity--;
      prod.item.inStock++;
      if(prod.quantity <= 0) {
        this.removeProdFromCart(prod);
      }
    },
    
    checkout() {
      // remove all products from the cart
      this.cartItems = [];
    }
  },

  computed: {
    // this function keeps an eye on the quantity in 
    // the cart, which may vary as users add or remove
    // products and calculates the total each time
    calculateTotal() {
      let total = 0;
      for (let i = 0; i < this.cartItems.length; i++) {
        total += this.cartItems[i].item.price * this.cartItems[i].quantity;
      }
      return total;
    }
  }
});
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js