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 to add/update reviews, and remove reviews.
Add the reviews list component
Inside the reviews list component, we display all the reviews with buttons to edit and delete a selected review.
<template>
<div class="card mb-2">
<div class="card-header bg-white">
<h5 class="text-center mt-2">
Reviews ({{ props.reviews.length }})
</h5>
</div>
<div class="card-body">
<ul class="mt-4 list-group">
<li class="list-group-item d-flex justify-content-between align-items-start"
v-for="review in props.reviews"
:key="review.id"
>
<div class="ms-2 me-auto">
<div class="fw-bold">
{{ review.title }}
</div>
<p>
{{ review.body }}
</p>
<p class="cart-text">
<small class="text-body-seconda">
by {{ review.user.name }} - <span class="text-danger">{{ review.created_at }}</span>
</small>
</p>
<p>
<StarRating
v-model:rating="review.rating"
:show-rating="false"
read-only
:star-size="24"
/>
</p>
</div>
<div class="d-flex flex-column align-items-center">
<button class="btn btn-sm btn-danger mb-2"
@click="removeReview(review)">
<i class="bi bi-trash"></i>
</button>
<button class="btn btn-sm btn-warning mb-2"
@click="editReview(review)">
<i class="bi bi-pencil"></i>
</button>
</div>
</li>
</ul>
</div>
</div>
</template>
<script setup>
import StarRating from 'vue-star-rating'
const props = defineProps({
reviews: {
type: Array,
required: true
}
})
const emit = defineEmits(['editReviewEvent', 'removeReviewEvent'])
const editReview = (review) => {
emit('editReviewEvent', review)
}
const removeReview = (review) => {
emit('removeReviewEvent', review.id)
}
</script>
<style>
</style>
Add the add review component
Inside, the add review component we have the form to review a selected product.
<template>
<form class="mt-5">
<div class="mb-3">
<label for="title">Title*</label>
<input type="text" name="title" id="title"
v-model="data.review.title"
class="form-control"
placeholder="Title">
</div>
<div class="mb-3">
<label for="body">Body*</label>
<textarea
rows="3"
cols="30"
name="body" id="body"
v-model="data.review.body"
class="form-control"
placeholder="Body"></textarea>
</div>
<div class="mb-3">
<StarRating
v-model:rating="data.review.rating"
:show-rating="false"
/>
</div>
<div class="mb-3">
<button class="btn btn-dark"
:disabled="disabled"
@click.prevent="storeReview">
Submit
</button>
</div>
</form>
</template>
<script setup>
import { computed, reactive } from 'vue'
import StarRating from 'vue-star-rating'
import axios from "axios"
import { useToast } from 'vue-toastification'
const toast = useToast()
const data = reactive({
review: {
title: '',
body: '',
rating: 0
}
})
const props = defineProps({
product: {
type: Object,
required: true
}
})
const emit = defineEmits(['reviewAdded'])
const disabled = computed(() => {
if(!data.review.title || !data.review.body || data.review.rating === 0){
return true
}else {
false
}
})
const storeReview = async () => {
try {
const response = await axios.post(`https://darija-coding.com/api/review/${props.product.id}/store`,
{
title: data.review.title,
body: data.review.body,
rating: data.review.rating,
user_id: Math.floor(Math.random() * 10) + 1
//generate a random user id between 1 and 10
}
)
data.review = {
title: '',
body: '',
rating: 0
}
emit('reviewAdded', response.data.data)
toast.success('Review has been added successfully', {
timeout: 2000
})
} catch (error) {
console.log(error)
}
}
</script>
<style>
</style>
Add the update review component
Inside, the update review component we have the form to update a selected review.
<template>
<form class="mt-5">
<div class="mb-3">
<label for="title">Title*</label>
<input type="text" name="title" id="title"
v-model="data.review.title"
class="form-control"
placeholder="Title">
</div>
<div class="mb-3">
<label for="body">Body*</label>
<textarea
rows="3"
cols="30"
name="body" id="body"
v-model="data.review.body"
class="form-control"
placeholder="Body"></textarea>
</div>
<div class="mb-3">
<StarRating
v-model:rating="data.review.rating"
:show-rating="false"
/>
</div>
<div class="mb-3">
<button class="btn btn-warning"
:disabled="disabled"
@click.prevent="updateReview">
Update
</button>
<button class="btn btn-primary mx-2"
@click.prevent="cancelUpdate">
Cancel
</button>
</div>
</form>
</template>
<script setup>
import { computed, onMounted, reactive } from 'vue'
import StarRating from 'vue-star-rating'
import axios from "axios"
import { useToast } from 'vue-toastification'
const toast = useToast()
const data = reactive({
review: {
title: '',
body: '',
rating: 0
}
})
const props = defineProps({
reviewToUpdate: {
type: Object,
required: true
},
product: {
type: Object,
required: true
}
})
const emit = defineEmits(['reviewUpdated', 'cancelUpdating'])
const disabled = computed(() => {
if(!data.review.title || !data.review.body || data.review.rating === 0){
return true
}else {
false
}
})
const updateReview = async () => {
try {
const response = await axios.put(`https://darija-coding.com/api/review/${props.product.id}/${data.review.id}/update`,
{
title: data.review.title,
body: data.review.body,
rating: data.review.rating,
user_id: Math.floor(Math.random() * 10) + 1
//generate a random user id between 1 and 10
}
)
data.review = {
title: '',
body: '',
rating: 0
}
emit('reviewUpdated', response.data.data)
toast.success('Review has been updated successfully', {
timeout: 2000
})
} catch (error) {
console.log(error)
}
}
const cancelUpdate = () => {
emit('cancelUpdating')
}
onMounted(() => data.review = props.reviewToUpdate)
</script>
<style>
</style>