In this post we will add the users module to manage the website users, so i will start by adding the backend CRUD in lumen then i will connect this to display them in the Nuxtjs admin panel.
Users CRUD
Let’s open the lumen project and update User model, update the $fillable property and add the $appends property like this:
app/Models/User.php
protected $fillable = [ 'name', 'email', 'is_super_admin', 'password' ]; protected $appends = array('create_date'); public function getCreateDateAttribute() { return $this->created_at ? date('Y-m-d', strtotime($this->created_at)) : ""; }
The $appends property used to add custom properties to the response of the Eloquent models, so i added getCreateDateAttribute() method to return the create date of the user then i added it’s name to the $appends property in this case when you retrieve users you will see the create_date in the response.
Users Controller
Create UsersController.php in app/Http/Controllers directory, with the following code:
app/Http/Controllers/UsersController.php
<?php namespace App\Http\Controllers; use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; class UsersController extends Controller { public function __construct() { $this->middleware('super_admin_check:store-update-destroy'); } public function index(Request $request) { $users = $this->filterAndResponse($request); return response()->json(['users' => $users], 200); } public function store(Request $request) { $validator = $this->getValidator($request); if ($validator->fails()) { return response()->json(['success' => 0, 'message' => 'Please fix these errors', 'errors' => $validator->errors()], 500); } $bundle = $request->except('password_confirmation'); $bundle['password'] = app('hash')->make($request->input('password')); $user = User::create($bundle); return response()->json(['success' => 1, 'message' => 'Created successfully', 'user' => $user], 201); } public function show($id) { return response()->json(['user' => User::findOrFail($id)], 200); } public function update(Request $request, $id) { $user = User::findOrFail($id); $validator = $this->getValidator($request, $id); if ($validator->fails()) { return response()->json(['success' => 0, 'message' => 'Please fix these errors', 'errors' => $validator->errors()], 500); } $user->name = $request->input('name'); $user->email = $request->input('email'); $user->is_super_admin = $request->input('is_super_admin'); if($request->input('password') != "") { $user->password = app('hash')->make($request->input('password')); } $user->save(); return response()->json(['success' => 1, 'message' => 'Updated successfully', 'user' => $user], 200); } public function destroy($id) { User::findOrFail($id)->delete(); return response()->json(['success' => 1, 'message' => 'Deleted successfully'], 200); } /** * @param Request $request */ protected function filterAndResponse(Request $request) { $query = User::with("products", "orders")->orderBy('id', 'DESC'); if($request->has('all')) { return $query->get(); } if ($request->has('id')) { $query->where('id', $request->id); } if ($request->has('name')) { $query->where('name', 'like', "%" . $request->name . "%"); } if ($request->has('email')) { $query->where('email', 'like', "%" . $request->email . "%"); } $users = $query->paginate(10); return $users; } private function getValidator($request, $id = null) { $validate_rules = [ 'name' => 'required|min:3', 'email' => 'required|email|' . ($id == null ? 'unique:users,email' : 'unique:users,email,' . $id), ]; if($id == null || $request->input('password') != "") { $validate_rules += ['password' => 'required|min:4|confirmed']; } $validator = Validator::make($request->all(), $validate_rules); return $validator; } }
The above controller is a simple CRUD controller so i will not go into the details of this controller as you already saw similar controllers in previous posts.
Now update routes/web.php
<?php $router->get('/', function () use ($router) { return $router->app->version(); }); $router->group(['prefix' => 'api'], function () use ($router) { $router->post('/login', 'Auth\\LoginController@login'); $router->post('/register', 'Auth\\RegisterController@register'); $router->group(['prefix' => 'category'], function () use ($router) { $router->get('/', 'CategoriesController@index'); $router->get('/htmltree', 'CategoriesController@getCategoryHtmlTree'); $router->get('/{id}', 'CategoriesController@show'); }); $router->group(['prefix' => 'brand'], function () use ($router) { $router->get('/', 'BrandsController@index'); $router->get('/{id}', 'BrandsController@show'); }); $router->group(['prefix' => 'product'], function () use ($router) { $router->get('/', 'ProductsController@index'); $router->get('/{id}', 'ProductsController@show'); }); $router->group(['prefix' => 'user'], function () use ($router) { $router->get('/', 'UsersController@index'); $router->get('/{id}', 'UsersController@show'); }); $router->group(['middleware' => 'auth:api'], function () use ($router) { $router->get('/me', 'Auth\\LoginController@userDetails'); $router->get('/logout', 'Auth\\LoginController@logout'); $router->get('/check-login', 'Auth\\LoginController@checkLogin'); $router->group(['prefix' => 'category'], function () use ($router) { $router->post('/', 'CategoriesController@store'); $router->put('/{id}', 'CategoriesController@update'); $router->delete('/{id}', 'CategoriesController@destroy'); }); $router->group(['prefix' => 'brand'], function () use ($router) { $router->post('/', 'BrandsController@store'); $router->put('/{id}', 'BrandsController@update'); $router->delete('/{id}', 'BrandsController@destroy'); }); $router->group(['prefix' => 'product'], function () use ($router) { $router->post('/', 'ProductsController@store'); $router->put('/{id}', 'ProductsController@update'); $router->delete('/delete-image/{id}', 'ProductsController@destroyImage'); $router->delete('/{id}', 'ProductsController@destroy'); }); $router->group(['prefix' => 'user'], function () use ($router) { $router->post('/', 'UsersController@store'); $router->put('/{id}', 'UsersController@update'); $router->delete('/{id}', 'UsersController@destroy'); }); }); });
Manipulating Users In Nuxtjs
The second thing we need to do in the Nuxt project in the /admin/ directory is to create the pages responsible for manipulating users.
Users Api
As usual we will start with implementing the apis or services for users so go to /admin/api/ directory and create user.js with following code:
/admin/api/user.js
const UserApi = { create: (axios, payload) => { return axios.$post('/api/user', payload); }, list: (axios, payload = null) => { let payload_arr = []; if(payload) { for(let key in payload) { payload_arr.push(key +"=" + payload[key]); } } return axios.$get('/api/user?' + payload_arr.join("&")); }, getAll: (axios) => { return axios.$get('/api/user?all=1'); }, delete: (axios, id) => { return axios.$delete('/api/user/' + id); }, show: (axios, id) => { return axios.$get('/api/user/' + id); }, update(axios, payload, id) { return axios.$put('/api/user/' + id, payload); } }; export {UserApi};
Next let’s create the store for users module.
Users Vuex Store
As we seen in previous tutorials how the store work in Nuxtjs i prepared the user store, in the same way as we done in previous lesson. So in /admin/store directory create user.js with the below code:
/admin/store/user.js
import { UserApi } from '../api/user'; export const state = () => ({ user: { name: '', email: '', password: '', password_confirmation: '', is_super_admin: 0 }, filterData: { id: '', name: '', email: '' }, page: 1, userList: {}, allUsers: [] }); export const mutations = { setUser(state, payload) { state.user[payload.key] = payload.value; }, resetUser(state) { state.user = { name: '', email: '', password: '', password_confirmation: '', is_super_admin: 0 }; }, setFilterData(state, payload) { state.filterData[payload.key] = payload.val; }, setPage(state, page) { state.page = page; }, setUserList(state, userList) { state.userList = userList; }, setAllUsers(state, value) { state.allUsers = value; } }; export const actions = { create({commit, dispatch, state}, payload) { commit('shared/resetStatusMessagesParameters', null, {root: true}); commit('shared/setStatusMessageParameter', {key: 'showLoading', val: true}, {root: true}); const dataToSend = {...state.user}; UserApi.create(this.$axios, dataToSend).then(response => { commit('shared/setStatusMessageParameter', {key: 'showLoading', val: false}, {root: true}); if(response.success) { commit('shared/setStatusMessageParameter', {key: 'success_message', val: response.message}, {root: true}); } setTimeout(() => { payload.router.push('/user'); }, 2000); }).catch(err => { dispatch('showValidationErrors', err); }).finally(() => { setTimeout(() => { commit('shared/resetStatusMessagesParameters', null, {root: true}); }, 10000); }); }, list({commit}, payload = null) { UserApi.list(this.$axios, payload).then(response => { commit('setUserList', response.users); }); }, getAllUsers({commit}) { UserApi.getAll(this.$axios).then(response => { commit('setAllUsers', response.users); }).catch(err => { console.log(err); }); }, show({commit}, id) { commit('shared/resetStatusMessagesParameters', null, {root: true}); commit('shared/setStatusMessageParameter', {key: 'showLoading', val: true}, {root: true}); commit('resetUser'); UserApi.show(this.$axios, id).then(response => { commit('shared/setStatusMessageParameter', {key: 'showLoading', val: false}, {root: true}); if(response.user) { commit('setUser', {key: 'name', value: response.user.name}); commit('setUser', {key: 'email', value: response.user.email}); commit('setUser', {key: 'is_super_admin', value: response.user.is_super_admin}); } }).catch(err => { console.log(err); }); }, update({commit, state, dispatch}, payload) { commit('shared/resetStatusMessagesParameters', null, {root: true}); commit('shared/setStatusMessageParameter', {key: 'showLoading', val: true}, {root: true}); const dataToSend = {...state.user}; UserApi.update(this.$axios, dataToSend, payload.id).then(response => { commit('shared/setStatusMessageParameter', {key: 'showLoading', val: false}, {root: true}); if(response.success) { commit('shared/setStatusMessageParameter', {key: 'success_message', val: response.message}, {root: true}); } setTimeout(() => { payload.router.push('/user'); }, 2000); }).catch(err => { dispatch('showValidationErrors', err); }).finally(() => { setTimeout(() => { commit('shared/resetStatusMessagesParameters', null, {root: true}); }, 10000); }); }, delete({commit, state, dispatch}, id) { commit('shared/resetStatusMessagesParameters', null, {root: true}); commit('shared/setStatusMessageParameter', {key: 'showLoading', val: true}, {root: true}); UserApi.delete(this.$axios, id).then(response => { commit('shared/setStatusMessageParameter', {key: 'showLoading', val: false}, {root: true}); if(response.success) { let userList = {...state.userList}; userList.data = userList.data.filter(item => item.id !== id); commit('setUserList', userList); commit('shared/setStatusMessageParameter', {key: 'success_message', val: response.message}, {root: true}); } }).catch(err => { dispatch('showValidationErrors', err); }).finally(() => { setTimeout(() => { commit('shared/resetStatusMessagesParameters', null, {root: true}); }, 10000); }); }, showValidationErrors({commit}, err) { commit('shared/setStatusMessageParameter', {key: 'showLoading', val: false}, {root: true}); if(err.response.data) { commit('shared/setStatusMessageParameter', {key: 'error_message', val: err.response.data.message}, {root: true}); if(err.response.data.errors) { let errors = []; for(let key in err.response.data.errors) { errors.push(err.response.data.errors[key][0]); } commit('shared/setStatusMessageParameter', {key: 'validation_errors', val: errors}, {root: true}); } } } }
Users Pages
At this point we are ready to create the pages needed for users display and management so go to /admin/pages and create user/ directory and inside it add these pages:
- pages/user/index.vue
- pages/user/create.vue
- pages/user/_edit.vue
Also in the /admin/components directory create user-components/ directory and add these partial components inside it:
- components/user-components/UserFilter.vue
- components/user-components/UserForm.vue
- components/user-components/UserRow.vue
Displaying All Users
Let’s display all users in index.vue page
index.vue
<template> <div class="main-content"> <div class="section__content section__content--p30"> <div class="container-fluid"> <div class="row"> <div class="col-md-12"> <loader></loader> <status-messages></status-messages> <UserFilter v-on:Filtering="handleFiltering"></UserFilter> </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>Name</th> <th>Email</th> <th>Admin</th> <th>Create Date</th> <th>Count Products</th> <th>Count Order</th> <th>Options</th> </tr> </thead> <tbody> <UserRow v-if="userList.data" v-for="user of userList.data" v-bind:user="user" v-bind:key="user.id" v-on:removeUser="removeUser"></UserRow> </tbody> </table> </div> <Pagination :data="userList" v-if="userList.data" v-on:handlePagination="handlePagination"></Pagination> </div> </div> </div> </div> </div> </template> <script> import Loader from "../../components/helpers/loader"; import statusMessages from "../../components/helpers/statusMessages"; import UserRow from "../../components/user-components/UserRow"; import Pagination from "../../components/helpers/Pagination"; import UserFilter from "../../components/user-components/UserFilter"; export default { name: "index", middleware: "auth", components: { UserFilter, Pagination, UserRow, Loader, statusMessages }, fetch() { this.$store.dispatch('user/list'); }, computed: { userList() { return this.$store.state.user.userList; } }, methods: { handlePagination(page_number) { this.$store.commit('user/setPage', page_number); this.$store.dispatch('user/list', this.getPayload()); }, handleFiltering(field, value) { this.$store.commit('user/setFilterData', {key: field, val: value}); this.$store.commit('user/setPage', 1); this.$store.dispatch('user/list', this.getPayload()); }, getPayload() { let payload = {}; for(let field in this.$store.state.user.filterData) { if(this.$store.state.user.filterData.hasOwnProperty(field) && this.$store.state.user.filterData[field] !== '') payload[field] = this.$store.state.user.filterData[field]; } payload.page = this.$store.state.user.page; return payload; }, removeUser(id) { if(confirm("Are you sure?")) { this.$store.dispatch('user/delete', id); } } } } </script> <style scoped> </style>
/admin/components/user-components/UserFilter.vue
<template> <form method="get" action="#" style="margin-bottom: 10px"> <h4>Filter</h4> <div class="row"> <div class="col-1"> <input type="text" class="form-control" placeholder="Id" name="id" @change="handleFiltering($event)" :value="this.$store.state.user.filterData.id"> </div> <div class="col-3"> <input type="text" class="form-control" placeholder="Name" name="name" @change="handleFiltering($event)" :value="this.$store.state.user.filterData.name"> </div> <div class="col-3"> <input type="text" class="form-control" placeholder="Email" name="email" @change="handleFiltering($event)" :value="this.$store.state.user.filterData.email"> </div> <div class="col-5"> <nuxt-link to="/user/create" class="btn btn-success pull-right"><i class="fa fa-plus-square"></i> Create</nuxt-link> </div> </div> </form> </template> <script> export default { name: "UserFilter", methods: { handleFiltering(event) { this.$emit('Filtering', event.target.name, event.target.value); } } } </script> <style scoped> </style>
/admin/components/user-components/UserRow.vue
<template> <tr> <td>{{ this.user.id }}</td> <td>{{ this.user.name }}</td> <td>{{ this.user.email }}</td> <td> <span class="badge badge-info" v-if="this.user.is_super_admin == 1">Yes</span> <span class="badge badge-warning" v-if="this.user.is_super_admin == 0">No</span> </td> <td>{{ this.user.create_date }}</td> <td>{{ this.user.products.length > 0 ? this.user.products.length : 'none' }}</td> <td>{{ this.user.orders.length > 0 ? this.user.orders.length : 'none' }}</td> <td> <nuxt-link :to="'/user/' + this.user.id" class="btn btn-info btn-sm"><i class="fa fa-edit"></i></nuxt-link> <a href="#" class="btn btn-danger btn-sm" @click.prevent="removeUser(user.id)"><i class="fa fa-remove"></i></a> </td> </tr> </template> <script> export default { name: "UserRow", props: ['user'], methods: { removeUser(userId) { this.$emit('removeUser', userId); } } } </script> <style scoped> </style>
Â
Creating Users
Open pages/user/create.vue and add the code below
pages/user/create.vue
<template> <div class="main-content"> <div class="section__content section__content--p30"> <div class="container-fluid"> <loader></loader> <status-messages></status-messages> <form method="post" action="#" @submit.prevent="save()"> <UserForm></UserForm> <div class="row"> <button type="submit" class="btn btn-lg btn-info btn-block"> <i class="fa fa-save fa-lg"></i> Save </button> </div> </form> </div> </div> </div> </template> <script> import Loader from '../../components/helpers/loader'; import statusMessages from '../../components/helpers/statusMessages'; import UserForm from '../../components/user-components/UserForm'; export default { name: "CreateUser", middleware: "auth", components: { Loader, statusMessages, UserForm }, methods: { save() { this.$store.dispatch('user/create', {router: this.$router}); } }, mounted() { this.$store.commit('user/resetUser'); } } </script> <style scoped> </style>
As you see in this code we extracted the form into a separate component <UserForm /> like we did in the previous part with products to make things simple.
components/user-components/UserForm.vue
<template> <div class="row"> <div class="col-lg-12"> <div class="card"> <div class="card-header"> <strong>{{ this.$route.params.edit?'Edit User #' + this.$route.params.edit : 'Create New User' }}</strong> </div> <div class="card-body card-block"> <div class="form-group"> <label for="name" class=" form-control-label">Name <span class="required-in">*</span></label> <input type="text" id="name" name="name" placeholder="Name" class="form-control" v-model="name" /> </div> <div class="form-group"> <label for="email" class=" form-control-label">Email <span class="required-in">*</span></label> <input type="text" id="email" name="email" placeholder="Email" class="form-control" v-model="email" /> </div> <button type="button" class="btn btn-info" v-if="this.$route.params.edit" @click="togglePasswordBlock()">Change Password</button> <div v-if="!this.$route.params.edit || showEditPassword"> <div class="form-group"> <label for="password" class=" form-control-label">Password <span class="required-in">*</span></label> <input type="password" id="password" name="password" placeholder="Password" class="form-control" v-model="password" /> </div> <div class="form-group"> <label for="password_confirmation" class=" form-control-label">Password Confirmation <span class="required-in">*</span></label> <input type="password" id="password_confirmation" name="password_confirmation" placeholder="Password Confirmation" class="form-control" v-model="password_confirmation" /> </div> </div><br/> <div class="form-group"> <label for="is_super_admin" class=" form-control-label">Is Super Admin</label> <label class="switch switch-default switch-primary mr-2" size="lg"> <input type="checkbox" class="switch-input" name="is_super_admin" id="is_super_admin" value="1" v-model="is_super_admin"> <span class="switch-label"></span> <span class="switch-handle"></span> </label> </div> </div> </div> </div> </div> </template> <script> export default { name: "UserForm", data() { return { showEditPassword: false } }, computed: { name: { get() { return this.$store.state.user.user.name; }, set(value) { this.$store.commit('user/setUser', {key: 'name', value}); } }, email: { get() { return this.$store.state.user.user.email; }, set(value) { this.$store.commit('user/setUser', {key: 'email', value}); } }, password: { get() { return this.$store.state.user.user.password; }, set(value) { this.$store.commit('user/setUser', {key: 'password', value}); } }, password_confirmation: { get() { return this.$store.state.user.user.password_confirmation; }, set(value) { this.$store.commit('user/setUser', {key: 'password_confirmation', value}); } }, is_super_admin: { get() { return this.$store.state.user.user.is_super_admin; }, set(value) { value = value == true ? 1 : 0; this.$store.commit('user/setUser', {key: 'is_super_admin', value}); } } }, methods: { togglePasswordBlock() { this.showEditPassword = !this.showEditPassword; } } } </script> <style scoped> </style>
Updating Users
The last page to update is the _edit.vue page which enable us to update users.
pages/user/_edit.vue
<template> <div class="main-content"> <div class="section__content section__content--p30"> <div class="container-fluid"> <loader></loader> <status-messages></status-messages> <form method="post" action="#" @submit.prevent="save()"> <UserForm></UserForm> <div class="row"> <button type="submit" class="btn btn-lg btn-info btn-block"> <i class="fa fa-save fa-lg"></i> Save </button> </div> </form> </div> </div> </div> </template> <script> import Loader from '../../components/helpers/loader'; import statusMessages from '../../components/helpers/statusMessages'; import UserForm from '../../components/user-components/UserForm'; export default { name: "EditUser", middleware: "auth", components: { Loader, statusMessages, UserForm }, fetch() { if(this.$route.params.edit) { this.$store.dispatch('user/show', this.$route.params.edit); } }, methods: { save() { this.$store.dispatch('user/update', {router: this.$router, id: this.$route.params.edit}); } }, mounted() { } } </script> <style scoped> </style>
Updating Navbar
Let’s update the navbar to display the link for user module so go to components/partials/NavMenu.vue and update as shown below:
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> <a href="#"> <i class="fas fa-gears"></i>Orders</a> </li> <li> <a href="#"> <i class="fas fa-gears"></i>Pending Orders</a> </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> <style scoped> </style>
Now give this a try by runing “npm run dev” and go to http://localhost:4000/user.
In the next part we will move on by starting to prepare the html template we will be using on the website.
Continue to Part10: Preparing Website Html Template