Review App Using Laravel 11 & Vue js 3 Composition API Part 4

3 months ago admin Vuejs

In the fourth part of this tutorial, we will fetch and display all the products on the home page, view product details, and add the navbar menu so we can move between pages.


Add the home component

Inside the home component, we fetch all the products from the backend and send them to the product list component.

                                                    
                                                                                                                
  <template>
    <ProductList :products="data.products" />
  </template>
  
  <script setup>
    import axios from "axios"
    import { onMounted, reactive } from 'vue'
    import ProductList from "./products/ProductList.vue"

    const data = reactive({
      products: []
    })

    const fetchAllProducts = async () => {
      try {
        const response = await axios.get('https://darija-coding.com/api/products')
        data.products = response.data.data
      } catch (error) {
        console.log(error)
      }
    }

    onMounted(() => fetchAllProducts())
  </script>
  
  <style>
  </style>

Add the product list component

Inside the product list component, we receive the products,  loop through, and send each product to the product list item component.

                                                        
                                                                                                                        
<template>
    <div class="row my-4">
        <ProductListItem v-for="product in products"
            :key="product.id" :product="product" />
    </div>
  </template>
  
  <script setup>
    import ProductListItem from "./ProductListItem.vue"

    const props = defineProps({
        products: {
            type: Array,
            required: true
        }
    })
  </script>
  
  <style>
  
  </style>

Add the product list item component

Inside the product list item component, we receive the product, display the details, calculate the average of the ratings, and display it.

                                                        
                                                                                                                        
<template>
  <div class="col-md-4 mb-2">
    <div class="card h-100">
        <img :src="product.image" alt="Product Image" 
            class="card-img-top"
            >
        <div class="card-body">
            <div class="card-title">
                {{ product.name }}
            </div>
            <p class="card-text">
                {{ product.desc }}
            </p>
            <p>
                <span class="fw-bold text-danger">
                    $ {{ product.price }}
                </span>
            </p>
            <p v-if="product.reviews.length > 0">
                <StarRating 
                  v-model:rating="reviewAvg"
                  read-only
                  :star-size="24"
                />
            </p>
            <router-link :to="`product/${product.id}`"
                class="btn btn-dark">
                <i class="bi bi-eye"></i> View
            </router-link>
        </div>
    </div>
  </div>
</template>

<script setup>
    import { computed } from 'vue'
    import StarRating from 'vue-star-rating'

    const props = defineProps({
        product: {
            type: Object,
            required: true
        }
    })

    //calculate the average of the ratings
    const reviewAvg = computed(() => parseInt(props.product.reviews.reduce((acc, review) => acc + review.rating / props.product.reviews.length, 0)))
</script>

<style>

</style>

Add the product component

Inside the product component, we fetch the product, using the ID, display the details, calculate the average of the ratings, and display it.

Also, we display the reviews of this product and the add/update review form that we will add in the next tutorial.

                                                        
                                                                                                                        
<template>
  <div class="row my-5" v-if="data.product">
    <div class="col-md-10 mx-auto">
      <div class="card mb-3">
        <div class="row g-0">
          <div class="col-md-4">
            <img :src="data.product.image" alt="Product Image" 
                class="img-fluid rounded-start">
          </div>
          <div class="col-md-8">
            <div class="card-body">
              <h5 class="card-text">
                {{  data.product.name }}
              </h5>
              <p class="card-text">
                {{ data.product.desc }}
              </p>
              <p>
                <small class="text-body-secondary">
                  $ {{  data.product.price }}
                </small>
              </p>
              <p v-if="data.product.reviews.length > 0">
                <StarRating 
                  v-model:rating="reviewAvg"
                  read-only
                  :star-size="24"
                />
              </p>
            </div>
          </div>
        </div>
      </div>
      <div v-if="data.product.reviews.length > 0">
        <ReviewsList 
          :reviews="data.product.reviews"
          @editReviewEvent="editReview"
          @removeReviewEvent="deleteReview"
        />
      </div>
      <div class="my-3">
        <div class="card">
          <div class="card-header bg-white text-center">
            <h5 class="mt-2" v-if="!data.reviewToUpdate.updating">Add a review</h5>
            <h5 class="mt-2" v-else>Edit a review</h5>
          </div>
          <div class="card-body">
            <AddReview 
              v-if="!data.reviewToUpdate.updating"
              :product="data.product"
              @reviewAdded="setProduct"
            />
            <UpdateReview 
              v-else
              :reviewToUpdate="data.reviewToUpdate.data"
              :product="data.product"
              @cancelUpdating="cancelUpdating"
              @reviewUpdated="setProduct"
            />
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
    import { computed, onMounted, reactive } from 'vue'
    import { useRoute } from 'vue-router'
    import axios from 'axios'
    import AddReview from '../reviews/AddReview.vue'
    import UpdateReview from '../reviews/UpdateReview.vue'
    import StarRating from 'vue-star-rating'
    import ReviewsList from '../reviews/ReviewsList.vue'
    import { useToast } from 'vue-toastification'

    const route = useRoute()

    const toast = useToast()

    const data = reactive({
      product: null,
      reviewToUpdate: {
        updating: false,
        data: null
      }
    })

    //calculate the average of the ratings
    const reviewAvg = computed(() => parseInt(data.product.reviews.reduce((acc, review) => acc + review.rating / data.product.reviews.length, 0)))

    const fetchAllProductById = async () => {
      try {
        const response = await axios.get(`https://darija-coding.com/api/product/${route.params.id}/show`)
        data.product = response.data.data
      } catch (error) {
        console.log(error)
      }
    }

    const setProduct = (newProductData) => {
      data.product = newProductData
      if(data.reviewToUpdate.updating) {
        data.reviewToUpdate =  {
          updating: false,
          data: null
        }
      }
    }

    const editReview = (review) => {
      data.reviewToUpdate = {
        updating: true,
        data: review
      }
    }

    const cancelUpdating = () => {
      if(data.reviewToUpdate.updating) {
        data.reviewToUpdate =  {
          updating: false,
          data: null
        }
      }
    }

    const deleteReview = async (review_id) => {
      if(confirm('are you sure you want to remove this review?')) {
        try {
            const response = await axios.post(`https://darija-coding.com/api/review/${data.product.id}/delete`,
                {
                  review_id
                }
            )

            data.product = response.data.data

            toast.success('Review has been deleted successfully', {
                timeout: 2000
            })

        } catch (error) {
            console.log(error)
        }
      }
    }

    onMounted(() => fetchAllProductById())
</script>

<style>

</style>

Add the header component

Inside the Header Component, we have the navigation menu.

                                                        
                                                                                                                        
<template>
    <nav class="navbar navbar-expand-lg bg-body-tertiary">
        <div class="container-fluid">
            <router-link class="navbar-brand" to="/">
                <i class="bi bi-star h1"></i>
            </router-link>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarSupportedContent">
                <ul class="navbar-nav mx-auto mb-2 mb-lg-0">
                    <li class="nav-item">
                        <router-link class="nav-link active" aria-current="page" to="/">
                            <i class="i bi-house"></i> Home
                        </router-link>
                    </li>
                </ul>
            </div>
        </div>
    </nav>
  </template>
  
  <script setup>
  </script>
  
  <style>
  
  </style>

Related Tuorials

How to Persist Data in the Pinia Store

In this lesson, we will see how to persist data in the Pinia store.When working with Pinia...


How to Reset the File input Field in Vue js

In this lesson, we will see how to reset the file input field in Vue js.When uploading files using V...


Review App Using Laravel 11 & Vue js 3 Composition API Part 5

In the last part of this tutorial, we will display the reviews list of each product, add the ability...


Review App Using Laravel 11 & Vue js 3 Composition API Part 3

In the third part of this tutorial, we will start coding the front end, first, we will install the p...


Review App Using Laravel 11 & Vue js 3 Composition API Part 2

In the second part of this tutorial, we will create the product and review controllers, and later we...


Review App Using Laravel 11 & Vue js 3 Composition API Part 1

In this tutorial, we will create a review app using Laravel 11 & Vue js 3 Composition API, the user...


Build a Shopping Cart Using Vue js 3 Composition API Laravel 11 & Stripe Payment Gateway Part 5

In the final part of this tutorial, we will display the cart items, add the ability to increment/dec...


Build a Shopping Cart Using Vue js 3 Composition API Laravel 11 & Stripe Payment Gateway Part 4

In the fourth part of this tutorial, we will fetch and display all the products, and add the store w...


Build a Shopping Cart Using Vue js 3 Composition API Laravel 11 & Stripe Payment Gateway Part 3

In the third part of this tutorial, we will move to the front end, we will install the packages...


Build a Shopping Cart Using Vue js 3 Composition API Laravel 11 & Stripe Payment Gateway Part 2

In the second part of this tutorial, we will create the product and order controllers, add the...