![Building Ecommerce Website With PHP Lumen Laravel And Nuxtjs](https://webmobtuts.com/wp-content/uploads/2020/12/Building-Ecommerce-Website-With-PHP-Lumen-Laravel-And-Nuxtjs-2-800x400.jpg)
In this part we will continue working on the checkout process and in this article we will save the order data into database, after that we will display the orders list in the designated page in the user profile.
Updating Order Models
In the lumen project modify the Order and OrderDetail models
app/Models/Order.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Order extends Model { protected $appends = ["created_at_formatted", "updated_at_formatted"]; public function user() { return $this->belongsTo(User::class, 'user_id'); } public function paymentMethod() { return $this->belongsTo(PaymentMethod::class, 'payment_method_id'); } public function shippingAddress() { return $this->belongsTo(ShippingAddress::class, 'shipping_address_id'); } public function orderDetails() { return $this->hasMany(OrderDetail::class, 'order_id')->with("product"); } public function getCreatedAtFormattedAttribute() { return $this->created_at->format('F d, Y h:i'); } public function getUpdatedAtFormattedAttribute() { return $this->updated_at->format('F d, Y h:i'); } }
app/Models/OrderDetail.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class OrderDetail extends Model { public function product() { return $this->belongsTo(Product::class, 'product_id')->with("gallery"); } }
Open app/Http/Controllers/OrdersController.php and update the store() method:
<?php namespace App\Http\Controllers; use App\Models\Order; use App\Models\OrderDetail; use App\Models\PaymentMethod; use App\Models\Product; use App\Models\ShoppingCart; use App\Traits\Helpers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Validator; class OrdersController extends Controller { use Helpers; public function __construct() { } public function index(Request $request) { } public function store(Request $request) { $user = Auth::user(); $cart = ShoppingCart::where("user_id", $user->id)->get(); if(!$cart) { return response()->json(["success" => 0, "message" => "You must have items in the cart in order to checkout!"], 500); } $validator = Validator::make($request->all(), [ "payment_method" => "required|exists:payment_methods,slug", "shipping_address_id" => "required|exists:shipping_addresses,id" ]); if($validator->fails()) { return response()->json(['success' => 0, 'message' => 'Required fields', 'errors' => $validator->errors()], 500); } $paymentMethod = PaymentMethod::where('slug', $request->input("payment_method"))->first(); $order = new Order(); $order->user_id = $user->id; if($request->input("payment_method") !== 'paypal') { $order->status = "pending"; } else { $order->status = $request->input("status"); } $order->status_message = $request->input("status_message"); $order->payment_method_id = $paymentMethod->id; $order->shipping_address_id = $request->input("shipping_address_id"); $order->total_price = $this->getOrderTotal($cart); if($request->has("paypal_order_id")) { $order->paypal_order_identifier = $request->input("paypal_order_id"); } if($request->has("paypal_email")) { $order->paypal_email = $request->input("paypal_email"); } if($request->has("paypal_given_name")) { $order->paypal_given_name = $request->input("paypal_given_name"); } if($request->has("paypal_payer_id")) { $order->paypal_payer_id = $request->input("paypal_payer_id"); } $order->save(); // saving order details foreach ($cart as $item) { $orderDetails = new OrderDetail(); $orderDetails->order_id = $order->id; $orderDetails->product_id = $item->product_id; $orderDetails->price = $item->product->price_after_discount_numeric; $orderDetails->amount = $item->amount; $orderDetails->save(); } // clear shopping cart foreach ($cart as $item) { $item->delete(); } // if order cancelled restore the order products into the product inventory if($order->status == "cancelled") { $this->restoreProducts($order); } $order = Order::with("user")->find($order->id); return response()->json(['success' => 1, "message" => "Order saved successfully!", "order" => $order]); } public function show($id) { } public function update(Request $request, $id) { } public function getLatestPendingOrders() { } private function getOrderTotal($cart) { $total = 0; foreach ($cart as $item) { $total += $item->total_price_numeric; } return $total; } private function restoreProducts($order) { $orderDetails = OrderDetail::where('order_id', $order->id)->get(); if (!$orderDetails) { return; } foreach ($orderDetails as $item) { $amount = $item->amount; $product = Product::find($item->product_id); if ($product) { $product->increment('amount', $amount); } } } }
In this code the store() method retrieves the current user cart and check if it’s not empty. Then using laravel validator i validate the request for a payment_method and shipping_address_id. Then i start saving the order and i check if the payment method not paypal then we set the status=’pending’, otherwise the status will be the incoming status from the request.
After saving the order record i iterate over the cart items and save the order details using the OrderDetail model. Then we clear the cart.
If the order status=’cancelled’ then we restore the products deducted amount into the products table again with the restoreProducts() method. And finally i return a success response that the order is saved.
Now the save functionality is completed, you can experiment to add products into the cart and start checkout with paypal or pay on delivery payments.
Displaying Orders
Let’s complete the orders cycle by displaying them into the user profile, so modify the index() methods as shown:
public function index(Request $request) { $orders = $this->retrieveOrders($request); return response()->json(["orders" => $orders]); }
protected function retrieveOrders($request) { if ($this->superAdminCheck()) { $data = Order::with("orderDetails", "paymentMethod", "shippingAddress", "user"); } else { $data = Order::with("orderDetails", "paymentMethod", "shippingAddress", "user")->where("user_id", Auth::user()->id); } if($request->has("orderId")) { $data->where("id", $request->input("orderId")); } if($request->has("userId")) { $data->where("user_id", $request->input("userId")); } if($request->has("status")) { $data->where("status", $request->input("status")); } $orders = $data->orderBy("id", "DESC"); if ($this->superAdminCheck()) { return $orders->paginate(20); } else { return $orders->get(); } }
The index() method displays the user orders for normal users and for the admin user it display all orders so i added another protected method retrieveOrders() which checks if the current user is admin by calling $this->superAdminCheck() , this method defined in the Helpers trait so i fetch all orders, otherwise we retrieve the user orders. The method also do some filteration by orderId, status, or userId.
Now move to the Nuxt project and update the orders page
pages/orders.vue
<template> <div class="container"> <div class="bg"> <div class="row"> <div class="col-sm-12"> <h2 class="title text-center">My Orders</h2> </div> </div> <div class="row"> <div class="col-md-3"> <account-sidebar></account-sidebar> </div> <div class="col-sm-9 col-md-9"> <table class="table table-responsive table-bordered"> <thead> <tr style="background: #ccc"> <th>#OrderId</th> <th>Status</th> <th>Price</th> <th>Payment Method</th> <th>Date</th> <th>Shipping Address</th> <th></th> </tr> </thead> <tbody> <template v-for="order in myOrders"> <tr :key="order.id"> <td>#{{ order.id }}</td> <td>{{ order.status }}</td> <td>${{ order.total_price }}</td> <td>{{ order.payment_method.name }}</td> <td>{{ order.created_at }}</td> <td>{{ order.shipping_address.address }}</td> <td><a href="#" class="btn btn-primary btn-sm" title="View details" @click.prevent="toggleDetails(order)"><i class="fa fa-chevron-down"></i></a></td> </tr> <tr class="detail-row" v-if="order.show_details"> <td colspan="7"> <div class="order-items"> <h4 style="color: #6a6a57">Products</h4> <ul> <li v-for="orderDetails in order.order_details" style="border-bottom: 1px solid #fff"> <img :src="orderDetails.product.gallery[0].image_url.product_gallery_slider" v-bind:alt="orderDetails.product.title"> <strong style="font-size: 13px">{{orderDetails.product.title}}</strong> <p>Unit Price: ${{ orderDetails.price }}</p> <p>Quantity: {{ orderDetails.amount }}</p> </li> </ul> </div> </td> </tr> </template> </tbody> </table> </div> </div> </div> </div> </template> <script> import AccountSidebar from "../components/account-components/AccountSidebar"; import {OrdersApi} from "../api/order"; export default { name: "orders", components: {AccountSidebar}, middleware: "auth", data() { return { myOrders: [] } }, mounted() { OrdersApi.list(this.$axios).then(response => { response.orders.map(item => { item.show_details = false; }); this.myOrders = response.orders; }); }, methods: { toggleDetails(order) { order.show_details = !order.show_details; } } } </script>
Now you can see the orders along with the order status and the order products in the orders page.
Â
Displaying Orders In The Admin
After displaying the orders in the user profile let’s handle the orders module in the admin panel similar to any other modules we have implemented so we have these to-do list in the admin:
- Displaying all orders.
- Displaying all pending orders so that the admin can either verify or cancel it.
- Using real-time notifications to display notifications with Pusher.
At first in the lumen project modify the OrdersController and complete the remaining method codes as follows:
app/Http/Controller/OrdersController.php
<?php namespace App\Http\Controllers; use App\Models\Order; use App\Models\OrderDetail; use App\Models\PaymentMethod; use App\Models\Product; use App\Models\ShoppingCart; use App\Traits\Helpers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Validator; class OrdersController extends Controller { use Helpers; public function __construct() { $this->middleware('super_admin_check:show-update-getLatestPendingOrders'); } public function index(Request $request) { $orders = $this->retrieveOrders($request); return response()->json(["orders" => $orders]); } public function store(Request $request) { $user = Auth::user(); $cart = ShoppingCart::where("user_id", $user->id)->get(); if(!$cart) { return response()->json(["success" => 0, "message" => "You must have items in the cart in order to checkout!"], 500); } $validator = Validator::make($request->all(), [ "payment_method" => "required|exists:payment_methods,slug", "shipping_address_id" => "required|exists:shipping_addresses,id" ]); if($validator->fails()) { return response()->json(['success' => 0, 'message' => 'Required fields', 'errors' => $validator->errors()], 500); } $paymentMethod = PaymentMethod::where('slug', $request->input("payment_method"))->first(); $order = new Order(); $order->user_id = $user->id; if($request->input("payment_method") !== 'paypal') { $order->status = "pending"; } else { $order->status = $request->input("status"); } $order->status_message = $request->input("status_message"); $order->payment_method_id = $paymentMethod->id; $order->shipping_address_id = $request->input("shipping_address_id"); $order->total_price = $this->getOrderTotal($cart); if($request->has("paypal_order_id")) { $order->paypal_order_identifier = $request->input("paypal_order_id"); } if($request->has("paypal_email")) { $order->paypal_email = $request->input("paypal_email"); } if($request->has("paypal_given_name")) { $order->paypal_given_name = $request->input("paypal_given_name"); } if($request->has("paypal_payer_id")) { $order->paypal_payer_id = $request->input("paypal_payer_id"); } $order->save(); // saving order details foreach ($cart as $item) { $orderDetails = new OrderDetail(); $orderDetails->order_id = $order->id; $orderDetails->product_id = $item->product_id; $orderDetails->price = $item->product->price_after_discount_numeric; $orderDetails->amount = $item->amount; $orderDetails->save(); } // clear shopping cart foreach ($cart as $item) { $item->delete(); } // if order cancelled restore the order products into the product inventory if($order->status == "cancelled") { $this->restoreProducts($order); } $order = Order::with("user")->find($order->id); return response()->json(['success' => 1, "message" => "Order saved successfully!", "order" => $order]); } public function show($id) { $order = Order::with("orderDetails", "paymentMethod", "shippingAddress", "user")->find($id); if(!$order) { return response()->json(['success' => 0, 'message' => 'Not found'], 404); } return response()->json(['orderDetails' => $order], 200); } public function update(Request $request, $id) { $order = Order::find($id); $order->status = $request->input("status"); $order->save(); return response()->json(['success' => 1, "message" => "Order updated!", "order" => $order]); } public function getLatestPendingOrders() { $topOrders = Order::with("user")->where("status", "pending")->orderBy("created_at", "DESC")->limit(4)->get(); $countAllPending = Order::with("user")->where("status", "pending")->count(); return response()->json(["topOrders" => $topOrders, "countAllPending" => $countAllPending]); } private function getOrderTotal($cart) { $total = 0; foreach ($cart as $item) { $total += $item->total_price_numeric; } return $total; } private function restoreProducts($order) { $orderDetails = OrderDetail::where('order_id', $order->id)->get(); if (!$orderDetails) { return; } foreach ($orderDetails as $item) { $amount = $item->amount; $product = Product::find($item->product_id); if ($product) { $product->increment('amount', $amount); } } } protected function retrieveOrders($request) { if ($this->superAdminCheck()) { $data = Order::with("orderDetails", "paymentMethod", "shippingAddress", "user"); } else { $data = Order::with("orderDetails", "paymentMethod", "shippingAddress", "user")->where("user_id", Auth::user()->id); } if($request->has("orderId")) { $data->where("id", $request->input("orderId")); } if($request->has("userId")) { $data->where("user_id", $request->input("userId")); } if($request->has("status")) { $data->where("status", $request->input("status")); } $orders = $data->orderBy("id", "DESC"); if ($this->superAdminCheck()) { return $orders->paginate(20); } else { return $orders->get(); } } }
In this code i updated the show() and update() methods, the update() method all it do is to update the order status which will be called from the admin panel to update the order to either complete or cancel. Also i added another method getLatestPendingOrders(), we will use this method to display the latest pending orders in the admin panel top header.
Now let’s move to the Nuxt project and start preparing the orders pages, so open online-shop-frontend/admin/api and add orders.js file
online-shop-frontend/admin/api/orders.js
const OrdersApi = { setAuthToken: (axios) => { axios.setHeader('Authorization', "Bearer " + localStorage.getItem('auth_token')); }, list: (axios, data = "") => { OrdersApi.setAuthToken(axios); return axios.$get('/api/orders' + (data ? "?" + data : "")); }, show: (axios, id) => { OrdersApi.setAuthToken(axios); return axios.$get('/api/orders/' + id); }, update: (axios, id, status) => { OrdersApi.setAuthToken(axios); return axios.$put('/api/orders/' + id, {status}); }, getLatestPending: (axios) => { OrdersApi.setAuthToken(axios); return axios.$get('/api/orders/latest-pending-orders'); }, } export {OrdersApi};
Then create a new store for the orders in admin/store directory
admin/store/orders.js
import { OrdersApi } from '../api/orders'; export const state = () => ({ filterData: { orderId: '', userId: '', status: '' }, page: 1, ordersList: {}, orderDetails: {} }); export const mutations = { setOrdersList(state, data) { state.ordersList = data; }, setPage(state, page) { state.page = page; }, setFilterData(state, value) { state.filterData[value.key] = value.val; }, resetFilter(state) { for(let field in state.filterData) { state.filterData[field] = ""; } }, setOrderDetails(state, orderDetails) { state.orderDetails = orderDetails; }, setOrderStatus(state, payload) { const itemIndex = state.ordersList.data.findIndex(i => i.id == payload.id); state.ordersList.data[itemIndex].status = payload.status; }, removeOrder(state, id) { state.ordersList.data = [...state.ordersList.data.filter(i => i.id != id)]; } }; export const actions = { listOrders({commit}, filterData = "") { OrdersApi.list(this.$axios, filterData).then(response => { commit('setOrdersList', response.orders); }).catch(err => { console.log(err); }); }, showOrder({commit}, id) { OrdersApi.show(this.$axios, id).then(response => { if(response.orderDetails) { commit('setOrderDetails', response.orderDetails); } }).catch(err => { console.log(err); }); }, updateOrder({commit}, payload) { commit('shared/resetStatusMessagesParameters', null, {root: true}); commit('shared/setStatusMessageParameter', {key: 'showLoading', val: true}, {root: true}); OrdersApi.update(this.$axios, payload.id, payload.status).then(response => { commit('shared/setStatusMessageParameter', {key: 'showLoading', val: false}, {root: true}); commit('shared/setStatusMessageParameter', {key: 'success_message', val: response.message}, {root: true}); commit('setOrderStatus', payload); commit('removeOrder', payload.id); }).catch(err => { console.log(err); }).finally(() => { setTimeout(() => { commit('shared/resetStatusMessagesParameters', null, {root: true}); }, 3000); }); } };
This store the same as other stores we created previously for products or categories. This store handle retrieving and storing orders list, order filteration, displaying single order details and update order status.
Orders Pages
Create a new directory named orders/ inside admin/pages/. Then create these two files:
- admin/pages/orders/index.vue: To display all orders
- admin/pages/orders/pending: To display pending orders.
admin/pages/orders/index.vue
<template> <div class="main-content"> <div class="section__content section__content--p30"> <div class="container-fluid"> <h3>All Orders</h3> <OrdersGrid page-type="allOrders"></OrdersGrid> </div> </div> </div> </template> <script> import OrdersGrid from "../../components/orders-components/OrdersGrid"; export default { name: "Orders", components: {OrdersGrid}, middleware: "auth", fetch() { this.$store.commit('orders/resetFilter'); this.$store.dispatch('orders/listOrders'); } } </script>
admin/pages/orders/pending.vue
<template> <div class="main-content"> <div class="section__content section__content--p30"> <div class="container-fluid"> <h3>Pending Orders</h3> <OrdersGrid page-type="pending"></OrdersGrid> </div> </div> </div> </template> <script> import OrdersGrid from "../../components/orders-components/OrdersGrid"; export default { name: "PendingOrders", components: {OrdersGrid}, middleware: "auth", fetch() { this.$store.commit('orders/resetFilter'); this.$store.dispatch('orders/listOrders', "status=pending"); } } </script>
admin/components/orders-components/OrdersGrid.vue
<template> <div class="row"> <div class="col-md-12"> <loader></loader> <status-messages></status-messages> <OrdersFilter v-on:Filtering="handleFiltering" :page-type="this.pageType" v-on:resetFilter="resetFilter"></OrdersFilter> </div> <div class="col-md-12"> <!-- DATA TABLE--> <div class="table-responsive m-b-40"> <table class="table table-borderless table-data3"> <thead> <tr> <th>#</th> <th>User</th> <th>Status</th> <th>Payment Method</th> <th>Shipping Address</th> <th>Total Price</th> <th>Paypal Indentifier</th> <th>Options</th> </tr> </thead> <tbody> <OrdersRow v-if="ordersList.data && ordersList.data.length > 0" v-for="order of ordersList.data" v-bind:order="order" v-bind:key="order.id" @viewOrder="showDetailModal"></OrdersRow> <tr v-if="!ordersList.data || (ordersList.data && ordersList.data.length == 0)"> <td colspan="8"> <p class="text-center">No data</p> </td> </tr> </tbody> </table> </div> <Pagination :data="ordersList" v-if="ordersList.data" v-on:handlePagination="handlePagination"></Pagination> </div> <OrderDetailsModal @closeDetailModal="dismissModal" v-if="this.showModal"></OrderDetailsModal> </div> </template> <script> import Loader from "../helpers/loader"; import StatusMessages from "../helpers/statusMessages"; import OrdersFilter from "./OrdersFilter"; import OrdersRow from "./OrdersRow"; import Pagination from "../helpers/Pagination"; import OrderDetailsModal from "./OrderDetailsModal"; export default { name: "OrdersGrid", props: ["pageType"], components: { OrderDetailsModal, Pagination, OrdersRow, OrdersFilter, StatusMessages, Loader }, data() { return { showModal: false } }, computed: { ordersList() { return this.$store.state.orders.ordersList; } }, methods: { showDetailModal(orderId) { this.showModal = true; }, dismissModal() { this.showModal = false; }, handlePagination(page_number) { this.$store.commit('orders/setPage', page_number); this.$store.dispatch('orders/listOrders', this.getPayload()); }, handleFiltering(field, value) { this.$store.commit('orders/setFilterData', {key: field, val: value}); this.$store.commit('orders/setPage', 1); this.$store.dispatch('orders/listOrders', this.getPayload()); }, resetFilter() { this.$store.commit('orders/setPage', 1); this.$store.commit('orders/resetFilter'); this.$store.dispatch('orders/listOrders', this.getPayload()); }, getPayload() { let payload = []; payload.push("page=" + this.$store.state.orders.page); for(let field in this.$store.state.orders.filterData) { if(this.$store.state.orders.filterData[field] !== '') payload.push(field + "=" + this.$store.state.orders.filterData[field]); } if(this.pageType == "pending") { payload.push("status=pending"); } return payload.join("&"); } } } </script>
admin/components/orders-components/OrdersFilter.vue
<template> <form method="get" action="#" style="margin-bottom: 10px"> <div class="row"> <div class="col-3"> <input type="text" class="form-control" placeholder="#order Id" name="orderId" @change="handleFiltering($event)" :value="this.$store.state.orders.filterData.orderId" /> </div> <div class="col-3" v-if="this.pageType == 'allOrders'"> <select name="status" id="status" class="form-control" @change="handleFiltering($event)" :value="this.$store.state.orders.filterData.status"> <option value="">All Orders</option> <option value="pending">Pending Orders</option> <option value="success">Completed Orders</option> <option value="cancelled">Cancelled Orders</option> </select> </div> <div class="col-3"> <input type="text" class="form-control" placeholder="#user Id" name="userId" @change="handleFiltering($event)" :value="this.$store.state.orders.filterData.userId" /> </div> <a href="#" @click.prevent="resetFilter" v-if="this.$store.state.orders.filterData.userId || this.$store.state.orders.filterData.orderId || this.$store.state.orders.filterData.status">Reset</a> </div> </form> </template> <script> export default { name: "OrdersFilter", props: ["pageType"], methods: { handleFiltering(event) { this.$emit('Filtering', event.target.name, event.target.value); }, resetFilter() { this.$emit('resetFilter'); } } } </script>
admin/components/orders-components/OrdersRow.vue
<template> <tr> <td>{{ this.order.id }}</td> <td>{{ this.order.user.name }} <br/> <span style="color: #700099">#{{this.order.user.id}}</span></td> <td> <label class="badge bg-success badge-status" v-if="this.order.status=='success'">{{this.order.status}}</label> <label class="badge bg-danger badge-status" v-if="this.order.status=='cancelled'">{{this.order.status}}</label> <label class="badge bg-info badge-status" v-if="this.order.status=='pending'">{{this.order.status}}</label> </td> <td>{{ this.order.payment_method.name }}</td> <td>{{ this.order.shipping_address.address + " " + this.order.shipping_address.country }} <br/>Id #{{ this.order.shipping_address.id }}</td> <td>${{ this.order.total_price }}</td> <td>{{ this.order.paypal_order_identifier }}</td> <td> <div class="btn-group" role="group" aria-label=""> <a href="#" class="btn btn-info btn-sm action-btn" title="View Order" @click.prevent="viewOrder(order.id)">View</a> <a href="#" class="btn btn-success btn-sm action-btn" v-if="this.order.status == 'pending'" @click.prevent="updateStatus(order.id, 'success')">Completed</a> <a href="#" class="btn btn-danger btn-sm action-btn" v-if="this.order.status == 'pending'" @click.prevent="updateStatus(order.id, 'cancelled')">Cancelled</a> </div> </td> </tr> </template> <script> export default { name: "OrdersRow", props: ['order'], methods: { viewOrder(orderId) { this.$store.dispatch('orders/showOrder', orderId); this.$emit("viewOrder", orderId); }, updateStatus(orderId, status) { this.$store.dispatch('orders/updateOrder', {status, id: orderId}); } } } </script> <style scoped> .badge-status { color: #fff; } .action-btn { font-size: 9px; } </style>
admin/components/orders-components/OrderDetailsModal.vue
<template> <div class="modal fade show order-details-modal" tabindex="-1" role="dialog" style="padding-right: 12px; display: block;top: 50px;"> <div class="modal-dialog modal-lg" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title">View Order #{{orderDetails.id}} <label v-if="orderDetails.status=='success'" class="badge badge-success">{{orderDetails.status}}</label> <label v-if="orderDetails.status=='cancelled'" class="badge badge-danger">{{orderDetails.status}}</label> <label v-if="orderDetails.status=='pending'" class="badge badge-info">{{orderDetails.status}}</label> </h5> <div style="margin-left: 50%">Total Price: <strong style="color: #000">${{ orderDetails.total_price }}</strong></div> <button type="button" class="close" aria-label="Close" @click="dismiss()"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <div class="row"> <div class="col-4"> <div class="list-group" id="list-tab" role="tablist"> <a class="list-group-item list-group-item-action active" id="list-home-list" data-toggle="list" href="#list-home" role="tab" aria-controls="home">Details</a> <a class="list-group-item list-group-item-action" id="list-products-list" data-toggle="list" href="#list-products" role="tab" aria-controls="home">Order Products <span class="badge badge-danger badge-pill">{{ orderDetails.order_details ? orderDetails.order_details.length : "" }}</span></a> </div> </div> <div class="col-8"> <div class="tab-content" id="nav-tabContent"> <div class="tab-pane fade show active" id="list-home" role="tabpanel" aria-labelledby="list-home-list"> <ul> <li><strong>#orderId:</strong> <label class="badge badge-info">{{ orderDetails.id }}</label></li> <li><strong>User:</strong> {{ orderDetails.user ? orderDetails.user.name + " - # " + orderDetails.user.id : "" }} </li> <li><strong>Status message:</strong> {{ orderDetails.status_message }}</li> <li><strong>Payment method:</strong> {{ orderDetails.payment_method ? orderDetails.payment_method.name : "" }}</li> <li><strong>Shipping address:</strong> {{ orderDetails.shipping_address ? orderDetails.shipping_address.address + " " + orderDetails.shipping_address.country + " #" + orderDetails.shipping_address.id : "" }} </li> <li><strong>Paypal Identifier:</strong> {{ orderDetails.paypal_order_identifier }}</li> <li><strong>Paypal Email:</strong> {{ orderDetails.paypal_email }}</li> <li><strong>Paypal Given Name:</strong> {{ orderDetails.paypal_given_name }}</li> <li><strong>Paypal Payer Id:</strong> {{ orderDetails.paypal_payer_id }}</li> <li><strong>Created Date:</strong> {{ orderDetails.created_at }}</li> <li><strong>Updated Date:</strong> {{ orderDetails.updated_at }}</li> </ul> </div> <div class="tab-pane fade" id="list-products" role="tabpanel" aria-labelledby="list-products-list" style="max-height: 350px !important;overflow-y: scroll"> <ul v-if="orderDetails.order_details"> <li v-for="orderDetails in orderDetails.order_details" style="border-bottom: 1px solid #ccc; padding-bottom: 6px; margin-bottom: 20px"> <img :src="orderDetails.product.gallery[0].image_url.product_gallery_slider"> <strong style="font-size: 13px">{{orderDetails.product.title}}</strong> <p>Unit Price: ${{ orderDetails.price }}</p> <p>Quantity: {{ orderDetails.amount }}</p> </li> </ul> </div> </div> </div> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" @click="dismiss()">Close</button> </div> </div> </div> </div> </template> <script> export default { name: "OrderDetailsModal", methods: { dismiss() { this.$emit("closeDetailModal"); } }, computed: { orderDetails() { return this.$store.state.orders.orderDetails; } } } </script>
The index.vue and pending.vue utilize a shared component <OrdersGrid /> instead of repeating the the same grid in each page and to differentiate each page i set a prop “page-type” to load either all orders or the pending orders. Then in the fetch() hook of each page i dispatched “orders/listOrders“, except that in case of pending.vue i sent a parameter “status=pending” to fetch only the pending orders.
The <OrdersGrid /> component contains the orders table and some other partial components like <OrdersFilter />, <OrdersRow />, <Pagination /> and <OrderDetailsModal />. In the <OrdersRow /> component there two buttons beside each row to view and update status respectively. When clicking the view button we emit an event to view order details using the <OrderDetailsModal /> partial component.
When clicking the update status button i dispatch “orders/updateOrder” action which send an ajax request to update the order status either as successful or cancelled. Note that pending orders only can be updated.
Now update the NavMenu.vue to include the orders and pending orders links.
admin/components/partials/NavMenu.vue
<template> <ul :class="ulclass"> <li :class="{active: this.$route.path === '/'}"> <nuxt-link to="/"> <i class="fas fa-tachometer-alt"></i>Dashboard </nuxt-link> </li> <li :class="{active: this.$route.path.indexOf('category') !== -1}"> <nuxt-link to="/category"> <i class="fas fa-list"></i>Categories </nuxt-link> </li> <li :class="{active: this.$route.path.indexOf('brand') !== -1}"> <nuxt-link to="/brand"> <i class="fas fa-television"></i>Brands </nuxt-link> </li> <li :class="{active: this.$route.path.indexOf('product') !== -1}"> <nuxt-link to="/product"> <i class="fas fa-shopping-cart"></i>Products </nuxt-link> </li> <li :class="{active: this.$route.path.indexOf('orders') !== -1 && this.$route.path.indexOf('pending') === -1}"> <nuxt-link to="/orders"> <i class="fas fa-gears"></i>Orders </nuxt-link> </li> <li :class="{active: this.$route.path.indexOf('orders/pending') !== -1}"> <nuxt-link to="/orders/pending"> <i class="fas fa-gears"></i>Pending Orders </nuxt-link> </li> <li :class="{active: this.$route.path.indexOf('user') !== -1}"> <nuxt-link to="/user"> <i class="fas fa-users"></i>Users </nuxt-link> </li> <li> <a href="#"> <i class="fas fa-sign-out-alt"></i>Logout</a> </li> </ul> </template> <script> export default { name: "nav-menu", props: ['ulclass'], mounted() { } } </script>
Continue To Part 21: Realtime Notifications