This is the second episode of "Portfolio Apps" series. Today, we are going to build a simple shopping cart. I choose simulating a little shop about smartphones and tablets.
Let's dive in this new tutorial.
1.0 / Setup
2.0 / Components & Router
[ 1.1 ] Install Vue 3
# Install latest stable of Vue
yarn global add @vue/cli
[ 1.2 ] Creating a new project
This time, let's manually select features for this new Vue application.
# run this command
vue create shopping-cart-app
Press your spacebar to select "Router" and "Vuex"
Choose this options :
- version 3 of Vue.js "3.x (Preview)",
- the "history mode for router",
- "ESLint with error prevention only",
- "Lint On Save",
- "Package .json"
- Preset "Yes"
- Custom and record the name you wish for this template.
[ 1.3 ] Images
Create four folders "iPadPro", "iPhone12Pro", "iPhoneSE" and "S21".
assets
|-- iPadPro
|-- iPhone12Pro
|-- iPHoneSE
|-- S21
Search and import all pictures in each folder as following :
[ 1.4 ] Vuex
Let's prepare all Vuex files. In "index.js", we need to initialize four states :
- cart,
- total,
- qty,
- products,
# ../store/index.js
import { createStore } from "vuex";
import rootMutations from "./mutations.js";
import rootActions from "./actions.js";
import rootGetters from "./getters.js";
const store = createStore({
state() {
return {
cart: [],
total: 0,
qty: 0,
products: [
{
id: 1,
type: "Smartphone",
brand: "Apple",
model: "iPhone 12 Pro",
color: "Graphite",
capacity: "128 GB",
imgSrc: require("@/assets/iPhone12Pro/iPhone12ProGraphite.jpg"),
price: 999,
},
{
id: 2,
type: "Smartphone",
brand: "Apple",
model: "iPhone 12 Pro",
color: "Silver",
capacity: "128 GB",
imgSrc: require("@/assets/iPhone12Pro/iPhone12ProSilver.jpg"),
price: 999,
},
{
id: 3,
type: "Smartphone",
brand: "Apple",
model: "iPhone 12 Pro",
color: "Gold",
capacity: "128 GB",
imgSrc: require("@/assets/iPhone12Pro/iPhone12ProGold.jpg"),
price: 999,
},
{
id: 4,
type: "Smartphone",
brand: "Apple",
model: "iPhone 12 Pro",
color: "Pacific Blue",
capacity: "128 GB",
imgSrc: require("@/assets/iPhone12Pro/iPhone12ProPacificBlue.jpg"),
price: 999,
},
{
id: 5,
type: "Smartphone",
brand: "Apple",
model: "iPhone 12 Pro",
color: "Graphite",
capacity: "256 GB",
imgSrc: require("@/assets/iPhone12Pro/iPhone12ProGraphite.jpg"),
price: 1199.0,
},
{
id: 6,
type: "Smartphone",
brand: "Apple",
model: "iPhone 12 Pro",
color: "Silver",
capacity: "256 GB",
imgSrc: require("@/assets/iPhone12Pro/iPhone12ProSilver.jpg"),
price: 1199,
},
{
id: 7,
type: "Smartphone",
brand: "Apple",
model: "iPhone 12 Pro",
color: "Gold",
capacity: "256 GB",
imgSrc: require("@/assets/iPhone12Pro/iPhone12ProGold.jpg"),
price: 1199,
},
{
id: 8,
type: "Smartphone",
brand: "Apple",
model: "iPhone 12 Pro",
color: "Pacific Blue",
capacity: "256 GB",
imgSrc: require("@/assets/iPhone12Pro/iPhone12ProPacificBlue.jpg"),
price: 1199,
},
{
id: 9,
type: "Smartphone",
brand: "Apple",
model: "iPhone 12 Pro",
color: "Graphite",
capacity: "512 GB",
imgSrc: require("@/assets/iPhone12Pro/iPhone12ProGraphite.jpg"),
price: 1399,
},
{
id: 10,
type: "Smartphone",
brand: "Apple",
model: "iPhone 12 Pro",
color: "Silver",
capacity: "512 GB",
imgSrc: require("@/assets/iPhone12Pro/iPhone12ProSilver.jpg"),
price: 1399,
},
{
id: 11,
type: "Smartphone",
brand: "Apple",
model: "iPhone 12 Pro",
color: "Gold",
capacity: "512 GB",
imgSrc: require("@/assets/iPhone12Pro/iPhone12ProGold.jpg"),
price: 1399,
},
{
id: 12,
type: "Smartphone",
brand: "Apple",
model: "iPhone 12 Pro",
color: "Pacific Blue",
capacity: "512 GB",
imgSrc: require("@/assets/iPhone12Pro/iPhone12ProPacificBlue.jpg"),
price: 1399,
},
{
id: 13,
type: "Smartphone",
brand: "Apple",
model: "iPhone SE",
color: "White",
capacity: "64 GB",
imgSrc: require("@/assets/iPhoneSE/iPhoneSEWhite.jpg"),
price: 399,
},
{
id: 14,
type: "Smartphone",
brand: "Apple",
model: "iPhone SE",
color: "White",
capacity: "64 GB",
imgSrc: require("@/assets/iPhoneSE/iPhoneSEWhite.jpg"),
price: 399,
},
{
id: 15,
type: "Smartphone",
brand: "Apple",
model: "iPhone SE",
color: "Red",
capacity: "64 GB",
imgSrc: require("@/assets/iPhoneSE/iPhoneSERed.jpg"),
price: 399,
},
{
id: 14,
type: "Smartphone",
brand: "Apple",
model: "iPhone SE",
color: "White",
capacity: "128 GB",
imgSrc: require("@/assets/iPhoneSE/iPhoneSEWhite.jpg"),
price: 499,
},
{
id: 15,
type: "Smartphone",
brand: "Apple",
model: "iPhone SE",
color: "White",
capacity: "128 GB",
imgSrc: require("@/assets/iPhoneSE/iPhoneSEWhite.jpg"),
price: 499,
},
{
id: 16,
type: "Smartphone",
brand: "Apple",
model: "iPhone SE",
color: "Red",
capacity: "128 GB",
imgSrc: require("@/assets/iPhoneSE/iPhoneSERed.jpg"),
price: 499,
},
{
id: 17,
type: "Smartphone",
brand: "Apple",
model: "iPhone SE",
color: "White",
capacity: "256 GB",
imgSrc: require("@/assets/iPhoneSE/iPhoneSEWhite.jpg"),
price: 599,
},
{
id: 18,
type: "Smartphone",
brand: "Apple",
model: "iPhone SE",
color: "White",
capacity: "256 GB",
imgSrc: require("@/assets/iPhoneSE/iPhoneSEWhite.jpg"),
price: 599,
},
{
id: 19,
type: "Smartphone",
brand: "Apple",
model: "iPhone SE",
color: "Red",
capacity: "256 GB",
imgSrc: require("@/assets/iPhoneSE/iPhoneSERed.jpg"),
price: 599,
},
{
id: 20,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi",
color: "Space Gray",
capacity: "128 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1299,
},
{
id: 21,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi",
color: "Silver",
capacity: "128 GB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 1299,
},
{
id: 22,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi",
color: "Space Gray",
capacity: "256 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1499,
},
{
id: 23,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi",
color: "Silver",
capacity: "256 GB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 1499,
},
{
id: 24,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi",
color: "Space Gray",
capacity: "512 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1599,
},
{
id: 25,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi",
color: "Silver",
capacity: "512 GB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 1599,
},
{
id: 26,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi",
color: "Space Gray",
capacity: "1 TB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1699,
},
{
id: 27,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi",
color: "Silver",
capacity: "1 TB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 1699,
},
{
id: 28,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi",
color: "Space Gray",
capacity: "2 TB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1799,
},
{
id: 29,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi",
color: "Silver",
capacity: "2 TB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 1799,
},
{
id: 30,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi",
color: "Space Gray",
capacity: "128 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1546,
},
{
id: 31,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi",
color: "Silver",
capacity: "128 GB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 1546,
},
{
id: 32,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi",
color: "Space Gray",
capacity: "256 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1599,
},
{
id: 33,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi",
color: "Silver",
capacity: "256 GB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 1599,
},
{
id: 34,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi",
color: "Space Gray",
capacity: "512 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1699,
},
{
id: 35,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi",
color: "Silver",
capacity: "512 GB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 1699,
},
{
id: 36,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi",
color: "Space Gray",
capacity: "512 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1799,
},
{
id: 37,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi",
color: "Silver",
capacity: "512 GB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 1799,
},
{
id: 38,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi",
color: "Space Gray",
capacity: "1 TB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1899,
},
{
id: 39,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi",
color: "Silver",
capacity: "1 TB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1899,
},
{
id: 40,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi",
color: "Space Gray",
capacity: "2 TB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1999,
},
{
id: 41,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi",
color: "Silver",
capacity: "2 TB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1999,
},
{
id: 42,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi + Cellular",
color: "Space Gray",
capacity: "128 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1799,
},
{
id: 43,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi + Cellular",
color: "Silver",
capacity: "128 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1799,
},
{
id: 44,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi + Cellular",
color: "Space Gray",
capacity: "256 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1899,
},
{
id: 45,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi + Cellular",
color: "Silver",
capacity: "256 GB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 1899,
},
{
id: 46,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi + Cellular",
color: "Space Gray",
capacity: "512 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1799,
},
{
id: 47,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi + Cellular",
color: "Silver",
capacity: "512 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1799,
},
{
id: 48,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi + Cellular",
color: "Space Gray",
capacity: "512 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1699,
},
{
id: 49,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi + Cellular",
color: "Silver",
capacity: "512 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1699,
},
{
id: 50,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi + Cellular",
color: "Space Gray",
capacity: "1 TB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1799,
},
{
id: 51,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi + Cellular",
color: "Silver",
capacity: "1 TB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 1799,
},
{
id: 52,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi + Cellular",
color: "Space Gray",
capacity: "2 TB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1899,
},
{
id: 53,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 11-inch display Wifi + Cellular",
color: "Silver",
capacity: "2 TB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 1899,
},
{
id: 54,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi + Cellular",
color: "Space Gray",
capacity: "128 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 1999,
},
{
id: 55,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi + Cellular",
color: "Silver",
capacity: "128 GB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 1999,
},
{
id: 56,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi + Cellular",
color: "Space Gray",
capacity: "256 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 2099,
},
{
id: 55,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi + Cellular",
color: "Silver",
capacity: "256 GB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 2099,
},
{
id: 56,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi + Cellular",
color: "Space Gray",
capacity: "512 GB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 2199,
},
{
id: 57,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi + Cellular",
color: "Silver",
capacity: "512 GB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 2199,
},
{
id: 58,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi + Cellular",
color: "Space Gray",
capacity: "1 TB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 2299,
},
{
id: 59,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi + Cellular",
color: "Silver",
capacity: "1 TB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 2299,
},
{
id: 60,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi + Cellular",
color: "Space Gray",
capacity: "2 TB",
imgSrc: require("@/assets/iPadPro/iPadProSpaceGray.jpg"),
price: 2399,
},
{
id: 61,
type: "Tablet",
brand: "Apple",
model: "iPad Pro 12.9-inch display Wifi + Cellular",
color: "Silver",
capacity: "2 TB",
imgSrc: require("@/assets/iPadPro/iPadProSilver.jpg"),
price: 2399,
},
{
id: 62,
type: "Smartphone",
brand: "Samsung",
model: "S21 5G",
color: "Phantom Grey",
capacity: "128 GB",
imgSrc: require("@/assets/S21/S21PhantomGrey.jpg"),
price: 799,
},
{
id: 63,
type: "Smartphone",
brand: "Samsung",
model: "S21 5G",
color: "Phantom Pink",
capacity: "128 GB",
imgSrc: require("@/assets/S21/S21PhantomPink.jpg"),
price: 799,
},
{
id: 64,
type: "Smartphone",
brand: "Samsung",
model: "S21 5G",
color: "Phantom Violet",
capacity: "128 GB",
imgSrc: require("@/assets/S21/S21PhantomViolet.jpg"),
price: 799,
},
{
id: 65,
type: "Smartphone",
brand: "Samsung",
model: "S21 5G",
color: "Phantom White",
capacity: "128 GB",
imgSrc: require("@/assets/S21/S21PhantomWhite.jpg"),
price: 799,
},
],
};
},
mutations: rootMutations,
actions: rootActions,
getters: rootGetters,
});
export default store;
Now, we are going to create three files "actions.js", "mutations.js", "getters.js"
store
|-- actions.js
|-- getters.js
|-- index.js
|-- mutations.js
This application required two actions :
- "addToCart"
- "removeFromCart"
And two mutations :
- "addProductToCart"
- "removeProductFromCart"
# ./store/actions.js
export default {
addToCart(context, payload) {
const prodId = payload.id;
const products = context.rootGetters.products;
const product = products.find((prod) => prod.id === prodId);
context.commit("addProductToCart", product);
},
removeFromCart(context, payload) {
context.commit("removeProductFromCart", payload);
},
};
And two mutations :
- "addProductToCart"
- "removeProductFromCart"
# ./store/mutations.js
export default {
addProductToCart(state, payload) {
const productData = payload;
const productInCartIndex = state.cart.findIndex(
(ci) => ci.id === productData.id
);
if (productInCartIndex >= 0) {
state.cart[productInCartIndex].qty++;
} else {
const newItem = {
id: productData.id,
type: productData.type,
brand: productData.brand,
model: productData.model,
color: productData.color,
capacity: productData.capacity,
imgSrc: productData.imgSrc,
price: productData.price,
qty: 1,
};
state.cart.push(newItem);
}
state.qty++;
state.total += productData.price;
},
removeProductFromCart(state, payload) {
const prodId = payload.id;
const productInCartIndex = state.cart.findIndex(
(cartItem) => cartItem.id === prodId
);
const prodData = state.cart[productInCartIndex];
state.cart.splice(productInCartIndex, 1);
state.qty -= prodData.qty;
state.total -= prodData.price * prodData.qty;
},
};
And four getters which return each state :
# ./store/getters
export default {
products(state) {
return state.products;
},
totalSum(state) {
return state.total;
},
quantity(state) {
return state.qty;
},
cart(state) {
return state.cart;
}
};
[ 2.1 ] Views & Components
We are going to create four views :
views
|-- AllProduts.vue
| # Display all products
|-- Smartphones.vue
| # Filter only Smartphones Products
|-- Tablets.vue
| # Filter only Tablets Products
|-- Cart.vue
# Display all products added to Cart
About the first view "All Products", we need to create three components :
AllProducts.vue
|-- Wrapper.vue
|-- ProductCard.vue
# ../views/AllProducts.vue
<template>
<Wrapper>
<ProductCard
v-for="product in products"
:key="product.id"
:id="product.id"
:type="product.type"
:brand="product.brand"
:model="product.model"
:color="product.color"
:capacity="product.capacity"
:imgSrc="product.imgSrc"
:price="product.price"
/>
</Wrapper>
</template>
<script>
import ProductCard from "@/components/ProductCard";
export default {
components: {
ProductCard,
},
computed: {
products() {
const productsSort = this.$store.getters["products"];
return productsSort.sort((a, b) => {
if (a.model < b.model) {
return -1;
}
if (a.model > b.model) {
return 1;
}
return 0;
});
},
},
};
</script>
# ../components/Wrapper.vue
<template>
<div class="wrapper">
<div class="product-container">
<slot />
</div>
</div>
</template>
<script>
export default {
name: "Wrapper",
}
</script>
<style>
.wrapper {
display: flex;
flex-direction: column;
justify-content: center;
}
.product-container {
padding-top: 170px;
display: flex;
flex-wrap: wrap;
justify-content: center;
flex-direction: row;
}
</style>
<template>
<div class="product-card">
<div class="image-container">
<img class="img-style" :src="imgSrc" :alt="brand + model" />
</div>
<p class="price-label">₿ {{ price.toFixed(2) }}</p>
<button @click="addToCart">Add to Cart</button>
<span class="product-title">
{{ brand }} {{ model }} {{ color }} {{ capacity }}
</span>
</div>
</template>
<script>
export default {
name: "ProductCard",
props: [
"id",
"type",
"brand",
"model",
"color",
"capacity",
"imgSrc",
"price",
],
methods: {
addToCart() {
this.$store.dispatch("addToCart", {
id: this.id,
type: this.type,
brand: this.brand,
model: this.model,
color: this.color,
capacity: this.capacity,
imgSrc: this.imgSrc,
price: this.price,
});
},
},
};
</script>
<style scoped>
.product-card {
display: flex;
flex-direction: column;
height: 450px;
max-height: 450px;
max-width: 250px;
background-color: white;
border-radius: 15px;
flex: 1 1 240px;
margin: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
border: 1px solid rgba(60, 60, 60, 0.2);
padding: 30px;
}
.img-style {
display: flex;
height: 250px;
object-fit: cover;
}
.image-container {
display: flex;
justify-content: center;
}
.product-title {
display: flex;
color: rgba(66, 185, 131, 1);
font-weight: bold;
height: 100%;
align-items: flex-start;
justify-content: center;
}
.price-label {
font-weight: bold;
font-size: 20px;
}
</style>
Why should we create components in two different folders ? Because "ProductCard.vue" and "Wrapper.vue" are reusable components ! We are going to use it again 💪 in "Smartphones.vue" and "Tablets.vue" :
# ../views/Smartphones.vue
<template>
<Wrapper>
<ProductCard
v-for="product in products"
:key="product.id"
:id="product.id"
:type="product.type"
:brand="product.brand"
:model="product.model"
:color="product.color"
:capacity="product.capacity"
:imgSrc="product.imgSrc"
:price="product.price"
/>
</Wrapper>
</template>
<script>
import ProductCard from "@/components/ProductCard";
export default {
components: {
ProductCard,
},
computed: {
products() {
const productsFilter = this.$store.getters["products"];
return productsFilter.filter((product) => {
if (product.type.includes("Smartphone")) {
return true;
} else {
return false;
}
});
},
},
};
</script>
# ../views/Tablets.vue
<template>
<Wrapper>
<ProductCard
v-for="product in products"
:key="product.id"
:id="product.id"
:type="product.type"
:brand="product.brand"
:model="product.model"
:color="product.color"
:capacity="product.capacity"
:imgSrc="product.imgSrc"
:price="product.price"
/>
</Wrapper>
</template>
<script>
import ProductCard from "@/components/ProductCard";
export default {
components: {
ProductCard,
},
computed: {
products() {
const productsFilter = this.$store.getters["products"];
return productsFilter.filter((product) => {
if (product.type.includes("Tablet")) {
return true;
} else {
return false;
}
});
},
},
};
</script>
This is how reusable components works ! Awesome right ? 👍
# ../views/Cart.vue
<template>
<Wrapper>
<div class="container-cart">
<h2>Your Cart</h2>
<h3>Total Amount: ₿ {{ cartTotal }}</h3>
<ul>
<CartCard
v-for="product in cartProducts"
:key="product.id"
:id="product.id"
:type="product.type"
:brand="product.brand"
:model="product.model"
:color="product.color"
:capacity="product.capacity"
:imgSrc="product.imgSrc"
:price="product.price"
:qty="product.qty"
></CartCard>
</ul>
</div>
</Wrapper>
</template>
<script>
import CartCard from "@/components/CartCard.vue";
export default {
components: {
CartCard,
},
computed: {
cartTotal() {
return this.$store.getters["totalSum"].toFixed(2);
},
cartProducts() {
return this.$store.getters["cart"];
},
},
};
</script>
<style scoped>
.container-cart {
display: flex;
flex-direction: column;
}
h2 {
color: #292929;
text-align: center;
border-bottom: 2px solid #ccc;
padding-bottom: 1rem;
}
h3 {
text-align: center;
}
ul {
list-style: none;
margin: 0;
padding: 0;
}
</style>
# ../components/CartCard.vue
<template>
<li>
<div class="image-container">
<img :src="imgSrc" :alt="model" />
</div>
<div>
<h3>{{ brand }} {{ model }} {{ color }} {{ capacity }}</h3>
<div class="item-data">
<div>
Price per Item:
<strong>₿ {{ price.toFixed(2) }}</strong>
</div>
<div>
Quantity:
<strong>{{ qty }}</strong>
</div>
</div>
<div class="item-total">Total: ₿ {{ itemTotal }}</div>
<button @click="remove">Remove</button>
</div>
</li>
</template>
<script>
export default {
props: [
"id",
"type",
"brand",
"model",
"color",
"capacity",
"imgSrc",
"price",
"qty",
],
computed: {
itemTotal() {
return (this.price * this.qty).toFixed(2);
},
},
methods: {
remove() {
this.$store.dispatch("removeFromCart", { id: this.id });
},
},
};
</script>
<style scoped>
.image-container {
display: flex;
align-items: center;
justify-content: center;
}
li {
margin: 1rem auto;
padding: 1rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
text-align: center;
max-width: 25rem;
border-radius: 15px;
border: 1px solid rgba(60, 60, 60, 0.2);
}
img {
display: flex;
height: 250px;
object-fit: cover;
}
.item-data {
display: flex;
justify-content: space-between;
}
.item-total {
font-weight: bold;
margin: 1rem 0;
border-top: 1px solid #474747;
border-bottom: 2px solid #474747;
padding: 0.25rem 0;
width: auto;
}
</style>
# ../components/TheHeader.vue
<template>
<div id="nav">
<router-link to="/">All Products</router-link>
<router-link to="/smartphones">Smartphones</router-link>
<router-link to="/tablets">Tablets</router-link>
<router-link to="/cart">Cart</router-link>
<span class="counter-cart" v-if="socketCart > 0"> {{ socketCart }} </span>
</div>
</template>
<script>
export default {
computed: {
socketCart() {
return this.$store.getters["quantity"];
},
},
};
</script>
<style>
#nav {
position: fixed;
background: rgb(255, 255, 255);
background: linear-gradient(
180deg,
rgba(255, 255, 255, 1) 0%,
rgba(255, 255, 255, 1) 94%,
rgba(255, 255, 255, 0) 100%
);
width: 100%;
padding: 30px;
}
#nav a {
font-weight: bold;
color: #2c3e50;
text-decoration: none;
line-height: 30px;
padding: 0px 30px 0px 30px;
}
#nav a.router-link-exact-active {
color: #42b983;
text-decoration: none;
padding: 0px 30px 0px 30px;
line-height: 30px;
}
.counter-cart {
display: inline-block;
padding: 0.3rem 0.7rem 0.3rem 0.7rem;
background-color: rgba(162, 205, 2, 1);
color: white;
border-radius: 50px;
}
</style>
# ../App.vue
<template>
<TheHeader />
<router-view />
</template>
<script>
import TheHeader from "./components/TheHeader.vue";
export default {
components: {
TheHeader,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
body {
margin: 0;
}
button {
font: inherit;
cursor: pointer;
background-color: rgba(66, 185, 131, 1);
color: white;
border: 1px solid rgba(66, 185, 131, 1);
padding: 0.5rem 1.5rem;
border-radius: 30px;
margin-bottom: 20px;
}
button:hover,
button:active {
background-color: #82dcb1;
border-color: #82dcb1;
}
</style>
[ 2.2 ] Router
Every components are ready now. We can configuring the router.
# ../router/index.js
import { createRouter, createWebHistory } from "vue-router";
import AllProducts from "../views/AllProducts.vue";
const routes = [
{
path: "/",
name: "AllProducts",
component: AllProducts,
},
{
path: "/smartphones",
name: "Smartphones",
component: () => import("../views/Smartphones.vue"),
},
{
path: "/tablets",
name: "Tablets",
component: () => import("../views/Tablets.vue"),
},
{
path: "/cart",
name: "cart",
component: () => import("../views/Cart.vue"),
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;
# ../main.js
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store/index.js";
import Wrapper from "@/components/Wrapper";
const app = createApp(App);
app.use(router);
app.use(store);
app.component("Wrapper", Wrapper);
app.mount("#app");
Done ! You can try this Shopping Cart Application here :
https://shopping-cart-demo-sith.netlify.app
See you soon in next episode of "Portfolio Apps" series.
Top comments (0)