Integrasi Dengan CSS dan JavaScript External (Template Dashboard CoreUI) ........... 413
Menampilkan Cart Setelah Login dan Menghapus Cart Setelah Logout ..................... 905
Bismillahirrahmannirrahiim.
Alhamdulillah, segala puji dan syukur penulis panjatkan kehadirat Tuhan Yang Maha Esa.
Karena berkat limpahan karunia-Nya, kami dapat menyelesaikan penulisan buku
Membangun Website Toko Online Dengan Laravel, Nuxt.js dan Payment Gateway.
Di Dalam penyusunan buku tersebut, penulis telah berusaha semaksimal mungkin sesuai
dengan kemampuan penulis demi penyelesaian buku ini dengan baik.
Penulis menyadari jika masih terdapat kekurangan ataupun suatu kesalahan dalam
penyusunan buku ini sehingga penulis mengharapkan kritik ataupun saran yang bersifat
positif untuk perbaikan di masa yang akan datang dari seluruh pembaca.
Maka dengan kerendahan hati penulis hanya bisa menyampaikan ucapan terima kasih
kepada semua pihak yang terlibat dalam proses penyelesaian ini.
Sekian semoga buku ini dapat bermanfaat dan mudah dipahami bagi penulis khususnya
serta para pembaca pada umumnya.
Raihlah ilmu, dan untuk meraih ilmu belajarlah tenang dan sabar. - Umar bin Khattab
8
License Buku
Buku ini menggunakan license personal, yang artinya buku dan isi di dalamnya hanya boleh
digunakan dan di baca untuk seseorang yang sudah membelinya. Selain pemilik license dari
buku ini tidak diperbolehkan menggunakan apalagi sampai menyebar luaskan tanpa izin
dari penulis.
Dan untuk pemilik license dari buku ini juga tidak di perbolehkan menyebarkan dan
memperjual belikan lagi kepada seseorang.
CATATAN : jika terjadi pembajakan, maka akan di tindak lanjuti secara hukum.
9
Tentang Buku
Buku ini akan mengajarkan kita semua bagaimana cara menjadi seorang Full-Stack Web
Developer dengan cara membangun sebuah website toko online menggunakan Laravel
sebagai backend dan Nuxt.js sebagai frontend. Dimana Laravel akan berperan membuat
layanan web service berupa Rest API. Sedangkan Nuxt.js akan berperan untuk mengelola
dan menggunakan Rest API tersebut.
Kita juga akan belajar mengimplementasikan layanan dari RajaOngkir untuk mendapatkan
biaya ongkos kirim secara realtime dan akurat. Dan juga kita akan belajar tentang integrasi
dengan layanan Payment Gateway (Midtrans), fungsinya untuk melakukan pembayaran
secara otomatis di dalam website yang kita buat.
Di bab pertama, kita akan belajar banyak hal di dalam Laravel, seperti membuat project
baru menggunakan Composer. Membuat Relationships untuk menghubungkan beberapa
table menjadi satu kesatuaan. Memanipulasi data menggunakan Accessor, Mutator dan
Casting. Implementasi JWT (Json Web Token) untuk proses otentikasi di Rest API. Integrasi
RajaOngkir dan Payment Gateway.
Di bab kedua, Kita akaan belajar membuat project Nuxt.js baru. Membuat proses otentikasi.
Penerapan middleware. Kustomisasi middleware untuk multiple level user. Menampilkan
statistik dan grafik penjualan. Menampilkan data dengan teknik SSR (server side rendering),
sehingga website tersebut baik disisi SEO (search engine optimization).
Di dalam Nuxt.js kita juga akan belajar bagaimana melakukan integrasi dengan external
template dashboard. Dan kita akan integrasikan juga menggunakan Bootstrap Vue agar
kita dapat menggunakan beberapa component Bootstrap untuk mempermudah dalam
pengembangan project sekala besar.
Di bab terakhir, setelah semua project berhasil diselesaikan maka akan dilanjutkan belajar
bagaimana cara melakukan proses deployment. Untuk Laravel akan di deploy ke dalam
cPanel (shared hosting) dan untuk Nuxt.js akan di deploy ke dalam Vercel dengan teknik
SSR dan menerapkan konsep CI/CD atau biasa disebut dengan Continue Integration dan
Contrinue Development.
Ilmu itu seperti air. Jika ia tidak bergerak, maka ia akan menjadi keruh lalu membusuk -
Imam Syafi'i
10
Diagram Alur Aplikasi
Sebelum membangun sebuah aplikasi tentu saja kita butuh sebuah diagram untuk
mengetahui alur program yang akan di kembangkan. Dengan menggunakan sebuah
diagram kita bisa lebih tau dan paham bagaimana sebuah data berjalan di dalam aplikasi.
Dengan menjabarkan beberapa fitur-fitur dan menggunakan sebuah diagram, maka seorang
software developer akan sangat terbantu saat proses pengembangan aplikasi dan apalagi
aplikasi yang dikembangkan memiliki sekala yang sangat besar.
Di dalam studi kasus membangun website toko online kali ini kita akan memisahkan menjadi
3 diagram , yaitu admin, customer dan web.
Diagram Admin
11
Dari diagram Admin di atas kita memliki sebuah endpoint dengan menggunakan konsep
12
otentikasi yang artinya kita perlu melakukan sebuah login terlebih dahulu dan disini untuk
otentikasi kita menggunakan JWT atau Json Web Token.
Setelah kita berhasil melakukan proses otentikasi, maka sekarang kita dapat mengakses
beberapa endpoint lainnya, seperti /categories, /products, /invoices dan lain-lain.
Dan disini kita bisa melakukan proses CRUD atau Create, Read, update dan Delete sebuah
data ke dalam database.
Jadi kesimpulannya, endpoint yang ada di atas merupakan jenis endpoint private yang mana
hanya bisa diakses jika kita sudah melakukan proses otentikasi terlebih dahulu.
Diagram Customer
Untuk diagram Customer di atas, sama seperti yang ada di dalam Admin, yaitu kita
menggunakan jenis endpoint yang bersifat private, dimana endpoint yang ada di dalamnya
hanya dapat diakses apabila kita sudah melakukan proses otentikasi terlebih dahulu.
Di dalam diagram Customer di atas, kita akan menampilkan beberapa data, yang pertama
adalah /dashboard yang nantinya akan kita gunakan untuk menampilkan Rest API berupa
13
statistik data. Dan untuk /invoices akan digunakan untuk menampilkan list data invoice
yang pernah dibuat oleh customer tersebut.
Dan di dalam endpoint detail invoice, kita juga akan menampilkan list data order atau
produk yang telah dibeli. Kemudian kita juga dapat memberikan review atau ulasan dari
produk tersebut di dalam endpoint /reviews.
Diagram Web
Untuk diagaram Web akan berbeda dari sebelum-sebelumnya, karena semua endpoint yang
ada disini akan bersifat public kecuali endpoint /cart. Dimana semua endpoint kecuali
/cart dapat di akses secara public tanpa perlu melakukan proses otentikasi terlebih
dahulu.
14
Karena endpoint tersebut akan digunakan untuk menampilkan data di halaman depan
website. Dan untuk /cart akan kita atur menjadi private dengan tujuan hanya bisa di akses
jika customer telah melakukan proses otentikasi terlebih dahulu, karena di dalam data cart
nanti akan ada ID dari customer yang melakukan proses Add To Cart.
15
Struktur dan Relasi Database
Pembuatan sebuah struktur database beserta relasi merupakan hal wajib yang perlu
dilakukan oleh seorang software developer atau programmer sebelum melakukan proses
development atau koding.
Permasalahan yang sering terjadi adalah para software developer atau programmer yang
baru belajar melakukan koding langsung. Dan membuat database berserta relasinya dengan
berjalannya waktu, dan tentu saja hal ini tidak dibenarkan, karena bisa membuat aplikasi
yang dikerjakan sangat lama dan bisa berubah-ubah sewaktu-waktu.
Dan disini untuk website toko online yang akan kita bangun nanti sudah kita persiapkan
struktur berserta relasi databasenya dan tentu saja karena kita menggunakan Laravel, maka
kita tidak perlu membuat struktur dan relasi database tersebut secara manual, karena kita
akan mengunakan fitur Migration untuk melakukan generate table-table dan Eloquent
Relationships untuk membuat relasi antar table di dalam Laravel.
Berikut ini struktur dan relasi yang akan kita buat nanti di dalam pengembangan website
toko online menggunakan Laravel, Nuxt.js dan Payment Gateway (Midtrans).
CATATAN!: jangan buat terlebih dahulu untuk table-table dari gambar di atas, karena kita
akan membuatnya nanti melalui migration di Laravel.
16
Wirefrime UI Desain Website
Wirefrime adalah salah satu tahap yang sangat penting ketika kita mengembangkan sebuah
aplikasi maupun website, dengan adanya wirefrime kita bisa membuat rancangan desain
dan user exprience atau biasanya disebut UI/UX sebelum masuk ke tahap development
atau pengembangan.
Wirefrime merupakan sebuah kerangkan yang berfungsi untuk membuat tata letak suatu
desain aplikasi atau website agar sesuai dengan apa yang akan di kerjakan, umumnya tugas
ini di kerjakan oleh seorang UI/UX desainer. Umunya wirefrime berupa beberapa komponen
seperti Header, Sidebar, Content dan Footer.
Disini kita akan melihat wirefrime dari aplikasi atau website Toko Online yang akan kita
buat nantinya, ini hanya sebuah gambaran dari website yang akan kita buat nanti. berikut
ini beberapa wirefrime yang akan kita implementasikan ke dalam website nanti.
Homepage
Pada halaman homepage ini kita akan menampilkan beberapa data, seperti sliders dan
products. Kurang lebih seperti berikut ini :
17
Detail Product
Dan untuk halaman detail product disini kita akan menampilkan informasi mengenai
produk yang akan di jual, seperti gambar, judul, deskripsi dan keterangan stok maupun
berat.
18
Dan kita juga akan menampilkan ulasan dan rating yang diberikan oleh customer setelah
membeli produk tersebut. Kurang lebih seperti berikut ini :
Cart / Checkout
Halaman cart / checkout digunakan untuk menampilkan beberapa produk yang akan di
beli, dimana disini kita akan menampilkan beberapa informasi, seperti nama produk,
quantity dan harga dari produk tersebut (termasuk harga diskon).
Setelah itu, kita juga akan menampilkan form untuk mengisi beberapa data yang
19
dibutuhkan untuk proses pengiriman, seperti provinsi, kota/kabupaten.
Kita juga akan melakukan proses perhitungan biaya ongkos kirim menggunakan Raja
Ongkir, dan untuk kurir yang akan di pakai seperti JNE, TIKI dan POS. Kurang lebih seperti
berikut ini :
20
Persiapan dan Persyaratan Tools
Setelah kita mengetahui diagram alur aplikasi, struktur database dan gambaran dari
website-nya, maka sekarang kita akan lanjutkan untuk mempersiapkan beberapa tools yang
akan kita gunakan untuk mengembangkan website toko online tersebut. Beberapa tools
tersebut seperti Text Editor, Web Server, PHP, Node.js, Database, Composer dan lain-lain.
Setelah Visual Studi Code berhasil terinsall, sekarang saya akan rekomendasikan beberapa
plugin yang akan kita gunakan dalam pengembangan aplikasi menggunakan Laravel dan
Nuxt.js
https://marketplace.visualstudio.com/items?itemName=amirmarmul.laravel-blade-vsc
ode
https://marketplace.visualstudio.com/items?itemName=austenc.laravel-blade-spacer
https://marketplace.visualstudio.com/items?itemName=codingyu.laravel-goto-view
https://marketplace.visualstudio.com/items?itemName=stef-k.laravel-goto-controller
https://marketplace.visualstudio.com/items?itemName=amiralizadeh9480.laravel-extr
a-intellisense
https://marketplace.visualstudio.com/items?itemName=mgmcdermott.vscode-langua
ge-babel
https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html
https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint
https://marketplace.visualstudio.com/items?itemName=svipas.prettier-plus
https://marketplace.visualstudio.com/items?itemName=octref.vetur
https://marketplace.visualstudio.com/items?itemName=jcbuisson.vue
https://marketplace.visualstudio.com/items?itemName=hollowtree.vue-snippets
https://marketplace.visualstudio.com/items?itemName=hakcorp.php-awesome-snippe
ts
https://marketplace.visualstudio.com/items?itemName=bmewburn.vscode-intelephen
se-client
21
https://marketplace.visualstudio.com/items?itemName=MehediDracula.php-namespac
e-resolver
https://marketplace.visualstudio.com/items?itemName=rexshi.phpdoc-comment-vsco
de-plugin
https://marketplace.visualstudio.com/items?itemName=ronvanderheijden.phpdoc-gen
erator
Disini kita bisa memilih menggunakan XAMPP atau Laragon, sesuai dengan selera masing-
masing, kelebihan menggunakan Laragon dibandingkan XAMPP adalah dapat berganti-
ganti versi PHP dengan lebih mudah dan hal tersebut belum bisa dilakukan oleh XAMPP
untuk saat ini.
Dan kekurangan Laragon sendiri belum memiliki Database Manager seperti phpMyAdmin,
tapi kita kita bisa menginstallnya sendiri dengan mudah.
Disini disarakan menggunakan PHP versi 7.4 di dalam XAMPP ataupun Laragon. Berikut ini
link yang bisa teman-teman pakai untuk mengunduh aplikasi-nya.
Windows:
https://www.apachefriends.org/xampp-files/7.4.20/xampp-windows-x64-7.4.20-0-VC15
-installer.exe
Linux :
https://www.apachefriends.org/xampp-files/7.4.20/xampp-linux-x64-7.4.20-0-installer.r
un
Mac OSX :
https://www.apachefriends.org/xampp-files/7.4.20/xampp-osx-7.4.20-0-installer.dmg
https://laragon.org/download/
22
Silahkan pilih versi Node.js yang paling terbaru atau menggunakan versi LTS atau Long
Time Support. Untuk mengetahui apakah Node.js sudah terinstall di komputer kita, silahkan
jalankan perintah di bawah ini di terminal/CMD:
node -v
npm -v
Untuk memverifikasi apakah Composer sudah berhasil terinstall di dalam komputer, kita
bisa menjalankan perintah berikut ini di dalam CMD/terminal :
composer
23
Langkah 5 - Installasi Postman
Postman merupakan salah satu tool yang wajib dimiliki oleh seorang backend developer.
Postman sendiri merupakan sebuah tool yang digunakan untuk melakukan API testing.
Postman sendiri memiliki fitur yang dapat digunakan secara personal maupun bersamaan
(teamwork).
Enaknya lagi, Postman juga dapat melakukan generate sebuah dokumentasi Api dari
proses testing API yang pernah dilakukan. Karena di dalam buku ini kita akan membuat dan
menguji sebuah Rest API, maka kita perlu melakukan installasi Postman terlebih dahulu.
Untuk installasi Postman bisa disesuaikan dengan sistem operasi yang digunakan dan
untuk mendapatkan tool tersebut kita bisa masuk di link berikut ini :
https://www.postman.com/downloads/
Silahkan di install dan sesuaikan dengan sistem operasi yang digunakan, jika berhasil maka
kurang lebih tampilannya seperti berikut ini :
24
25
Membuat Project Baru Laravel Dengan Composer
Jika kita lihat di dokumentasi resmi dari Laravel ada banyak sekali cara untuk membuat
project baru, yaitu menggunakan Sails Service (Based Docker), Laravel Installer,
Composer Create Project.
Di dalam buku ini kita akan belajar membuat project Laravel baru menggunakan perintah
composer create-project. Dengan perintah ini kita dapat melakukan installasi dengan
menyertakan spesifik versi Laravel yang digunakan.
Perintah di atas akan membuat sebuah project Laravel baru dengan nama backend-
ecommerce dan versi yang akan digunakan adalah 8.x.x. Silahkan tunggu proses installasi
sampai selesai dan pastikan komputer terhubung dengan internet, karena semua paket
akan diunduh secara online.
26
Langkah 2 - Menjalankan Project Laravel
Setelah proses installasi project selesai, sekarang kita akan belajar bagaimana cara
menjalankan project Laravel tersebut, silahkan jalankan perintah berikut ini :
cd backend-ecommerce
Perintah cd digunakan untuk melakukan enter atau masuk ke dalam sebuah direktori/folder,
dalam contoh di atas kita masuk ke dalam folder backend-ecommerce, dimana folder ini
merupakan project Laravel-nya. Setelah itu jalankan perintah berikut ini :
Jika perintah di atas berhasil dijalankan, maka project kita akan di jalankan di dalam
localhost menggunakan port 8000, kita bisa membukanya di dalam web browser dengan
mengetikkan http://localhost:8000, jika berhasil, maka kurang lebih tampilannya seperti
berikut ini :
Jika sudah muncul hasil seperti gambar di atas, maka kita telah berhasil melakukan installasi
dan menjalankan project Laravel-nya.
27
Langkah 3 - Menjalankan Storage Link
Sekarang kita akan melakukan proses symlink folder storage di dalam Laravel, dimana
folder storage ini agar dapat di akses melalui folder public. Silahkan jalankan perintah
berikut ini di terminal/CMD:
Jika berhasil, maka teman-teman akan melihat file/folder dengan nama storage di dalam
folder public.
28
Membuat Helpers di Laravel
Helpers merupakan sebuah fungsi yang sering digunakan di dalam aplikasi dan sifatnya
global, jadi bisa dipanggil dimana saja di dalam aplikasi. Contohnya jika kita ingin mengubah
format mata uang menjadi Rupiah di dalam template, mengubah taanggal menjadi format
Indonesia dan lain-lain. Di dalam Laravel sendiri ada berbagai macam cara pembuatan
helpers, dan disini kita akan belajar bagaimana cara membuat helpers di Laravel dengan
benar sesuai aturan best practice dan mudah tentunya.
Setelah project berhasil dibuka menggunakan Text Editor seperti gambar di atas, sekarang
kita lanjutkan untuk proses bagaimana cara membuat helpers-nya.
Silahkan buat folder baru dengan nama Helpers di dalam folder app, setelah itu di dalam
folder Helpers silahkan buat file baru dengan nama helpers.php dan masukkan kode
berikut ini :
29
<?php
if (! function_exists('moneyFormat')) {
/**
* moneyFormat
*
* @param mixed $str
* @return void
*/
function moneyFormat($str) {
return 'Rp. ' . number_format($str, '0', '', '.');
}
}
Dari penambahan kode helpers di atas, kita menambahkan function yang bernama
moneyFormat. Fungsinya akan digunakan untuk mengubah sebuah nilai integer atau angka
menjadi format mata uang yang dipisahkan berdasarkan satuan menggunakan notasi .. Dan
menambahkan kata Rp. di depannya. Contohnya, jika kita ingin mem-format angka 10000
maka hasilnya akan menjadi Rp. 10.000.
30
<?php
$angka = 10000;
echo $hasil;
?>
Di dalam function moneyFormat kita menggunakan fungsi bawaan dari PHP yaitu
number_format, dimana di dalam fungsi tersebut memiliki 4 parameter, kurang lebih
seperti berikut ini :
angka - merupakan parameter pertama, parameter ini berisi nilai angka yang akan di
format.
angka_dibelakan_koma - parameter ini bersifat opsional dan digunakan untuk
menentukan jumlah angka desimal (angka dibelakang koma) yang dibutuhkan.
Apabila tidak diisi, akan dianggap sebagai 0.
pemisah_desimal - parameter ini akan berisi tanda untuk dijadikan pemisah angka
desimal, disini bisa kita kosongkan saja.
pemisah_ribuan - parameter ini yang akan digunakan untuk memisahkan angka
ribuan dengan menggunakan notasi ..
31
"autoload": {
......
"files": [
"app/Helpers/helpers.php"
]
},
Setelah itu, simpan file composer.json dan jalankan perintah berikut ini :
composer dump-autoload
32
Dan sekarang, helpers kita sudah bisa digunakan secara global di dalam project.
33
Upgrade Versi Laravel
34
Koneksi Database di Laravel
Karena website yang kita akan bangun bersifat dinamis, maka kita perlu menggunakan
sebuah database untuk menyimpan data tersebut, dimana data tersebut dapat kita
manipulasi sesuai dengan keinginan, seperti menambah, edit dan hapus. Di Laravel untuk
menghubungkan ke dalam database sangatlah mudah, kita cukup mendefinisikan beberapa
konfigurasi di dalam file .env, maka website kita sudah bisa terhubung dengan database.
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
DB_DATABASE=db_backend_ecommerce
DB_USERNAME=root
DB_PASSWORD=
35
Di atas, kita atur untuk DB_DATABASE menggunakan db_backend_ecommerce dan untuk
DB_PASSWORD silahkan disesuaikan dengan konfigurasi dari masing-masing MySQL yang
digunakan, jika menggunakan XAMPP, secara default adalah kosong atau kita tidak perlu
mengisinya.
36
Membuat Model dan Migration
Di dalam kebanyakan framework pasti menggunakan arsitektur/pola yang disebut MVC atau
kepanjangan dari (Model, View dan Controller). Laravel juga menerapkan arsitektur/pola
MVC di dalam core systemnya, dengan menggunakan arsitektur ini maka proses
pembuatan aplikasi akan dipisahkan berdasarkan fungsinya masing-masing. Model
merupakan bagian yang digunakan untuk menangani query data dari database. View
digunakan untuk menampilkan sesuatu di layar web browser, biasanya berupa kode-kode
seperti HTML, CSS dan JavaScript. Dan sedangkan untuk Controller digunakan untuk
menangani logika dan menghubungkan antara Model dengan View.
Migration merupakan version control untuk database, dimana kita bisa membuat schema
database dan membagikannya dengan tim yang lain secara mudah. Jika sebelumnya kita
membuat attribute secara manual di dalam database, maka dengan migration kita tidak
perlu melakukan hal seperti itu lagi, seperti membuat table, menambahkan attribute,
mengubah tipe data dan lain-lain.
Pada bab perancangan dan materi struktur database, kita sudah melihat beberapa table-
table yang akan kita gunakan nantinya dan kali ini kita akan belajar bagaimana cara
membuat Model dan juga Migration untuk melakukan generate table-table tersebut ke
dalam database.
37
Sekarang kita coba untuk membuat sebuah Model dan juga Migration untuk table
customers, silahkan jalankan perintah berikut ini di dalam terminal/CMD dan pastikan
berada di dalam project Laravel-nya :
Perintah di atas, digunakan untuk melakukan generate sebuah Model dengan nama
Customer dan karena kita menambahkan flag -m, maka kita juga akan menyertakan
sebuah Migration-nya.
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 2 file baru di dalam
project Laravel, yang berad di dalam folder :
app/Models/Customer.php
database/migrations/2021_07_08_005044_create_customers_table.php
CATATAN : untuk nama file migration akan diambil dari tanggal pembuatannya.
Setelah berhasil melakukan generate Model dan Migration, maka sekarang kita lanjutkan
untuk melakukan beberapa penambahan kode di dalamnya.
Pertama kita akan belajar untuk menambahkan beberapa attribute di dalam table
customers, silahkan buka file migrationnya di
database/migrations/2021_07_08_005044_create_customers_table.php dan
pada function up silahkan diubah menjadi seperti berikut ini :
38
name - menggunakan tipe data string.
email - menggunakan tipe data string dan bersifat unique, artinya isi dari
attribute ini tidak boleh ada yang sama.
email_verified_at - menggunakan tipe data timestamp dan di atur menjadi
nullable, artinya attribute ini tidak wajib diisi.
password - menggunakan tipe data string.
Setelah menambahkan beberapa attribute di file migration, sekarang kita lanjutkan di dalam
Model Customer, yaitu menambahkan mass assigment, ini bertujuan agar attribute yang
sudah kita tambahkan di atas dapat melakukan manipulasi data, seperti proses insert,
update dan delete. Silahkan buka file app/Models/Customer.php dan ubah semua kode-
nya menjadi seperti berikut ini :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'email', 'email', 'email_verified_at', 'password',
'remember_token'
];
}
Di atas kita menambahkan properti $fillable, dimana properti tersebut digunakan untuk
mass assigment dari attribute yang sudah kita tambahkan di file migration.
39
php artisan make:model Category -m
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 2 file baru yang
berada di dalam folder :
app/Models/Category.php
database/migrations/2021_07_08_013658_create_categories_table.php
Seperti sebelumnya, pertama kita akan lakukan penambahan beberapa attribute terlebih
dahulu di dalam file migration. Silahkan buka file
database/migrations/2021_07_08_013658_create_categories_table.php dan
pada function up silahkan ubah kode-nya menjadi seperti berikut ini :
Setelah berhasil menambahkan beberapa attribute di dalam file migration, maka sekarang
kita lanjutkan untuk menambahkan properti $fillable di dalam model. Silahkan buka file
app/Models/Category.php dan ubah semua kode-nya menjadi seperti berikut ini :
40
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'slug', 'image'
];
}
Jika perintah di atas berhasil, maka kita akan mendapatkan 2 file baru yang berada di dalam
folder :
app/Models/Product.php
database/migrations/2021_07_09_004905_create_products_table.php
Pertama, kita akan menambahkan beberapa attribute dulu di dalam file migration, attribute
ini yang nantinya akan di generate di dalam table products.
41
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('image');
$table->string('title');
$table->string('slug');
$table->unsignedBigInteger('category_id');
$table->unsignedBigInteger('user_id');
$table->text('description');
$table->integer('weight');
$table->bigInteger('price');
$table->integer('stock');
$table->integer('discount');
$table->timestamps();
//relationship category
$table->foreign('category_id')->references('id')->on('categories');
//relationship user
$table->foreign('user_id')->references('id')->on('users');
});
}
42
//relationship category
$table->foreign('category_id')->references('id')->on('categories');
Di atas, kita mereferensikan attribute category_id sebagai foreign key ke dalam attribute
id yang ada di dalam table categories.
//relationship user
$table->foreign('user_id')->references('id')->on('users');
Di atas, kita mereferensikan attribute user_id sebagai foreign key ke dalam attribute id
yang ada di dalam table users.
Setelah berhasil menambahkan beberapa attribute di dalam file migration, maka sekarang
kita lanjutkan untuk menambahkan properti $fillable atau yang biasa disebut dengan
Mass Assigment di dalam Model. Silahkan buka file app/Models/Product.php dan ubah
semua kode-nya menjadi seperti berikut ini :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'image', 'title', 'slug', 'category_id', 'user_id',
'description', 'weight', 'price', 'stock', 'discount'
];
}
43
Langkah 4 - Membuat Model dan Migration Cart
Kita lanjutkan untuk membuat Model dan Migration untuk Cart, silahkan jalankan perintah
berikut ini di dalam terminal/CMD dan tentunya di dalam project Laravel.
Jika perintah di atas berhasil, maka kita akan mendapatkan 2 file baru yang berada di dalam
folder :
app/Models/Cart.php
database/migrations/2021_07_09_013817_create_carts_table.php
Pertama, tentu saja kita akan lakukan seperti langkah-langkah sebelumnya, yaitu
menambahakan beberapa attribute di dalam file migration, silahkan buka file
database/migrations/2021_07_09_013817_create_carts_table.php dan pada
function up ubah menjadi seperti berikut ini :
//relationship product
$table->foreign('product_id')->references('id')->on('products');
//relationship customer
$table->foreign('customer_id')->references('id')->on('customers');
});
}
44
customer_id - menggunakan jenis unsignedBigInteger, yang artinya attribute
ini akan digunakan untuk melakukan relasi dengan table customers.
qty - menggunakan tipe data integer.
price - menggunakan tipe data bigInteger.
weight - menggunakan tipe data integer.
Di atas kita juga menambahkan 2 relasi, yaitu ke dalam table products dan customers,
kurang lebih seperti berikut ini :
//relationship product
$table->foreign('product_id')->references('id')->on('products');
Di atas, kita mereferensikan attribute product_id ke dalam attribute id yang ada di dalam
table products.
//relationship customer
$table->foreign('customer_id')->references('id')->on('customers');
Setelah berhasil membuat beberapa attribute dan relasi di dalam file migration, maka
sekarang kita lanjutkan untuk menambahkan Mass Assigment di dalam Model. Silahkan
buka file app/Models/Cart.php dan ubah semua kode-nya menjadi seperti berikut ini :
45
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'product_id', 'customer_id', 'qty', 'price', 'weight'
];
}
Jika perintah di atas berhasil, maka kita akan mendapatkan 2 file baru yang berada di dalam
folder :
app/Models/Province.php
database/migrations/2021_07_09_015943_create_provinces_table.php
Pertama, kita akan menambahkan beberapa attribute terlebih dahulu di dalam file migration
provinces, silahkan buka file
database/migrations/2021_07_09_015943_create_provinces_table.php dan
pada function up silahkan ubah kode-nya menjadi seperti berikut ini :
46
public function up()
{
Schema::create('provinces', function (Blueprint $table) {
$table->id();
$table->integer('province_id');
$table->string('name');
$table->timestamps();
});
}
Setelah itu, kita lanjutkan untuk menambahkan Mass Assigment di dalam Model province,
silahkan buka file app/Models/Province.php dan ubah semua kode-nya menjadi seperti
berikut ini :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'province_id', 'name'
];
}
47
Langkah 6 - Membuat Model dan Migration City
Setelah berhasil membuat Model dan Migration untuk Province, maka sekarang kita
lanjutkan untuk membuatnya untuk City, silahkan jalankan perintah berikut ini di dalam
terminaal/CMD :
Jika perintah di atas berhasil, maka kita akan mendapatkan 2 file baru yang berada di dalam
folder :
app/Models/City.php
database/migrations/2021_07_09_021738_create_cities_table.php
Pertama, kita akan menambahkan beberapa attribute terlebih dahulu di dalam file
migration, silahkan buka file
database/migrations/2021_07_09_021738_create_cities_table.php dan ubah
pada function up menjadi seperti berikut ini :
Dan di atas kita juga menambahkan 1 relasi ke dalam table provinces, yaitu :
48
//relationship province
$table->foreign('province_id')->references('id')->on('provinces');
Dari kode di atas, attribute province_id kita jadikan sebagai foreign key dan kita
referensikan ke dalam attribute id yang ada di dalam table provinces.
Setelah berhasil menambahkan beberapa attribute dan relasi di dalam file migration, maka
sekarang kita lanjutkan untuk menambahkan properti $fillable atau Mass Assigment di
dalam Moedel. Silahkan buka file app/Models/City.php dan ubah semua kode-nya
menjadi seperti berikut ini :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'province_id', 'city_id', 'name'
];
}
49
php artisan make:model Invoice -m
Jika perintah di atas berhasil, maka kita akan mendapatkan 2 file baru di dalam folder :
app/Models/Invoice.php
database/migrations/2021_07_09_025035_create_invoices_table.php
Pertama, kita akan lakukan sedikit perubahan dan di dalam file migration, yaitu
menambahkan beberapa attribute di dalamnya. Silahkan buka file
database/migrations/2021_07_09_025035_create_invoices_table.php dan
pada function up ubah kode-nya menjadi seperti berikut ini :
50
public function up()
{
Schema::create('invoices', function (Blueprint $table) {
$table->id();
$table->string('invoice');
$table->unsignedBigInteger('customer_id');
$table->string('courier');
$table->string('courier_service');
$table->bigInteger('courier_cost');
$table->integer('weight');
$table->string('name');
$table->string('phone');
$table->unsignedBigInteger('city_id');
$table->unsignedBigInteger('province_id');
$table->text('address');
$table->enum('status', array('pending', 'success', 'expired',
'failed'));
$table->bigInteger('grand_total');
$table->string('snap_token')->nullable();
$table->timestamps();
//relationship customer
$table->foreign('customer_id')->references('id')->on('customers');
//relationship city
$table->foreign('city_id')->references('id')->on('cities');
//relationship province
$table->foreign('province_id')->references('id')->on('provinces');
});
}
51
akan digunakan untuk melakukan relasi dengan table cities.
province_id - menggunakan jenis unsignedBigInteger, yang artinya attribute
ini akan digunakan untuk melakukan relasi dengan table provinces.
address - menggunakan tipe data text.
status - menggunakan tipe data enum dan default nilai-nya adalah pending,
success, expired, failed.
grand_total - menggunakan tipe data bigInteger.
snap_token - menggunakan tipe data string dan di set menjadi nullable, yang
artinya tidak wajib diisi.
//relationship customer
$table->foreign('customer_id')->references('id')->on('customers');
Di atas, attribute customer_id dijadikan sebagai foreign key dan mereferensikan ke dalam
id yang ada di dalam table customers.
//relationship city
$table->foreign('city_id')->references('id')->on('cities');
Di atas, attribute city_id dijadikan sebagai foreign key dan akan di hubungkan ke dalam
attribute id yang ada di dalam table cities.
//relationship province
$table->foreign('province_id')->references('id')->on('provinces');
Di atas, attribute province_id dijadikan sebagai foreign key dan mereferensikan ke dalam
id yang ada di dalam table provinces.
Setelah berhasil membuat relasi dan menambahkan beberapa attribute di dalam file
migration, maka sekarang kita akan lanjutkan untuk membuat Mass Assigment di dalam
model. Silahkan buka file app/Models/Invoice.php dan ubah semua kode-nya menjadi
seperti berikut ini :
52
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice', 'customer_id', 'courier', 'courier_service',
'courier_cost', 'weight', 'name', 'phone', 'city_id', 'province_id',
'address', 'status', 'grand_total', 'snap_token'
];
}
Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan jangan lupa harus berada
di dalam project Laravel.
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 2 file baru yang
berada di dalam folder :
app/Models/Order.php
database/migrations/2021_07_10_001434_create_orders_table.php
53
Pertama, tentu saja kita akan menambahkan beberapa attribute terlebih dahulu di dalam
file migration. Silahkan buka file
database/migrations/2021_07_10_001434_create_orders_table.php dan pada
function up ubah menjadi seperti berikut ini :
//relationship invoice
$table->foreign('invoice_id')->references('id')->on('invoices');
//relationship product
$table->foreign('product_id')->references('id')->on('products');
});
}
Kemudian, di dalam penambahan kode di atas, kita juga membuat 2 relasi baru, yaitu :
//relationship invoice
$table->foreign('invoice_id')->references('id')->on('invoices');
Artinya, attribute invoice_id dijadikan sebagai foreign key dan mereferensikan attribute
id yang ada di dalam table invoices.
54
//relationship product
$table->foreign('product_id')->references('id')->on('products');
Artinya, attribute product_id dijadikan sebagai foreign key dan mereferensikan attribute
id yang ada di dalam table products.
Setelah semua attribute berhasil di tambahkan di dalam file migration, maka sekarang kita
lanjutkan untuk menambahkan Mass Assigment atau properti $fillable di dalam Model.
Silahkan buka file app/Models/Order.php dan ubah semua kode-nya menjadi seperti
berikut ini :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice_id', 'product_id', 'qty', 'price'
];
}
55
php artisan make:model Review -m
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 2 file baru di dalam
folder :
app/Models/Review.php
database/migrations/2021_07_10_003132_create_reviews_table.php
Setelah berhasil melakukan generate file, maka sekarang kita lanjutkan untuk
menambahkan beberapa attribute di dalam file migration. Silahkan buka file
database/migrations/2021_07_10_003132_create_reviews_table.php dan pada
function up ubah kode-nya menjadi seperti berikut ini :
//relationship product
$table->foreign('product_id')->references('id')->on('products');
//relationship order
$table->foreign('order_id')->references('id')->on('orders');
//relationship customer
$table->foreign('customer_id')->references('id')->on('customers');
});
}
56
rating -menggunakan tipe data integer.
review - menggunakan tipe data text.
//relationship product
$table->foreign('product_id')->references('id')->on('products');
Di atas, kita atur agar attribute product_id menjadi foreign key dan mereferensikan ke
attribute id yang ada di dalam table products.
//relationship order
$table->foreign('order_id')->references('id')->on('orders');
Di atas, kita atur agar attribute order_id menjadi foreign key dan mereferensikan ke
attribute id yang ada di dalam table orders.
//relationship customer
$table->foreign('customer_id')->references('id')->on('customers');
Di atas, kita atur agar attribute customer_id menjadi foreign key dan mereferensikan ke
attribute id yang ada di dalam table customers.
Setelah berhasil menambahkan beberapa attribute dan relasi, maka kita akan
menambahkan Mass Assigment di dalam Model, yang bertujuan agar attribute yang sudah
kita definisikan dapat melakukan manipulasi data di dalam database. Silahkan buka file
app/Models/Review.php dan ubah semua kode-nya menjadi seperti berikut ini :
57
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'rating', 'review', 'product_id', 'order_id', 'customer_id'
];
}
Jika perintah di atas berhasil, maka kita akan mendapatkan 2 file baru yang berada di dalam
folder :
app/Model/Slider.php
database/migrations/2021_07_10_014717_create_sliders_table.php
Pertama, tentu saja kita akan melakukan penambahan beberapa attribute di dalam file
migration, silahkan buka file
database/migrations/2021_07_10_014717_create_sliders_table.php dan pada
function up ubah menjadi seperti berikut ini :
58
public function up()
{
Schema::create('sliders', function (Blueprint $table) {
$table->id();
$table->string('image');
$table->string('link');
$table->timestamps();
});
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'image', 'link'
];
}
59
Langkah 11 - Menjalankan Migration Database
Setelah kita berhasil membuat beberapa file migration untuk kebutuhan table dari website
yang akan kita bangun, sekarang kita akan menjalankan perintah migrate, yang mana
perintah tersebut akan melakukan generate dari file-file migrasi ke dalam sebuah table
beserta attribute di dalamnya.
Jika berhasil kita akan mendapatkan beberapa table di dalam database, kurang lebih seperti
berikut ini :
60
61
Eloquent Relationships
Saat seorang software developer mengembangkan aplikasi yang cukup besar, tentu saja
akan membutuhkan banyak table di dalamnya dan memungkinkan juga data di dalam table
tersebut akan dipisah-pisah lagi ke table yang lain, itu biasanya disebut dengan normalisasi
database. Seperti one-to-one, one-to-many dan many-to-many.
Karena dalam membangun aplikasi tidak mungkin kita akan menyimpan sebuah data di
dalam 1 table, apalagi aplikasi yang di kembangkan sangat besar, maka dengan relasi atau
normalisasi kita bisa memecahkan problem tersebut, yaitu memisahkan menjadi beberapa
table yang saling berhubungan.
Di dalam Laravel kita akan sangat terbantu dengan adanya fitur Eloquent Relationship,
yang bertujuan untuk mempermudah pada software developer dalam melakukan
normalisasi database. Berikut ini contoh normalisasi dari database.
One to one
One to one relationship terjadi jika data di dalam table A hanya boleh memiliki satu data
di dalam table B (A has one B) dan data B hanya boleh dimiliki oleh data di table A (B
Belongs to A).
Dalam kasus ini, kita cukup memberikan sebuah foreign key di dalam table B, dimana
foreign key ini akan diisi dengan ID yang ada di dalam table A. Contohnya seperti berikut ini
:
table users
62
id name
2 Rizqi Maulana
3 Yudi Purwanto
table phones
id no_telephone user_id
1 085785852224 2
2 081200046719 1
3 089223918038 3
field user_id di dalam table phones akan menyimpan nilai id dari table users, dengan
begitu maka relasi one-to-one bisa terpenuhi.
One to many
One-to-many relathionship memiliki struktur relasi yang hampir sama dengan one-to-
one, perbedaanya adalah satu data di dalam table A boleh memiliki banyak data di dalam
table B. Contoh sederhananya adalah satu data post bisa memiliki banyak komentar.
Perbedaan antara One to Many dan One to Many (Inverse) bisa di ibaratkan contoh
seperti ini :
One to Many - sebuah post hanya bisa memanggil data komentar dan data
komentar tidak bisa memanggil induk dari data post, yang artinya hanya satu arah
saja.
One to Many (Inverse) - sebuah post bisa memanggil data komentar, kemudian
data komentar juga dapat memanggil data induk post, yang artinya bisa dua arah.
63
Jika kita lihat dari gambar diatas antara relasi one-to-one dan one-to-many tidak ada
perbedaan sama sekali, perbedaanya adalah di dalam foreign key pada table comments
tidak bersifat unique, sehingga dapat menyimpan data yang sama, kalau di dalam relasi
one-to-one foreign key bersifat unique, artinya tidak ada data yang boleh sama.
table posts
id title
1 Judul Post 1
2 Judul Post 2
3 Judul Post 3
table comments
id comment post_id
1 Mantap 2
3 Terima Kasih 3
field foreign key post_id di dalam table comments dapat menyimpan id yang sama dari
table posts, dengan begini maka relasi one-to-many dapat terpenuhi.
64
Many to many
Many-to-many relationships merupakan relasi dimana data di dalam table A bisa memiliki
banyak data di dalam table B dan juga sebaliknya, data di dalam table B bisa memiliki
banyak data di dalam table A. Disini kita tidak dapat melakukan relasi langsung antara table
A ke dalam table B, maka dari itu kita harus memiliki satu table lagi untuk menghubungkan
dari kedua table tersebut, biasanya table ini di sebut dengan pivot table. Kurang lebih
seperti berikut ini :
Dari gambar di atas bisa kita lihat bersama-sama, kita memiliki satu table lagi yang
bernama post_tag, table inilah yang disebut dengan pivot table, dimana akan
difungsikan untuk menyimpan id dari masing-masing table yang berhubungan.
table posts
id title
1 Judul Post 1
2 Judul Post 2
table tags
id name
1 Laravel
2 Vue Js
3 React Js
65
id name
4 Node Js
id post_id tag_id
1 1 3
2 1 4
3 2 1
4 1 1
5 2 2
Di dalam table post_tag atau pivot table terdapat dua foreign key yaitu post_id
dimana ini akan menyimpan id dari table posts dan tag_id akan menyimpan id dari
table tags. Dengan begini maka bisa dikatakan sebagai relasi many-to-many.
Setelah mengetahui gambaran dasar tentang normalisasi database, maka sekarang kita
akan coba implementasikan ke dalam project yang sedang kita kembangkan. Berikut ini
relasi yang akan kita buat :
1. ONE TO MANY - antara table users dengan table products, yang artinya 1 data
user bisa memiliki banyak data product.
2. ONE TO MANY (Inverse) - antara table categories dengan table products,
yang artinya 1 data category bisa memiliki banyak data product. Kita jadikan
Inverse, agar kita juga dapat memanggil data induk yaitu category dari data product
(dua arah).
3. ONE TO MANY - antara table invoices dengan table orders, yang artinya 1 data
invoice bisa memiliki banyak data order.
4. ONE TO MANY (Belongs To) - antara table products dengan table carts, disini
kita hanya memanggil data product dari cart. Dan kita tidak perlu membuat relasi
many dari product ke cart.
5. ONE TO MANY (Belongs To) - antara table customers dengan table carts, disini
kita hanya memanggil data customer dari cart. Dan kita tidak perlu membuat relasi
many dari customer ke cart.
6. ONE TO MANY (Inverse) - antara table customers dengan table invoices, yang
artinya 1 data customer bisa memiliki banyak data invoice. Kita jadikan Inverse,
agar kita juga dapat memanggil data induk yaitu customer dari data invoice (dua
arah).
7. ONE TO MANY (Belongs To) - antara table cities dengan table invoices, disini
kita hanya memanggil data city dari invoice. Dan kita tidak perlu membuat relasi
many dari invoice ke city.
66
8. ONE TO MANY (Belongs To) - antara table provinces dengan table invoices,
disini kita hanya memanggil data province dari invoice. Dan kita tidak perlu membuat
relasi many dari invoice ke province.
9. ONE TO MANY (Inverse) - antara table provinces dengan table cities, yang
artinya 1 data province bisa memiliki banyak data city. Kita jadikan Inverse, agar
kita juga dapat memanggil data induk yaitu province dari data city (dua arah).
10. ONE TO MANY - antara table orders dengan table reviews, yang artinya 1 data
order bisa memiliki banyak data review.
11. ONE TO MANY (Inverse) - antara table products dengan table reviews, yang
artinya 1 data product bisa memiliki banyak data review. Kita jadikan Inverse, agar
kita juga dapat memanggil data induk yaitu product dari data review (dua arah).
12. ONE TO MANY (Inverse) - antara table customers dengan table reviews, yang
artinya 1 data customer bisa memiliki banyak data review. Kita jadikan Inverse,
agar kita juga dapat memanggil data induk yaitu customer dari data review (dua
arah).
13. ONE TO MANY (Belongs To) - antara table orders dengan table products, disini
kita hanya memanggil data product dari order. Dan kita tidak perlu membuat relasi
many dari product ke order.
Pertama, kita akan mendefinisikan relasi many dari table users ke table products.
Silahkan buka file app/Models/User.php dan ubah kode-nya menjadi seperti berikut ini :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
67
'email',
'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
/**
* prooducts
*
* @return void
*/
public function products()
{
return $this->hasMany(Product::class);
}
}
Dari perubahan di atas, kita menambahkan method baru yang bernama products. Dimana
method tersebut berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi
Many ke Model Product atau table products.
68
Category.
Silahkan buka file app/Models/Category.php dan ubah kode-nya menjadi seoerti berikut
ini :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'slug', 'image'
];
/**
* products
*
* @return void
*/
public function products()
{
return $this->hasMany(Product::class);
}
}
Di atas, kita menambahkan satu method baru, yaitu products. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model
Product atau table products.
Karena kita akan jadikan sebagai Inverse, dimana product juga bisa memanggil data
induk-nya yaitu category, yang artinya bisa dua arah. Silahkan buka file
app/Models/Product.php dan ubah semua kode-nya menjadi seperti berikut ini :
69
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'image', 'title', 'slug', 'category_id', 'user_id',
'description', 'weight', 'price', 'stock', 'discount'
];
/**
* category
*
* @return void
*/
public function category()
{
return $this->belongsTo(Category::class);
}
}
Kita lanjutkan lagi untuk membuat relasi one-to-many antara table invoices dan table
orders, silahkan buka file app/Models/Invoice.php dan ubah semua kode-nya menjadi
seperti berikut ini :
70
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice', 'customer_id', 'courier', 'courier_service',
'courier_cost', 'weight', 'name', 'phone', 'city_id', 'province_id',
'address', 'status', 'grand_total', 'snap_token'
];
/**
* order
*
* @return void
*/
public function orders()
{
return $this->hasMany(Order::class);
}
}
Di atas, kita menambahkan satu method baru, yaitu orders. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model
Order atau table orders.
71
Silahkan buka file app/Models/Cart.php dan ubah kode-nya menjadi seperti berikut ini :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'product_id', 'customer_id', 'qty', 'price', 'weight'
];
/**
* product
*
* @return void
*/
public function product()
{
return $this->belongsTo(Product::class);
}
}
72
melainkan hanya membuat relasi belongs to / Inverse agar kita dapat memanggil
data customer dari cart.
Silahkan buka file app/Models/Cart.php dan ubah semua kode-nya menjadi seperti
berikut ini :
73
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'product_id', 'customer_id', 'qty', 'price', 'weight'
];
/**
* product
*
* @return void
*/
public function product()
{
return $this->belongsTo(Product::class);
}
/**
* customer
*
* @return void
*/
public function customer()
{
return $this->belongsTo(Customer::class);
}
}
74
Langkah 6 - ONE TO MANY (Inverse) - antara table customers
dengan table invoices
Sekarang kita lanjutkan untuk membuat relasi many dan inverse antara customer dan
invoice. Pertama kita akan membuat relasi many terlebih dahulu dari customer ke invoice,
setelah itu kita atur inverse atau belongs to-nya dari invoice ke customer.
Silahkan buka file app/Models/Customer.php dan ubah semua kode-nya menjadi seperti
berikut ini :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'email', 'email', 'email_verified_at', 'password',
'remember_token'
];
/**
* invoice
*
* @return void
*/
public function invoices()
{
return $this->hasMany(Invoice::class);
}
}
75
Di atas, kita menambahkan satu method baru, yaitu invoices. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model
Invoice atau table invoices.
Kemudian kita akan membuat proses inverse agar kita juga dapat memanggil customer
melalui data invoice. Silahkan buka file app/Models/Invoice.php kemudian ubah kode-
nya menjadi seperti berikut ini :
76
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice', 'customer_id', 'courier', 'courier_service',
'courier_cost', 'weight', 'name', 'phone', 'city_id', 'province_id',
'address', 'status', 'grand_total', 'snap_token'
];
/**
* order
*
* @return void
*/
public function orders()
{
return $this->hasMany(Order::class);
}
/**
* customer
*
* @return void
*/
public function customer()
{
return $this->belongsTo(Customer::class);
}
}
77
bahwa model Customer atau table customers ini dimiliki dan terhubung dengan Model
Invoice atau table invoices.
Silahkan buka file app/Models/Invoice.php dan ubah semua kode-nya menjadi seperti
berikut ini :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice', 'customer_id', 'courier', 'courier_service',
'courier_cost', 'weight', 'name', 'phone', 'city_id', 'province_id',
'address', 'status', 'grand_total', 'snap_token'
];
/**
* order
*
* @return void
*/
public function orders()
{
return $this->hasMany(Order::class);
78
}
/**
* customer
*
* @return void
*/
public function customer()
{
return $this->belongsTo(Customer::class);
}
/**
* city
*
* @return void
*/
public function city()
{
return $this->belongsTo(City::class, 'city_id', 'city_id');
}
}
Di atas, kita menambahkan method city yang di fungsikan untuk mendeklarasikan bahwa
model City atau table cities ini dimiliki dan terhubung dengan Model Invoice atau
table invoices.
Di atas karena foreign key dan primary key menggunakan attribute yang bernama
city_id, maka di dalam method-nya kita juga set. Jika menggunakan custom primary
key atau foreign key, kita bisa lakukan seperti berikut ini :
79
invoice, agar kita dapat memanggil data induk-nya, yaitu province.
Silahkan buka file app/Models/Invoice.php dan ubah semua kode-nya menjadi seperti
berikut ini :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice', 'customer_id', 'courier', 'courier_service',
'courier_cost', 'weight', 'name', 'phone', 'city_id', 'province_id',
'address', 'status', 'grand_total', 'snap_token'
];
/**
* order
*
* @return void
*/
public function orders()
{
return $this->hasMany(Order::class);
}
/**
* customer
*
* @return void
*/
public function customer()
{
return $this->belongsTo(Customer::class);
80
}
/**
* city
*
* @return void
*/
public function city()
{
return $this->belongsTo(City::class, 'city_id', 'city_id');
}
/**
* province
*
* @return void
*/
public function province()
{
return $this->belongsTo(Province::class, 'province_id',
'province_id');
}
}
Silahkan buka file app/Models/Province.php dan ubah semua kode-nya menjadi seperti
berikut ini :
81
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'province_id', 'name'
];
/**
* cities
*
* @return void
*/
public function cities()
{
return $this->hasMany(City::class, 'province_id');
}
}
Di atas, kita menambahkan satu method baru, yaitu cities. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model City
atau table cities.
Kemudian kita akan membuat proses Inverse agar kita juga dapat memanggil province
melalui data city. Silahkan buka file app/Models/City.php kemudian ubah kode-nya
menjadi seperti berikut ini :
82
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'province_id', 'city_id', 'name'
];
/**
* province
*
* @return void
*/
public function province()
{
return $this->belongsTo(Province::class, 'province_id');
}
}
Sekarang kita akan membuat relasi one-to-many dari table orders ke table reviews.
Silahkan buka file app/Models/Order.php dan ubah semua kode-nya menjadi seperti
berikut ini :
83
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice_id', 'product_id', 'qty', 'price'
];
/**
* reviews
*
* @return void
*/
public function reviews()
{
return $this->hasMany(Review::class);
}
}
Di atas, kita menambahkan satu method baru, yaitu reviews. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model
Review atau table reviews.
84
Silahkan buka file app/Models/Product.php dan ubah semua kode-nya menjadi seperti
berikut ini :
85
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'image', 'title', 'slug', 'category_id', 'user_id',
'description', 'weight', 'price', 'stock', 'discount'
];
/**
* category
*
* @return void
*/
public function category()
{
return $this->belongsTo(Category::class);
}
/**
* reviews
*
* @return void
*/
public function reviews()
{
return $this->hasMany(Review::class);
}
}
Di atas, kita menambahkan satu method baru, yaitu reviews. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model
86
Review atau table reviews.
Setelah itu, sekarang kita tinggal membuat proses inverse atau belongs to-nya. Agar data
review juga dapat mengakses data induknya yaitu product.
Silahkan buka file app/Models/Review.php dan ubah semua kode-nya menjadi seperti
berikut ini :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'rating', 'review', 'product_id', 'order_id', 'customer_id'
];
/**
* product
*
* @return void
*/
public function product()
{
return $this->belongsTo(Product::class);
}
}
87
Langkah 12 - ONE TO MANY (Inverse) - antara table customers
dengan table reviews
Sama seperti sebelumnya, yaitu membuat relasi one-to-many (Inverse), disini kita
akan menghubungkan antara table customers dan reviews.
Silahkan buka file app/Models/Customer.php dan ubah kode-nya menjadi seperti berikut
ini :
88
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'email', 'email', 'email_verified_at', 'password',
'remember_token'
];
/**
* invoice
*
* @return void
*/
public function invoices()
{
return $this->hasMany(Invoice::class);
}
/**
* reviews
*
* @return void
*/
public function reviews()
{
return $this->hasMany(Review::class);
}
}
Di atas, kita menambahkan satu method baru, yaitu reviews. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model
89
Review atau table reviews.
Setelah itu, kita akan menambahkan inverse atau belongs to di dalam model Review.
Silahkan buka file tersebut di dalam app/Models/Review.php dan ubah semua kode-nya
menjadi seperti berikut ini :
90
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'rating', 'review', 'product_id', 'order_id', 'customer_id'
];
/**
* product
*
* @return void
*/
public function product()
{
return $this->belongsTo(Product::class);
}
/**
* customer
*
* @return void
*/
public function customer()
{
return $this->belongsTo(Customer::class);
}
}
91
Langkah 13 - ONE TO MANY (Belongs To) - antara table orders
dengan table products
Terakhir, kita akan membuat relasi one-to-many (Belongs To), jadi kita akan
memanggil data product melalui data order. Silahkan buka file app/Models/Order.php
dan ubah semua kode-nya menjadi seperti berikut ini :
92
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice_id', 'product_id', 'qty', 'price'
];
/**
* reviews
*
* @return void
*/
public function reviews()
{
return $this->hasMany(Review::class);
}
/**
* product
*
* @return void
*/
public function product()
{
return $this->belongsTo(Product::class);
}
}
93
Eloquent Accessors, Mutators & Casting
aravel memiliki fitur yang bernama Mutator, Accessor dan Casting, fitur-fitur ini digunakan
untuk melakukan manipulasi data di dalam attribute database dengan sangat mudah.
Contoh sederhanya misalnya kita ingin melakukan insert data dengan enkripsi ke dalam
database dan melakukan deskripsi saat menampilkan dari database secara otomatis, maka
kita bisa menggunakan fitur ini.
get{namaAttribute}Attribute
94
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
Di atas, kita membuat sebuah function dengan nama getNameAttribute dan di dalamnya
kita lakukan return dengan menjadikannya sebagai huruf besar menggunakan strtouper.
Jika kita asumsikan mempunya data user dengan attribute name Fika Ridaul Maulayya,
dan jika kita coba panggil kurang lebih seperti berikut ini :
use App\Models\User;
$user = User::find(1);
$name = $user->name;
Jika di jalankan, maka akan menghasilkan seperti ini : FIKA RIDAUL MAULAYYA.
95
set{namaAttribute}Attribute
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
Setiap kita melakukan insert data name ke dalam database, maka akan di format menjadi
huruf kecil semuanya.
array
boolean
collection
date
datetime
96
decimal:<digits>
double
encrypted
encrypted:array
encrypted:collection
encrypted:object
float
integer
object
real
string
timestamp
Dalam kasus ini kita ambil contoh untuk melakukan konversi attribute
email_verified_at yang memiliki tipe data timestap di database, kemudian kita ubah
menjadi datetime . Kurang lebih seperti berikut ini :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
Setelah kita belajar tentang Laravel Mutator, Accessor dan Casting, sekarang kita akan
lanjutkan untuk menerapkan Accessor di dalam project yang sedang kita kembangkan, disini
kebetulan kita hanya butuh untuk Accessor saja.
Sekarang kita akan belajar menambahkan Accessor di dalam Model Category untuk
memanipulasi attribute image, disini kita akan membuat agar saat kita memanggil attribute
97
image akan mendapatkan hasil full-path dari attribute tersebut.
98
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'slug', 'image'
];
/**
* products
*
* @return void
*/
public function products()
{
return $this->hasMany(Product::class);
}
/**
* getImageAttribute
*
* @param mixed $image
* @return void
*/
public function getImageAttribute($image)
{
return asset('storage/categories/' . $image);
}
}
99
storage/categories/. Jadi jika kita memanggil attribute image makan akan otomatis
menghasilkan output seperti berikut ini :
domain.com/storage/categories/nama_file_image.png
Tapi, jika kita tidak menggunakan fitur Accessor, maka hasilnya akan seperti berikut ini :
nama_file_image.png
Sekarang kita lanjutkan untuk menambahkan Accessor di dalam Model Customer, disini kita
akan memanipulasi attribute created_at saat ditampilkan menjadi format tanggal
Indonesia yang mudah di baca.
<?php
namespace App\Models;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'email', 'email', 'email_verified_at', 'password',
'remember_token'
];
/**
100
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* invoice
*
* @return void
*/
public function invoices()
{
return $this->hasMany(Invoice::class);
}
/**
* reviews
*
* @return void
*/
public function reviews()
{
return $this->hasMany(Review::class);
}
/**
* getCreatedAtAttribute
*
* @param mixed $date
* @return void
*/
public function getCreatedAtAttribute($date)
{
$value = Carbon::parse($date);
$parse = $value->locale('id');
return $parse->translatedFormat('l, d F Y');
}
}
101
Disini kita menggunakan bantuan Carbon dan kita set dengan locale id, yang artinya
adalah format Indonesia.
Disini kita akan membuat 2 accessor, yang pertama untuk manipulate attribute image dan
yang satunya untuk manipulate attribute dari hasil response JSON, dimana hasil response
tersebut yaitu berupa data average yang nantinya akan digunakan untuk menampilkan
rating atau ulasan.
Silahkan buka file app/Models/Product.php dan ubah semua kode-nya menjadi seperti
berikut ini :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'image', 'title', 'slug', 'category_id', 'user_id',
'description', 'weight', 'price', 'stock', 'discount'
];
/**
* category
*
* @return void
*/
public function category()
{
return $this->belongsTo(Category::class);
}
102
/**
* reviews
*
* @return void
*/
public function reviews()
{
return $this->hasMany(Review::class);
}
/**
* getImageAttribute
*
* @param mixed $image
* @return void
*/
public function getImageAttribute($image)
{
return asset('storage/products/' . $image);
}
/**
* getReviewsAvgRatingAttribute
*
* @param mixed $value
* @return void
*/
public function getReviewsAvgRatingAttribute($value) {
return $value ? substr($value, 0, 3) : 0;
}
}
Terakhir, kita akan menambahkan Accessor di dalam Model Slider, fungsinya sama yaitu
untuk memanipulasi attribute image agar dapat menghasilkan full-path gambar.
103
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'image', 'link'
];
/**
* getImageAttribute
*
* @param mixed $image
* @return void
*/
public function getImageAttribute($image)
{
return asset('storage/sliders/' . $image);
}
}
104
Membuat Data Seeder User
Laravel memiliki fitur yang bernama Database Seeding, fitur ini bisa kita manfaatkan
untuk membuat dummy data ke dalam database. Dan disini kita akan manfaatkan fitur ini
untuk membuat dummy data user, yang mana akan digunakan untuk melakukan proses
authentication di dalam halaman admin.
Jika perintah diatas berhasil dijalankan, maka kita akan mendapatkan 1 file baru di dalam
folder database/seeders/UserSeeder.php. Silahkan buka file tersebut dan ubah kode-
nya menjadi seperti berikut ini :
105
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
Di atas, kita import Facades DB, ini digunakan untuk melakukan insert data user ke dalam
database dengan Query Builder .
use Illuminate\Support\Facades\DB;
Selanjutnya, kita juga import Facades Hash, ini akan digunakan untuk melakukan hash
password ke dalam database.
use Illuminate\Support\Facades\Hash;
Dan di dalam function run kita melakukan insert data user ke dalam table users
menggunakan Query Builder dan jika teman-teman perhatikan untuk password kita
tambahkan Facades Hash untuk melakukan hashing password ke dalam database.
106
Langkah 2 - Menjalankan Seeder
Sekarang, kita akan menjalankan UserSeeder agar data user di atas di masukkan ke dalam
table users. Silahkan jalankan perintah di bawah ini di dalam terminal/CMD :
composer dump-autoload
Jika berhasil, maka kita bisa melihat ada data user baru di dalam table users. Kurang lebih
seperti berikut ini :
107
Apa itu API ?
API atau Application Programming Interface merupakan sebuah interface yang dapat
menghubungkan antara satu aplikasi dengan aplikasi yang lain dengan lebih mudah.
Dengan API kita dapat melakukan pertukaraan data dari aplikasi yang berbeda, baik dalam
satu platform maupun berbeda platform. [1]
Dimana tujuannya adalah untuk menjadikan sistem yang memiliki performa yang baik,
cepat dan mudah untuk di kembangkan (scale) terutama dalam pertukaran dan komunikasi
data. [2]
Di dalam RESTful API terdapaat beberapa bagian yang harus dipenuhi. Diantaranya adalah :
1. URL
RESTful API diakses menggunakan protokol HTTP. Penamaan dan struktur URL yang
konsisten akan menghasilkan API yang baik dan mudah untuk dimengerti developer.
URL API biasa disebut endpoint dalam pemanggilannya.
2. HTTP Verbs
108
Setiap request yang dilakukan terdapat metode yang dipakai agar server mengerti
apa yang sedang di request client
GET : adalah metode HTTP Request yang paling simpel, metode ini digunakan
untuk membaca atau mendapatkan data dari sumber.
POST : adalah metode HTTP Request yang digunakan untuk membuat data baru
dengan menyisipkan data dalam body saat request dilakukan.
PUT : adalah metode HTTP Request yang biasanya digunakan untuk melakukan
update data resource.
DELETE : adalah metode HTTP Request yang digunakan untuk menghapus
suatu data pada resource.
3. HTTP Response Code HTTP response code adalah kode standarisasi dalam
menginformasikan hasil request kepada client. Secara umum terdapat 3 kelompok
yang biasa kita jumpai pada RESTful API yaitu :
4. Format Response Setiap request yang dilakukan client akan menerima data
response dari server, response tersebut biasanya berupa data XML ataupun JSON.
Setelah mendapatkan data response tersebut barulah client bisa menggunakannya
dengan cara memparsing data tersebut dan diolah sesuai kebutuhan.
Dalam buku ini kita akan belajar bagaimana cara bertukar data dari satu aplikasi (Laravel)
ke aplikasi yang lain (Nuxt.js) dengan menggunakan API. Dan untuk pertukaran data
tersebut kita akan menggunakan data berupa JSON.
[1] : https://www.niagahoster.co.id/blog/api-adalah/#Apa_itu_API
[2] : https://jogjaweb.co.id/blog/pengertian-restfull-api
109
Apa itu JWT (Json Web Token) ?
JSON Web Token (JWT) adalah standar terbuka (RFC 7519) yang mendefinisikan cara
kompak dan mandiri untuk mentransmisikan informasi dengan aman antar pihak sebagai
objek JSON. Informasi ini dapat diverifikasi dan dipercaya karena ditandatangani secara
digital. JWT dapat ditandatangani menggunakan sebuah secreet (dengan algoritme HMAC)
atau pasangan kunci publik / pribadi menggunakan RSA atau ECDSA.
(https://jwt.io/introduction/)
Sederhananya JWT merupakan Web Token yang berupa JSON dan umumnya digunakan
untuk proses authentication. Dimana untuk mendapatkan suatu data di dalam server kita
harus menyertakan token tersebut sebagai kuncinya.
Authentication
Pertukaran Informasi
JSON Web Token adalah cara yang baik untuk mengirimkan informasi antar pihak
dengan aman. Dengan token yang sudah ditandatangani dengan algoritme RSA, maka
kita bisa tahu siapa yang melakukan request tersebut.
Untuk mempelajari tentang JWT lebih lanjut, kita bisa melihatnya di dalam website resminya
di https://jwt.io/. Dan untuk implementasi JWT di dalam project kali ini, kita akan
menggunakan package dari https://jwt-auth.readthedocs.io/en/develop/laravel-installation/.
Dan ini akan kita implementasikan untuk membuat Authentication di dalam project Nuxt Js
nanti. Di materi berikutnya kita bersama-sama akan belajar begaimana cara installasi dan
110
konfigurasi package tersebut di dalam project kita.
111
Installasi dan Konfigurasi JWT untuk Admin
Di dalam materi kali ini kita akan belajar bagaimana cara melakukan proses installasi dan
konfigurasi otentikasi JWT untuk admin dan untuk otentikasi customer akan kita bahas di
mater-materi selanjutnya.
Pertama, kita akan lakukan installasi JWT di dalam project Laravel, kemudian melakukan
beberapa konfigurasi di dalam Model yang akan digunakan sebagai otentikasi berbasis JWT
nantinya.
Silahkan tunggu proses installasi sampai selesai. Dan pastikan harus terhubung dengan
internet, karena package ini akan di unduh secara online.
Jika perintah di atas berhasil, maka kita akan mendapatkan 1 file di dalam folder
config/jwt.php. Di dalam file tersebut kita bisa sesuaikan konfigurasi dari JWT jika
memang dibutuhkan, misalnya mengubah waktu expired token, dan lain-lain.
112
php artisan jwt:secret
Jika perintah di atas berhasil, maka kita akan mendapatkan variable yang isinya adalah
secreet key dari JWT di dalam file .env.
JWT_SECRET=
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],
113
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
Di atas, pertama kita ubah nama guard yang semula api menjadi api_admin, kemudian
untuk driver-nya yang semula token kita ubah menjadi jwt.
<?php
namespace App\Models;
/**
* The attributes that are mass assignable.
*
114
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
/**
* prooducts
*
* @return void
*/
public function products()
{
return $this->hasMany(Product::class);
}
/**
* Get the identifier that will be stored in the subject claim of
the JWT.
*
* @return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}
115
/**
* Return a key value array, containing any custom claims to be
added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims()
{
return [];
}
}
function getJWTIdentifier
function getJWTCustomClaims
116
Membuat Restful API Login Admin
Setelah belajar installasi dan konfigurasi JWT (Json Web Token) di dalam project Laravel,
maka sekarang kita akan lanjutkan untuk membuat sebuah controller yang nantinya akan
digunakan untuk proses otentikasi. Disini kita akan membuat beberapa method juga,
diantaranya adalah :
Perintah di atas akan digunakan untuk membuat controller baru dengan nama
LoginController yang berada di dalam folder app/Http/Controllers/Api/Admin.
Sekarang buka file tersebut kemudian ubah kode-nya menjadi seperti berikut ini :
<?php
namespace App\Http\Controllers\Api\Admin;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Facades\JWTAuth;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
117
*/
public function index(Request $request)
{
//set validasi
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'password' => 'required',
]);
//response error validasi
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
}
//response login "success" dengan generate "Token"
return response()->json([
'success' => true,
'user' => auth()->guard('api_admin')->user(),
'token' => $token
], 200);
}
/**
* getUser
*
* @return void
*/
public function getUser()
{
//response data "user" yang sedang login
return response()->json([
'success' => true,
'user' => auth()->guard('api_admin')->user()
], 200);
118
}
/**
* refreshToken
*
* @param mixed $request
* @return void
*/
public function refreshToken(Request $request)
{
//refresh "token"
$refreshToken = JWTAuth::refresh(JWTAuth::getToken());
}
}
Dari perubahan kode di atas, pertama kita import provider Http Request dari Laravel, ini
digunakan untuk menerimaa request yang dikirim melalui cookie, input form dan lain-lain.
119
use Illuminate\Http\Request;
Setelah itu, kita juga import Facades dari JWT, ini akan kita gunakan untuk proses yang
berhubungan dengan Token JWT nantinya.
use Tymon\JWTAuth\Facades\JWTAuth;
Dan kita juga import Facades Validator, yang mana akan kita gunakan untuk proses
validasi input yang akan kita buat di Rest API nanti.
use Illuminate\Support\Facades\Validator;
Di atas, pertama kita ubah nama guard yang semula api menjadi api_admin,
kemudian untuk driver-nya yang semula token kita ubah menjadi jwt.
getUser
refreshToken
logout
function index
Di dalam method ini kita akan melakukan proses otentikasi menggunakan JWT, dimana jika
proses otentikasi berhasil maka akan dilakukan generate Token. Sebelum itu disini kita
menambahkan sebuah validasi untuk pengecekan apakah data yang dikirim benar-benar
sesuai dengan yang diharapkan.
120
//set validasi
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'password' => 'required',
]);
Jika data yang dikirim tidak sesuai dengan validasi di atas, maka kita akan mengembalikan
sebuah response dengan format JSON yang berisi informasi error tentang validasi.
Setelah itu kita buat sebuah variable yang bernama $credentials yang berisi sebuah
data email dan password yang dikirim melalui Http request. Kemudian kita lakukan proses
pengecekan jika email dan password tersebut tidak terdaftar di dalam database.
//...
Maka akan mengembalikan sebuah response dengan format JSON yang berisi informasi
tentang proses login gagal atau failed.
121
//response login "failed"
return response()->json([
'success' => false,
'message' => 'Email or Password is incorrect'
], 401);
Tapi jika email dan password ada di dalam database, maka kita akan mengembalikan
sebuah response dalam format JSON yang berisi informasi data user dan informasi token
JWT.
function getUser
Method ini digunakan untuk menampilkan informasi data user yang sedang login, kurang
lebih seperti berikut ini :
Di atas kita mendapatkan data user menggunakan helper auth dari Laravel, dimana helper
auth tersebut kita arahkan ke dalam object guard api_admin, yang memang proses
otentikasinya menggunakan guard API tersebut.
function refreshToken
Method ini kita gunakan untuk proses memperbarui token JWT, alurnya kita generate
sebuah token baru setelah itu token tersebut kita set ke data user yang sedang login.
122
//refresh "token"
$refreshToken = JWTAuth::refresh(JWTAuth::getToken());
Kode di atas digunakan untuk melakukan proses generate token baru. Setelah itu hasil dari
token baru tersebut kita set ke dalam data user yang sedang login.
Kemudian kita set juga untuk header Authorization dengan value Bearer + token baru.
Setelah itu kita mengembalikan sebuah response dalam format JSON yang berisi informasi
data user beserta token baru.
function logout
Method ini digunakan untuk proses menghapus token yang terdaftar di server backend,
untuk mengahapus token kita bisa menggunakan kode seperti berikut ini :
Setelah itu kita mengembalikan sebuah response dalam format JSON dengan status success
true.
123
//response "success" logout
return response()->json([
'success' => true,
], 200);
Silahkan buka file routes/api.php kemudian ubah kode-nya menjadi seperti berikut ini :
124
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
//route login
Route::post('/login',
[App\Http\Controllers\Api\Admin\LoginController::class, 'index', ['as'
=> 'admin']]);
//data user
Route::get('/user',
[App\Http\Controllers\Api\Admin\LoginController::class, 'getUser', ['as'
=> 'admin']]);
//logout
Route::post('/logout',
[App\Http\Controllers\Api\Admin\LoginController::class, 'logout', ['as'
=> 'admin']]);
});
});
Di atas, kita buat sebuah route dengan prefix admin. Prefix ini digunakan untuk
menambahkan kata admin di awalan URL. Jadi jika kita atur sebuah route dengan url
/login, maka kita akan otomatis mendapatkan hasilnya menjadi /admin/login. Karena
awalan URL akan di tambahkan kata admin.
125
//group route with prefix "admin"
Route::prefix('admin')->group(function () {
//...
});
Di dalamnya kita membuat sebuah route, yang kita arahkan ke URL /login dan route ini
memanggil sebuah method yang bernama index di dalam controller LoginController.
//route login
Route::post('/login',
[App\Http\Controllers\Api\Admin\LoginController::class, 'index', ['as'
=> 'admin']]);
Selanjutnya kita membuat route dengan jenis group, yaitu kita akan mengelompokan route
berdasarkan kondisi tertentu, disini kita akan mengelompokan sebuah route ke dalam
middleware auth:api_admin. Yang artinya route yang ada di dalam group ini hanya bisa
diakses jika sudah melakukan proses otentikasi atau login.
//...
});
/user - digunakan untuk menampilkan data user yang sedang login, dimana route ini
kita arahkan ke dalam sebuah method yang bernama getUser yang berada di dalam
controller LoginController.
refresh - digunakan untuk melakukan generate token JWT baru atau biasa disebut
dengan refresh token, disini kita arahkan ke dalam method yang bernama
refreshToken yang berada di dalam controller LoginController.
logout - digunakan untuk proses logout dari aplikasi, disini kita arahkan ke dalam
sebuah method yang bernama logout yang berada di dalam controller
LoginController.
Dan setiap route kita berikan tambahan ['as' => 'admin'], yang artinya setiap route
126
akan diberikan name dengan awalan admin.
Silahkan teman-teman buka aplikasi Postman terlebih dahulu, setelah aplikasi berhasil
terbuka, silahkan masukkan URL berikut ini http://localhost:8000/api/admin/login dan untuk
method-nya silahkan gunakan POST.
Jika sudah, silahkan klik Send, maka kita akan mendapatkan sebuah response validasi yang
berisi informasi bahwa kolom email dan password tidak boleh dikosongkan.
Dari gambar di atas bisa dilihat, kita mendapatkan sebuah response validasi dengan status
code 422 Unprosessable Entity.
Sekarang mari kita coba lagi dengan memasukkan email dan password yang sebelumnya
sudah kita buat di dalam user seeder. Silahkan kembali lagi ke aplikasi Postman dan klik
tab Body kemudian pilih form-data dan masukkan key dan value berikut ini :
KEY VALUE
127
KEY VALUE
email admin@gmail.com
password password
CATATAN ! : data di atas merupakan data user yang ada di dalam database dan data
tersebut dibuat saat kita melakukan pembuatan user seeder.
Sekarang, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response yang berisi informasi data user dan token JWT. Kurang lebih seperti berikut ini :
INFORMASI : selanjutnya untuk mengakses route yang membutuhkan otentikasi, maka kita
harus menyertakan token yang di dapatkan dari proses login seperti di atas.
Setelah berhasil mendapatkan token dari hasil proses otentikasi, sekarang kita lanjutkan
untuk proses uji coba get data user, dimana untuk mendapatkan data user yang sedang
login kita membutuhkan token dari proses otentikasi sebelumnya.
Silahkan buka tab baru di aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/admin/user, dan untuk method-nya silahkan pilih GET. Setelah itu,
silahkan klik tab Headers kemudian masukkan key dan value berikut ini di dalamnya.
128
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, silahakn klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response yang berisi informasi tentang data user yang sedang login.
Pada tahap kali ini kita semua akan belajar bagaimana cara melakuakn refresh token di
dalam JWT (Json Web Token), yaitu konsepnya melakukan regenerate atau memperbarui
token yang lama menjadi baru dan di set ke user yang sedang login.
Silahkan buka tab baru di dalam aplikasi Postman, kemudian masukkan URL berikut ini
http://localhost:8000/api/admin/refresh dan untuk method-nya silahkan pilih GET.
Selanjutnya klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
129
KEY VALUE
Content-Type application/json
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dengan berisi informasi data user beserta token baru. Jika kita perhatikan token
dari hasil login akan di regenerate dengan token baru, kita bisa bandingkan 2 token
tersebut, maka nilainya akan berbeda.
CATATAN ! : selanjutnya untuk token yang dipakai adalah token dari hasil Refresh Token.
Tapi jika kita melakukan proses login ulang, maka token yang di pakai adalah token hasil
login tersebut.
Terakhir kita akan lakukan uji coba untuk proses logout, silahkan buka tab baru di Postman
daan masukkan URL berikut ini http://localhost/api/admin/logout dan untuk method-nya
adalah POST.
Selanjutnya klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
130
KEY VALUE
Content-Type application/json
Jika sudah silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response berupa informasi bahwa proses logout berhasil.
131
Membuat Restful API Dashboard Admin
Di materi sebelumnya kita sudah belajar banyak hal tentang proses otentikasi menggunakan
JWT (Json Web Token). Maka disini kita akan lanjutkan untuk membuat sebuah Rest API
untuk dashboard, yang mana isinya adalah statistik pendapatan dari data order yang akan
kita tampilkan nantinya di halaman frontend menjadi grafik atau chart.
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama DashboardController yang berada di dalam folder
app/Http/Controllers/Api/Admin. Silahkan buka file tersebut dan ubah kode-nya
menjadi seperti berikut ini :
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Models\Invoice;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
132
$expired = Invoice::where('status', 'expired')->count();
$failed = Invoice::where('status', 'failed')->count();
//chart
$transactions = DB::table('invoices')
->addSelect(DB::raw('SUM(grand_total) as grand_total'))
->addSelect(DB::raw('MONTH(created_at) as month'))
->addSelect(DB::raw('MONTHNAME(created_at) as month_name'))
->addSelect(DB::raw('YEAR(created_at) as year'))
->whereYear('created_at', '=', $year)
->where('status', 'success')
->groupBy('month')
->orderByRaw('month ASC')
->get();
if(count($transactions)) {
foreach ($transactions as $result) {
$month_name[] = $result->month_name;
$grand_total[] = (int)$result->grand_total;
}
}else {
$month_name[] = "";
$grand_total[] = "";
}
//response
return response()->json([
'success' => true,
'message' => 'Statistik Data',
'data' => [
'count' => [
'pending' => $pending,
'success' => $success,
'expired' => $expired,
'failed' => $failed
],
'chart' => [
'month_name' => $month_name,
'grand_total' => $grand_total
]
]
], 200);
}
}
133
Dari perubahan kode di atas, pertama kita melakukan import Model Invoice terlebih
dahulu, karena kita akan menggunakannya untuk mendapatkan data transaksi.
use App\Models\Invoice;
Setelah itu, kita juga melakukan import Facades DB dari Laravel, karena nanti kita akan
melakukan sedikit query advanced untuk mendapatkan data transaksi di bulan dan tahun
yang sedang aktif.
use Illuminate\Support\Facades\DB;
//count invoice
$pending = Invoice::where('status', 'pending')->count();
$success = Invoice::where('status', 'success')->count();
$expired = Invoice::where('status', 'expired')->count();
$failed = Invoice::where('status', 'failed')->count();
Setelah itu, kita menambahkan kode lagi dengan melakukan query ke dalam table
invoices untuk mendapatkan nama bulan, jumlah transaksi dan data yang memiliki status
success di tahun yang aktif.
134
//yearth
$year = date('Y');
//chart
$transactions = DB::table('invoices')
// 1
->addSelect(DB::raw('SUM(grand_total) as grand_total'))
// 2
->addSelect(DB::raw('MONTH(created_at) as month'))
// 3
->addSelect(DB::raw('MONTHNAME(created_at) as month_name'))
// 4
->addSelect(DB::raw('YEAR(created_at) as year'))
// 5
->whereYear('created_at', '=', $year)
// 6
->where('status', 'success')
// 7
->groupBy('month')
// 8
->orderByRaw('month ASC')
// 9
->get();
if(count($transactions)) {
foreach ($transactions as $result) {
$month_name[] = $result->month_name;
$grand_total[] = (int)$result->grand_total;
}
else {
$month_name[] = "";
$grand_total[] = "";
}
Di atas, kita coba terjemahkan query-nya kurang lebih seperti berikut ini :
135
9. di urutkan berdasarkan nama bulan secara Ascending, menggunakan orderByRaw.
Jika transaksi dari query di atas ditemukan, maka kita akan melakuak perulangan
menggunakan foreach dan nilainnya akan di assign ke dalam variable $month_name dan
$grand_total.
Tapi jika hasil dari query di atas kosong, maka kita cukup atur kedua variable tersebut
dengan nilai kosong.
$month_name[] = "";
$grand_total[] = "";
Setelah itu, kita lakukan return ke dalam format JSON, kurang lebih seperti berikut ini :
/response
return response()->json([
'success' => true,
'message' => 'Statistik Data',
'data' => [
'count' => [
'pending' => $pending,
'success' => $success,
'expired' => $expired,
'failed' => $failed
],
'chart' => [
'month_name' => $month_name,
'grand_total' => $grand_total
]
]
], 200);
136
Langkah 2 - Menambahkan Route Dashboard
Setelah berhasil membuat controller dan menambahkan method di dalamnya, maka
sekarang kita lanjutkan untuk menambahkan route agar controller dashboard dapat diakses.
Silahkan tambahkan route di bawah ini di dalam file routes/api.php, di dalam prefix
admin dan group middleware auth:api_admin, lebih tepatnya di bawah route /logout.
//dashboard
Route::get('/dashboard',
[App\Http\Controllers\Api\Admin\DashboardController::class, 'index',
['as' => 'admin']]);
137
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
//route login
Route::post('/login',
[App\Http\Controllers\Api\Admin\LoginController::class, 'index', ['as'
=> 'admin']]);
//data user
Route::get('/user',
[App\Http\Controllers\Api\Admin\LoginController::class, 'getUser', ['as'
=> 'admin']]);
//logout
Route::post('/logout',
[App\Http\Controllers\Api\Admin\LoginController::class, 'logout', ['as'
=> 'admin']]);
//dashboard
Route::get('/dashboard',
[App\Http\Controllers\Api\Admin\DashboardController::class, 'index',
['as' => 'admin']]);
});
});
138
Silahkan teman-teman buka aplikasi Postman terlebih dahulu, setelah aplikasi berhasil
terbuka, silahkan masukkan URL berikut ini http://localhost:8000/api/admin/dashboard dan
untuk method-nya silahkan gunakan GET.
Setelah itu, silahkan klik tab Headers kemudian masukkan key dan value berikut ini di
dalamnya.
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, silahkan klik Send, jika menemukan error seperti gambar dibawah ini, maka kita
perlu melakukan sedikit konfigurasi tambahan di dalam config database yang ada di Laravel
untuk mengizinkan melakukan query GROUP BY.
139
'strict' => false,
Jika sudah, silahkan simpahan perubahan di atas dan kita coba lagi klik Send di dalam
aplikasi Postman, jika berhasil maka kita akan mendapatkan hasil seperti berikut ini :
140
Membuat Restful API CRUD Categories
Di materi sebelumnya kita telah belajar bagaimana cara membuat proses otentikasi di
Laravel menggunakan JWT, sekarang kita lanjutkan untuk belajar bagaimana cara membuat
Rest API untuk proses CRUD atau Create, Read, Update dan Delete data Categories.
Dan kali ini kita akan menggunakan salah satu fitur dari Laravel yang berfungsi untuk
mentransformasi dari Model ke dalam sebuah format JSON dengan lebih mudah dan cepat.
Fitur tersebut bernama API Resources.
Sebenernya kita selalu bisa menggunakan fitur dari Eloquent yaitu toJson untuk membuat
sebuah response di dalam Model, tapi dengan menggunakan API Resouces kita bisa
memberikan kontrol lebih dan sangat terperinci.
Didalam buku ini kita semua akan belajar menggunakan API Resources untuk menangani
dalam proses pembuatan response JSON agar lebih sederhana dan cepat. Disini kita juga
akan belajar bagaimana cara melakukan customisasi sebuah Resource agar dapat
menampilkan format JSON sesuai dengan yang kita harapkan.
Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan tentunya berada di dalam
project Laravel.
Jika perintah di atas berhasil, maka kita akan mendapatkan 1 file baru dengan nama
CategoryResource.php yang berada di dalam folder app/Http?Resources/.
141
Secara defualt file Resource tersebut sudah dapat digunakan untuk melakukan konversi dari
model menjadi format JSON yang cepat dan efisien, tetapi disini kita akan melakukan
sedikit perubahan agar format JSON yang dihasilkan bisa lebih baik lagi.
Silahkan buka file tersebut dan ubah kode-nya menjadi seperti berikut ini :
142
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}
Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.
143
public $status;
public $message;
Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :
$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.
Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
Jika berhasil, maka kita akan mendapatkan 1 file controller baru dengan nama
CategoryController.php yang berada di dalam folder
app/Http/Controllers/Api/Admin. Silahkan buka file tersebut dan ubah semua kode-
nya menjadi seperti berikut ini :
<?php
144
namespace App\Http\Controllers\Api\Admin;
use App\Models\Category;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
use App\Http\Resources\CategoryResource;
use Illuminate\Support\Facades\Validator;
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'image' => 'required|image|mimes:jpeg,jpg,png|max:2000',
'name' => 'required|unique:categories',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
145
//upload image
$image = $request->file('image');
$image->storeAs('public/categories', $image->hashName());
//create category
$category = Category::create([
'image'=> $image->hashName(),
'name' => $request->name,
'slug' => Str::slug($request->name, '-'),
]);
if($category) {
//return success with Api Resource
return new CategoryResource(true, 'Data Category Berhasil
Disimpan!', $category);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$category = Category::whereId($id)->first();
if($category) {
//return success with Api Resource
return new CategoryResource(true, 'Detail Data Category!',
$category);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
146
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Category $category)
{
$validator = Validator::make($request->all(), [
'name' =>
'required|unique:categories,name,'.$category->id,
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
if($category) {
//return success with Api Resource
return new CategoryResource(true, 'Data Category Berhasil
Diupdate!', $category);
}
147
return new CategoryResource(false, 'Data Category Gagal
Diupdate!', null);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy(Category $category)
{
//remove image
Storage::disk('local')->delete('public/categories/'.basename($category->
image));
if($category->delete()) {
//return success with Api Resource
return new CategoryResource(true, 'Data Category Berhasil
Dihapus!', null);
}
Dari perubahan kode di atas, kita melakukan import Model Category, ini dilakukan karena
kita akan menggunakan Model ini untuk melakukan beberapa hal, seperti menampilkan
data, melakuakn insert, update, dan lain-lain.
use App\Models\Category;
Setelah itu, kita juga import Helper Str dari Laravel, yang mana akan kita gunakan nanti
untuk melakukan generate slug untuk data category.
use Illuminate\Support\Str;
Slug biasanya digunakan untuk membuat sebuah URL yang SEO friendly. Contoh genearte
148
slug kurang lebih seperti berikut ini :
//hasil generate
echo $slug; // ouput --> laravel-5-framework
Jadi dari kata Laravel 5 Framework akan digenerate menjadi slug dengan diubah
menjadi huruf kecil dan dipisahkan dengan notasi -. Dan hasilnya kurang lebih seperti ini
laravel-5-framework.
Kemudian kita juga import Http Request dari Laravel, dimana akan digunakan untuk
menerima sebuah input yang dikirim melalui cookie, form, URL dan lain-lain.
use Illuminate\Http\Request;
Selanjutnya, tentu saja kita akan import CategoryResource yang sebelumnya sudah kita
buat, ini akan kita gunakan untuk mentransformasi Model menjadi sebuah format JSON
dengan lebih cepat dan efisien.
use App\Http\Resources\CategoryResource;
Karena kita akan membutuhkan sebuah validasi, maka kita perlu melakukan import juga,
disini kita import Facades Validator dari Laravel.
use Illuminate\Support\Facades\Validator;
Dan jika kita perhatikan dari penambahan kode yang ada di dalam controller
CategoryController, kita telah menambahkan 5 method baru, dimana method tersebut
yang akan kita gunakan untuk melakukan proses CRUD atau Create, Read, Update dan
Delete. Ke 5 method tersebut yaitu :
1. function index
149
2. function store
3. function show
4. function update
5. function destroy
function index
Method ini akan kita gunakan untuk menampilkan list data category, dan di dalam method
ini kita membuat kondisi di dalam Eloquent untuk proses pencarian data berdasarkan nama.
//get categories
$categories = Category::when(request()->q, function($categories) {
$categories = $categories->where('name', 'like', '%'. request()->q .
'%');
})->latest()->paginate(5);
Di atas kita membuat kondisi menggunakan method when, jadi jika ada sebuah request
dengan nama q, maka kita akan melakukan pencarian ke dalam table categories
berdasarkan attribute name yang sesuai dengan isi dari request q tersebut.
Kemudian kita urutkan datanya berdasarkan yang paling baru menggunakan method
latest() dan kita batasi data yang ditampilkan perhalaman 5 record menggunakan
method paginate().
Di atas bisa kita perhatikan, bahwa kita memberikan 3 parameter di dalamnya. Kurang lebih
seperti berikut ini :
150
function store
Method store merupakan fungsi yang akan digunakan untuk melakukan proses insert atau
memasukkan data ke dalam database, di dalam method ini sebelum melakukan proses
insert kita akan menambahkan sebuah validasi terlebih dahulu, fungsinya untuk memeriksa
apakah data yang akan di masukkan ke dalam database sudah sesuai dengan yang
diharapkan.
$validator = Validator::make($request->all(), [
'image' => 'required|image|mimes:jpeg,jpg,png|max:2000',
'name' => 'required|unique:categories',
]);
Dari penambahan kode validasi di atas, kurang lebih penjelasannya seperti berikut ini :
Jika validasi di atas tidak terpenuhi, maka kita akan mengembalikan sebuah response
dengan format JSON yang berisi informasi tentang error validasi.
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
Jika data yang akan di insert ke dalam database sudah sesuai dengan validasi di atas, maka
pertama kita akan melakukan proses upload gambar ke dalam server.
151
//upload image
$image = $request->file('image');
$image->storeAs('public/categories', $image->hashName());
Di atas, pertama kita membuat variable dengan nama $image yang isinya akan mengambil
sebuah request dengan jenis file dan bernama image, setelah itu akan dipindahkan ke
dalam folder /storage/app/public/categories menggunakan method storeAs dan
nama dari file tersebut akan di ubah menjadi random menggunakan method hashName().
Setelah gambar berhasil di upload di dalam server, selanjutnya kita melakukan proses insert
data ke dalam database menggunakan Eloquent, kurang lebih seperti berikut ini :
//create category
$category = Category::create([
'image'=> $image->hashName(),
'name' => $request->name,
'slug' => Str::slug($request->name, '-'),
]);
Dari kode proses insert data di atas, kuranng lebih penjelasannya seperti berikut ini :
Untuk memastikan proses insert data ke dalam database di atas berhasil atau tidak, maka
kita membuat sebuah kondisi dengan mengecek nilai dari variable $category.
Jika variable $category bernilai true atau proses insert berhasil dilakukan, maka kita akan
melakukan return ke dalam CategoryResource dengan memberikan 3 paramater, kurang
lebih seperti berikut ini :
152
if($category) {
//return success with Api Resource
return new CategoryResource(true, 'Data Category Berhasil
Disimpan!', $category);
}
Tapi, jika proses insert data ke dalam database gagal dilakukan, yang artinya variable
$category bernilai false maka kita akan melakukan return ke dalam
CategoryResource dengan 3 parameter yang memiliki status false.
function show
Method ini akan kita gunakan untuk menampilkan detail data category berdasarkan ID, di
dalam method ini kita melakukan proses pencarian data menggunakaan Elqouent.
$category = Category::whereId($id)->first();
Di atas, kita mencari data category menggunakan method whereId yang artinya mencari
berdasarkan ID. Dan data ID tersebut yaitu variable $id, yang mana akan di ambil dari
parameter yang ada di dalam method show.
Jika variable $category di atas bernilai true, yang artinya data ditemukan di dalam
database, maka kita akan melakukan return ke dalam CategoryRescoure dengan 3
parameter yang memiliki status true.
if($category) {
//return success with Api Resource
return new CategoryResource(true, 'Detail Data Category!',
$category);
}
Tapi, jika variable $category bernilai false, artinya data tidak ditemukan di dalam
database, maka kita akan melakuakn return ke dalam CategoryResource dengan
153
memberikan status false dan pesan Detail Data Category Tidak Ditemukan!.
function update
Method ini akan digunakan untuk melakukan proses update data ke dalam database, dan
sebelum data di update, kita harus melakukan proses validasi terlebih dahulu untuk
memastikan data yang akan diupdate sudah sesuai dengan yang diharapkan.
$validator = Validator::make($request->all(), [
'name' => 'required|unique:categories,name,'.$category->id,
]);
Dari kode validasi di atas, kurang lebih penjelasan detailnya seperti berikut ini :
Jika data yang akan diupdate belum sesuai dengan validasi yang di definisikan di atas, maka
akan mengembalikan sebuah error response validasi menggunakan kode berikut ini :
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
Tapi, jika data yang diupdate sudah sesuai dengan validasi di atas, maka kita akan
melakukan sebuah kondisi untuk mengecek apakah request dengan jenis file dan nama
image memiliki sebuah value. Jika iya, maka kita akan melakukan proses update data
beserta melakukan proses upload gambar baru.
154
//check image update
if ($request->file('image')) {
//...
Di mana di dalamnya pertama kita akan melakukan proses hapus gambar yang lama
terlebih dahulu.
Setelah gambar lama berhasil di hapus, sekarang kita lanjutkan untuk melakukan proses
upload gambar yang baru.
Dan jika gambar baru sudah berhasil terupload, maka selanjutnya kita tinggal melakukan
proses update data category ke dalam database dengan data baru.
Tapi, jika request dengan jenis file dan bernama image tersebut tidak memiliki value,
maka kita hanya melakukan proses update data category tanpa mengupdate attribute
image-nya.
155
//update category without image
$category->update([
'name' => $request->name,
'slug' => Str::slug($request->name, '-'),
]);
Setelah itu, untuk memastikan proses update data berhasil atau tidak, kita akan membuat
sebuah kondisi untuk mengecek nilai dari variable $category. Jika variable tersebut
bernilai true, maka kita akan melakukan return ke dalam CategoryResource dengan
status true.
if($category) {
//return success with Api Resource
return new CategoryResource(true, 'Data Category Berhasil Diupdate!',
$category);
}
Tapi, jika variable $category bernilai false, yang artinya data category gagal diupdate ke
dalam database, maka kita akan melakukan return ke dalam CategoryResource dengan
status false.
function destroy
Method ini akan digunakan untuk menghapus data dari database dan gambar yang terkait
akan dihapus juga dari server. Di dalam method ini pertama kita akan melakukan hapus
gambar terlebih dahulu dari server.
//remove image
Storage::disk('local')->delete('public/categories/'.basename($category->
image));
Kode di atas, digunakan untuk menghapus sebuah gambar dari category yang terkait di
dalam folder storage/app/public/categories.
156
Setelah proses hapus gambar berhasil, selanjutnya kita melakukan proses hapus data
category dari database menggunakan kode seperti berikut ini :
$category->delete()
Dan jika proses hapus data berhasil, maka kita akan melakukan return ke dalam
CategoryResource dengan memberikan status true.
Dan jika proses hapus data gagal dilakukan, maka akan melakukan return ke dalam
CategoryResource dengan status false.
//categories resource
Route::apiResource('/categories',
App\Http\Controllers\Api\Admin\CategoryController::class, ['except' =>
['create', 'edit'], 'as' => 'admin']);
Di atas, kita menambahkan except, yang artinya untuk mengecualikan route tertentu.
Karena jika menggunakan route dengan jenis Resource atau ApiRescource, maka semua
method akan dibuatkan sebuah route secara otomatis, karena kita tidak membutuhkan
route create dan edit, maka kita akan melakukan pengecualian di kedua route tersebut.
157
Untuk mengetahui apakah route yang kita tambahkan di atas berhasil atau tidak, kita bisa
menjalankan perintah berikut ini di dalam terminal/CMD :
Jika belum muncul seperti gambar di atas, silahkan jalankan perintah berikut ini di dalam
terminal/CMD :
Pertama kita akan lakukan uji coba untuk menampilkan list data category, silahkan buka
aplikasi Postman dan masukkan URL berikut ini http://localhost:8000/api/admin/categories
dan untuk method-nya silahkan pilih GET.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
158
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah silahkan klik Send dan jika berhasil maka kita akan mendapatkan response dalam
format JSON yang berisi informasi list data category.
Selanjutnya kita akan lakukan uji coba untuk melakukan proses insert data category ke
dalam database. Silahkan buka aplikasi Postman, kemudiaan masukkan URL berikut ini
http://localhost:8000/api/admin/categories dan untuk method-nya silahkan pilih POST.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
159
KEY VALUE
Content-Type application/json
Jika sudah, sekarang klik tab Body kemudian pilih form-data dan masukkan key dan value
berikut ini :
Setelah semua input sudah diisi, sekarang silahkan klik Send dan jika berhasil maka kita
akan mendapatkan sebuah response dalam format JSON yang berisi informasi data category
yang baru saja di insert.
Setelah berhasil melakukan proses insert data category ke dalam database, maka sekarang
kita lanjutkan untuk menampilkan data tersebut. Silahkan buka aplikasi Postman kemudian
masukkan URL berikut ini http://localhost:8000/api/admin/categories/1 dan untuk method-
nya silahkan pilih GET.
CATATAN ! : di atas kita akan mencoba menampilkan data category yang memiliki ID 1.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
160
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, sekarang silahkan klik Send dan jika berhasil maka kita akan mendapatkan
sebuah response dengan format JSON yang berisi informasi tentang detail data category
dengan ID 1.
Selanjutnya kita akan lakukan uji coba untuk proses update, disini untuk attribute image
sifatnya opsional, yaitu boleh di isi dan boleh dikosongkan.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
161
KEY VALUE
Jika sudah, sekarang klik tab Body kemudian pilih form-data dan masukkan key dan value
berikut ini :
Setelah semua sudah diisi sekarang silahkan klik Send dan jika berhasil maka kita akan
mendapatkan sebuah response dengan format JSON yang berisi informasi data category
yang diupdate.
Sekarang kita lanjutkan untuk proses uji coba hapus data dari database. Silahkan buka
aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/admin/categories/1 dan untuk method-nya silahkan pilih DELETE.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
162
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, siljhkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dengan format JSON yang berisi informasi data category yang sudah dihapus.
CATATAN ! : silahkan tambahkan data category yang banyak ke dalam database untuk
digunakan pada materi CRUD Product nanti.
163
Membuat Restful API CRUD Products
Setelah di materi sebelumnya kita belajar bagaimana cara membuat sebuah resource,
controller berserta method-nya dan cara uji coba Rest Api untuk data category di aplikasi
Postman, maka sekarang kita akan melakukan hal yang sama lagi tetapi dengan data yang
berbeda. Yups kita akan membuat sebuah CRUD Rest API untuk data product.
Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan tentunya harus berada di
dalam folder Laravel-nya.
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file baru yang telah
di generate dengan nama ProductResource.php yang berada di dalam folder
app/Http/Resources/.
Silahkan buka file tersebut dan kita akan melakukan kustomisasi agar response JSON yang
di hasilkan bisa sesuai dengan yang diharapkan, setelah berhasil dibuka kemudian ubah
semua kode-nya menjadi seperti berikut ini :
164
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}
Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.
165
public $status;
public $message;
Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :
$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.
Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan tentunya berada di dalam
project Laravel.
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller yang
telah di generate dengan nama ProductController.php yang berada di dalam folder
app/Http/Controllers/Api/Admin. Silahkan buka file tersebut dan ubah semua kode-
nya menjadi seperti berikut ini :
166
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Models\Product;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Resources\ProductResource;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'image' =>
'required|image|mimes:jpeg,jpg,png|max:2000',
'title' => 'required|unique:products',
'category_id' => 'required',
167
'description' => 'required',
'weight' => 'required',
'price' => 'required',
'stock' => 'required',
'discount' => 'required'
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
//upload image
$image = $request->file('image');
$image->storeAs('public/products', $image->hashName());
//create product
$product = Product::create([
'image' => $image->hashName(),
'title' => $request->title,
'slug' => Str::slug($request->title, '-'),
'category_id' => $request->category_id,
'user_id' => auth()->guard('api_admin')->user()->id,
'description' => $request->description,
'weight' => $request->weight,
'price' => $request->price,
'stock' => $request->stock,
'discount' => $request->discount
]);
if($product) {
//return success with Api Resource
return new ProductResource(true, 'Data Product Berhasil
Disimpan!', $product);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
168
public function show($id)
{
$product = Product::whereId($id)->first();
if($product) {
//return success with Api Resource
return new ProductResource(true, 'Detail Data Product!',
$product);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Product $product)
{
$validator = Validator::make($request->all(), [
'title' =>
'required|unique:products,title,'.$product->id,
'category_id' => 'required',
'description' => 'required',
'weight' => 'required',
'price' => 'required',
'stock' => 'required',
'discount' => 'required'
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
169
$image->storeAs('public/products', $image->hashName());
if($product) {
//return success with Api Resource
return new ProductResource(true, 'Data Product Berhasil
Diupdate!', $product);
}
/**
* Remove the specified resource from storage.
*
170
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy(Product $product)
{
//remove image
Storage::disk('local')->delete('public/products/'.basename($product->ima
ge));
if($product->delete()) {
//return success with Api Resource
return new ProductResource(true, 'Data Product Berhasil
Dihapus!', null);
}
Dari perubahan kode di atas, pertama kita melakukan import Model Product terlebih
dahulu, karena kita akan gunakan untuk proses yang berhubungan dengan database,
seperti menampilkan, insert, update dan delete data.
use App\Models\Product;
Setelah itu, kita juga import Helper Str dari Laravel, yang mana akan kita gunakan nanti
untuk melakukan generate slug untuk data product.
use Illuminate\Support\Str;
kemudian kita juga import Http Request dari Laravel, dimana akan digunakan untuk
menerima sebuah input yang dikirim melalui cookie, form, URL dan lain-lain.
use Illuminate\Http\Request;
Dan kita import ProductResource yang sebelumnya sudah kita buat di atas, Resource ini
171
akan digunakan untuk mentransformasi Model kita ke dalam format JSON dengan lebih baik
dan cepat.
use App\Http\Resources\PostResource;
Karena kita akan melakukan upload gambar, maka kita juga akan import Facades Storage
dari Laravel.
use Illuminate\Support\Facades\Storage;
kita juga akan membutuhkan sebuah validasi, maka kita perlu melakukan import juga, disini
kita import Facades Validator dari Laravel.
use Illuminate\Support\Facades\Validator;
1. function index
2. function store
3. function show
4. function update
5. function destroy
function index
Method ini akan kita gunakan untuk menampilkan list data product dari database. Dan di
dalam method ini kita melakukan beberapa kondisi, kurang lebih seperti berikut ini :
//get products
$products = Product::with('category')->when(request()->q,
function($products) {
$products = $products->where('title', 'like', '%'. request()->q
. '%');
})->latest()->paginate(5);
Kode di atas merupakan Eloquent di Laravel untuk mendapatkan list data menggunakan
Model. Kemudian kita juga menambahkan eager loaded menggunakan method with,
172
dimana method tersebut akan memanggil relasi yang bernama category di dalam Model
Product. Dan jika diterjemahkan seperti melakukan sebuah query join dari 2 table yang
berbeda.
Setelah itu, kita membuat kondisi lagi untuk proses pencarian, disini kita menggunakan
method when yang artinya jika ada sebuah request bernama q, maka akan melakukan
proses pencarian data berdasarkan title yang sesuai dengan isi dari request q tersebut.
Setelah itu, kita urutkan data yang akan ditampilkan berdasarkan yang paling terbaru, disini
kita menggunakan method latest(). Dan data yang akan ditampilkan akan dibatas
perhalaman 5 record menggunakan method paginate().
Kemudian, agar variable $products di atas dapat menjadi sebuah format JSON, maka kita
perlu menkonversinya ke dalam ProductResource, kurang lebih seperti berikut ini :
function store
Method ini yang akan digunakan untuk melakukan proses insert data ke dalam database
dan juga melakukan proses upload gambar ke dalam server. Sebelum data disimpan,
pertama-tama kita akan lakukan validasi terlebih dahulu agar data yang akan disimpan
sudah benar-benar sesuai dengan yang diharapkan.
$validator = Validator::make($request->all(), [
'image' => 'required|image|mimes:jpeg,jpg,png|max:2000',
'title' => 'required|unique:products',
'category_id' => 'required',
'description' => 'required',
'weight' => 'required',
'price' => 'required',
'stock' => 'required',
'discount' => 'required'
]);
Dari definisi validasi di atas, kurang lebih penjelasan detailnya seperti berikut ini :
173
KEY VALIDASI KETERANGAN
Jika data yang akan disimpan tidak sesuai dengan validasi di atas, maka akan
mengembalikan sebuah response error validasi menggunakan kode berikut ini :
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
Tapi, jika data yang akan di insert sudah sesuai dengan yang diharapkan, maka pertama
akan melakukan proses upload gambar ke dalam server menggunakan kode berikut ini :
//upload image
$image = $request->file('image');
$image->storeAs('public/products', $image->hashName());
Di atas, pertama kita membuat variable dengan nama $image yang isinya akan mengambil
sebuah request dengan jenis file dan bernama image, setelah itu akan dipindahkan ke
dalam folder /storage/app/public/categories menggunakan method storeAs dan
nama dari file tersebut akan di ubah menjadi random menggunakan method hashName().
174
Setelah proses upload gambar selesai, sekarang akan dilanjutkan untuk proses insert data
ke dalam database menggunakan Eloquent, kurang lebih seperti berikut ini :
//create product
$product = Product::create([
'image' => $image->hashName(),
'title' => $request->title,
'slug' => Str::slug($request->title, '-'),
'category_id' => $request->category_id,
'user_id' => auth()->guard('api_user')->user()->id,
'description' => $request->description,
'weight' => $request->weight,
'price' => $request->price,
'stock' => $request->stock,
'discount' => $request->discount
]);
Dari kode proses insert di atas, kurang lebih penjelasannya seperti berikut ini :
175
ATTRIBUTE VALUE KETERANGAN
Setelah itu, kita membuat kondisi untuk memastikan apakah proses insert data di atas
berhasil atau tidak. Disini kita mengecek jika variable $product bernilai true, maka akan
melakukan return ke dalam ProductResource dengan status true.
if($product) {
//return success with Api Resource
return new ProductResource(true, 'Data Product Berhasil Disimpan!',
$product);
}
Tapi jika variable $product bernilai false, artinya proses insert data gagal dilakukaan,
maka akan melakukan return ke dalam ProductResource dengan status false.
function show
Method ini akan digunakan untuk menampilkan informasi detail data product berdasarkan
ID, dimana di dalam method ini kita melakukan pencarian data menggunakan ELoquent
dengan parameter ID yang di dapatkan dari URL.
$product = Product::whereId($id)->first();
Jika variable $product bernilai true, artinya data ditemukan di dalam database dan kita
176
akan melakukan return ke dalam ProductResource dengan status true.
if($product) {
//return success with Api Resource
return new ProductResource(true, 'Detail Data Product!', $product);
}
Tapi, jika variable tersebut bernilai false, yang artinya data tidak ditemukan di dalam
database, maka kita akan lakukan return ke dalam ProductResource dengan status
false.
function update
Method ini akan digunakan untuk melakukaan proses update data ke dalam database dan
untuk attribute image ini sifatnya opsional, yang artinya bisa diubah dan juga tidak.
Tentu saja kita akan menambahkan sebuah validasi terlebih dahulu untuk memeriksa data
yang akan di update ke dalam database, apakah sudah sesuai dengan apa yang diharapkan.
$validator = Validator::make($request->all(), [
'title' => 'required|unique:products,title,'.$product->id,
'category_id' => 'required',
'description' => 'required',
'weight' => 'required',
'price' => 'required',
'stock' => 'required',
'discount' => 'required'
]);
Dari penambahan kode validasi di atas, kurang lebih penjelasan detailnya seperti berikut ini
:
177
KEY VALIDASI KETERANGAN
Jika data yang akan diupdate belum sesuai dengan ketentuan validasi di atas, maka kita
akan melakukan return atau mengembalikan ke sebuah format JSON dengan informasi
validasi.
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
Tapi jika validasi sudah sesuai dengan yang di harapkan, maka selanjutnya kita akan
membuat kondisi lagi untuk pengecekan request file gambar. Jika request file dengam nama
image memiliki value, maka kita akan melakukan update data post dengan gambar baru.
//...
Di dalamnya kita melakukan remove atau mengahpus data gambar lama di dalam server.
178
//remove old image
Storage::disk('local')->delete('public/products/'.basename($product->ima
ge));
Setelah gambar lama berhasil dihapus, selanjutnya kita melakukan proses upload gambar
yang baru.
Dan jika gambar sudah berhasil terupload di dalam server, maka selanjutnya kita akan
melakukan proses update data product ke dalam database.
Kemudian jika request file dengan nama image tidak memiliki value, maka kita cukup
melakukan update data product tanpa melakukan update attribute image.
179
//update product without image
$product->update([
'title' => $request->title,
'slug' => Str::slug($request->title, '-'),
'category_id' => $request->category_id,
'user_id' => auth()->guard('api_admin')->user()->id,
'description' => $request->description,
'weight' => $request->weight,
'price' => $request->price,
'stock' => $request->stock,
'discount' => $request->discount
]);
Setelah itu, untuk memastikan proses update data berhasil atau tidak, kita akan membuat
sebuah kondisi untuk mengecek nilai dari variable $product. Jika variable tersebut bernilai
true, maka kita akan melakukan return ke dalam ProductResource dengan status true.
if($product) {
//return success with Api Resource
return new ProductResource(true, 'Data Product Berhasil Diupdate!',
$product);
}
Tapi, jika variable $product bernilai false, yang artinya data product gagal diupdate ke
dalam database, maka kita akan melakukan return ke dalam ProductResource dengan
status false.
function destroy
Method ini akan digunakan untuk melakukan proses hapus data di dalam database sesuai
dengan ID yang dikirimkan dan disini kita juga akan melakukan proses hapus gambar yang
terkait di dalam server.
180
//remove image
Storage::disk('local')->delete('public/products/'.basename($product->ima
ge));
Kode di atas, digunakan untuk menghapus gambar yang terkait dengan data product, dan
untuk lokasi gambar tersebut berada di dalam folder
/storage/app/public/categories/.
Setelah proses hapus gambar di dalam server berhasil, selanjutnya kit melakukan proses
hapus data product di dalam database, kurang lebih seperti ini :
$product->delete()
Jika proses hapus data berhasil, maka kita akan lakukan return atau mengembalikan ke
dalam ProductResource dengan status true.
Tapi, jika proses hapus data di dalam database gagal dilakukan, maka kita juga akan
melakukan return atau mengembalikan ke dalam ProductResource dengan status false.
Silahkan tambahkan route di bawah ini di dalam file routes/api.php, di dalam prefix
admin dan group middleware auth:api_admin, lebih tepatnya di bawah route
/categories.
181
//products resource
Route::apiResource('/products',
App\Http\Controllers\Api\Admin\ProductController::class, ['except' =>
['create', 'edit'], 'as' => 'admin']);
Untuk memastikan apakah route yang sudah kita tambahkan berhasil, kita bisa melakukan
verifikasi dengan menjalankan perintah berikut ini di dalam terminal/CMD :
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :
182
Langkah 4 - Uji Coba Rest API Product
Sebelum memulai uji coba Rest API products, jangan lupa menambahkan data baru untuk
categories, berikut ini contoh data categories yang saya buat, silahkan disesuaikan dengan
keinginan masing-masing.
Untuk menambahkan data tersebut, silahkan bisa melalui aplikasi Postman, seperti di
langkah-langkah sebelumnya. Untuk datanya bebas, jadi silahkan disesuaikan dengan
keinginan masing-masing.
Pertama kita akan lakukan uji coba untuk mendapatkan list data products dari database.
Silahkan buka aplikasi Postman kemudian masukkan URL berikut ini
http://localhost:8000/api/admin/products dan untuk method-nya silahkan pilih GET.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data products.
183
Uji Coba Method Store
Sekarang kita lanjutkan untuk melakukan uji coba proses insert data products ke dalam
database. Silahkan buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/admin/products dan untuk method-nya silahkan pilih POST.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Setelah itu, silahkan klik tab Body dan pilih form-data kemudian masukkan key dan value
seperti berikut ini :
184
KEY TYPE VALUE
Dari key dan value di atas, saya memberikan contoh seperti berikut ini :
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data product yang baru saja di insert.
185
Uji Coba Method Show
Sekarang kita lanjutkan untuk proses uji coba menampilkan data product yang ada di dalam
database, dan di dalam contoh kali ini kita akan menampilkan data product yang memiliki ID
1.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan response yang
berisi informasi detail data product dengan ID 1.
186
Uji Coba Update
Sekarang kita lanjutkan untuk proses uji coba update data product ke dalam database. Dan
disini kita akan mencoba melakukan update data product yang memiliki ID 1. Silahkan buka
aplikasi Postman dan masukkan URL berikut ini http://localhost:8000/api/admin/products/1
dan untuk method-nya silahkan pilih POST.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Setalah itu, silahkan masuk ke tab Body dan pilih form-data dan masukkan key dan value
seperti berikut ini :
187
KEY TYPE VALUE
Dari key dan value di atas, saya memberikan contoh seperti berikut ini :
Jika sudah silahkan klik Send dan jika berhasil maka kita akan mendapatkan response dalam
format JSON yang berisi informasi data product yang baru saja diupdate.
188
Uji Coba Destroy
Sekarang kita lanjutkan untuk proses uji coba menghapus data product dari database. Dan
disini kita juga akan menggunakan data product yang memiliki ID 1.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data product yang telah dihapus.
189
190
Membuat Restful API Invoices Admin
Setalah berhasil membuat Rest API untuk CRUD data products, sekarang kita akan lanjutkan
untuk membuat Rest API untuk menampilkan data invoices, disini kita tidak akan membuat
proses CRUD seperti sebelumnya, karena data invoice ini akan digenerate oleh customer
nantinya dan di halaman admin tinggal menampilkan data tersebut.
Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan tentu saja harus berada di
dalam project Laravel.
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file baru dengan
nama InvoiceResource.php yang berada di dalam folder app/Http/Resources.
Silahkan buka file tersebut, karena kita akan melakukan modifikasi agar response JSON
yang dihasilkan bisa sesuai dengan yang diharapkan. Setelah berhasil dibuka, silahkan ubah
semua kode-nya menjadi seperti berikut ini :
191
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}
Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.
192
public $status;
public $message;
Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :
$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.
Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
Jika perintah berhasil dijalankan, maka kita akan mendapatkan 1 file baru dengan nama
InvoiceController.php yang berada di dalam folder
app/Http/Controllers/Api/Admin. Silahkan buka file tersebut dan ubah semua kode-
nya menjadi seperti berikut ini :
193
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Models\Invoice;
use App\Http\Controllers\Controller;
use App\Http\Resources\InvoiceResource;
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$invoice = Invoice::with('orders.product', 'customer', 'city',
'province')->whereId($id)->first();
if($invoice) {
//return success with Api Resource
return new InvoiceResource(true, 'Detail Data Invoice!',
$invoice);
}
194
return new InvoiceResource(false, 'Detail Data Invoice Tidak
Ditemukan!', null);
}
}
Dari perubahan kode di atas, pertama kita melakukan import Model Invoice terlebih
dahulu, karena kita akan menggunakan model tersebut untuk mendapatkan data dari
database.
use App\Models\Invoice;
Setelah itu, kita juga melakukan import untuk InvoiceResource, file ini yang nantinya
akan digunakan untuk mentransformasi model ke dalam format JSON menjadi sangat
mudah dan baik.
use App\Http\Resources\InvoiceResource;
1. function index
2. function show
funtion index
Method ini akan digunakan untuk menampilkan list data invoice dari dalam database, disini
kita menggunakan Eloquent untuk proses getting datanya.
$invoices = Invoice::with('customer')->when(request()->q,
function($invoices) {
$invoices = $invoices->where('invoice', 'like', '%'. request()->q .
'%');
})->latest()->paginate(5);
Kode di atas merupakan Eloquent di Laravel untuk mendapatkan list data menggunakan
Model. Setelah itu, kita membuat kondisi lagi untuk proses pencarian, disini kita
menggunakan method when yang artinya jika ada sebuah request bernama q, maka akan
melakukan proses pencarian data berdasarkan attribute invoice yang sesuai dengan isi
195
dari request q tersebut.
Setelah itu, kita urutkan data yang akan ditampilkan berdasarkan yang paling terbaru, disini
kita menggunakan method latest(). Dan data yang akan ditampilkan akan dibatas
perhalaman 5 record menggunakan method paginate().
Kemudian, agar variable $invoices di atas dapat menjadi sebuah format JSON, maka kita
perlu menkonversinya ke dalam InvoiceResource, kurang lebih seperti berikut ini :
function show
Method ini akan digunakan untuk mendapatkaan detail data invoice dari dalam database
berdasarkan ID yang di dapatkaan dari parameter.
Kode diatas digunakan untuk mencari data invoice berdasarkan ID, dan kita juga memanggil
beberapa relasi menggunakan eager loaded yaitu method with, yang mana di dalamnya
ada beberapa relasi, diantaranya adalah :
Relasi tersebut merupakan pendefinisian sebuah method yang sebelumnya sudah kita buat
di dalam materi Eloquent Relationships.
Setelah itu, kita membuat kondisi untuk memastikan apakah variable $invoice tersebut
berilai true atau false, jika bernilai true, artinya data ditemukan di dalam database, dan
kita akan melakukan return ke dalam InvoiceResource dengan status true.
196
if($invoice) {
//return success with Api Resource
return new InvoiceResource(true, 'Detail Data Invoice!', $invoice);
}
Tapi, jika variable tersebut bernilai false, artinya data invoice tidak ditemukan di dalam
database, maka kita akan melakukan return ke dalam InvoiceResource dengan status
false.
Silahkan tambahkan route di bawah ini di dalam file routes/api.php, di dalam prefix
admin dan group middleware auth:api_admin, lebih tepatnya di bawah route
/products.
//invoices resource
Route::apiResource('/invoices',
App\Http\Controllers\Api\Admin\InvoiceController::class, ['except' =>
['create', 'store', 'edit', 'update', 'destroy'], 'as' => 'admin']);
Untuk memastikan apakah route yang sudah kita tambahkan berhasil, kita bisa melakukan
verifikasi dengan menjalankan perintah berikut ini di dalam terminal/CMD :
197
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :
Pertama kita akan lakukan uji coba untuk mendapatkan list data invoices dari database.
Silahkan buka aplikasi Postman kemudian masukkan URL berikut ini
http://localhost:8000/api/admin/invoices dan untuk method-nya silahkan pilih GET.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
198
KEY VALUE
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data invoices.
199
Membuat Restful API Customers
Di materi kali ini kita semua akan belajar bagaimana cara membuat Rest API untuk
menampilkan list data customer yang sudah register di dalam website toko online kita.
Pertama-tama tentu saja kita akan membuat Resource terlebih dahulu, karena dengan
menggunakan fitur ini proses membuat JSON sangat mudah dan cepat.
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file baru dengan
nama CustomerResource.php yang berada di dalam folder app/Http/Resources.
Silahkan buka file tersebut dan ubah semua kode-nya menjadi seperti berikut ini :
200
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}
Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.
201
public $status;
public $message;
Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :
$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.
Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan pastikan sudah berada di
dalam project Laravel-nya.
Jika berhasil, maka kita akan mendapatkan 1 file baru dengan nama
CustomerController.php yang berada di dalam folder
app/Http/Controllers/Api/Admin. Silahkan buka file tersebut dan ubah kode-nya
menjadi seperti berikuit ini :
202
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Models\Customer;
use App\Http\Controllers\Controller;
use App\Http\Resources\CustomerResource;
Dari perubahan kode di atas, pertama-tama kita melakukan import Model Customer
terlebih dahulu, karena kita akan menggunakan model untuk mendapatkan data dari
database.
use App\Models\Customer;
Kemudian, kita juga melakukan import ResourceCustomer, ini akan digunakan untuk
melakukan transformasi atau konversi data dari model menjadi format JSON dengan sangat
mudah dan cepat.
use App\Http\Resources\CustomerResource;
203
nama index, di dalam method tersebut kita melakukan getting data dari database
menggunakan Model, kurang lebih seperti berikut ini :
Di atas, kita melakukan get data ke dalam database mengunakan Model dan di dalamnya
kita membuat kondisi untuk proses pencarian menggunakan method when. Jika ada sebuah
request dengan nama q, maka akan melakuakn pencarian berdasarkan attribute name dan
di cocokan dengan isi dari request q tersebut.
Setelah itu, kita urutkan data yang akan ditampilkan berdasarkan yang paling terbaru, disini
kita menggunakan method latest(). Dan data yang akan ditampilkan akan dibatas
perhalaman 5 record menggunakan method paginate().
Kemudian, agar variable $customers di atas dapat menjadi sebuah format JSON, maka
kita perlu menkonversinya ke dalam CustomerResource, kurang lebih seperti berikut ini :
Silahkan tambahkan route di bawah ini di dalam file routes/api.php, di dalam prefix
admin dan group middleware auth:api_admin, lebih tepatnya di bawah route
/invoices.
//customer
Route::get('/customers',
[App\Http\Controllers\Api\Admin\CustomerController::class, 'index',
['as' => 'admin']]);
Untuk memastikan apakah route yang sudah kita tambahkan berhasil, kita bisa melakukan
verifikasi dengan menjalankan perintah berikut ini di dalam terminal/CMD :
204
php artisan route:list
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :
Pertama kita akan lakukan uji coba untuk mendapatkan list data customers dari database.
Silahkan buka aplikasi Postman kemudian masukkan URL berikut ini
http://localhost:8000/api/admin/customers dan untuk method-nya silahkan pilih GET.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
205
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data customers.
206
Membuat Restful API CRUD Sliders
Kita lanjutkan untuk membuat Rest API untuk proses CRUD data sliders, karena data slider
maka kita otomatis akan melakukan proses upload gambar ke dalam server. Dan di dalam
controller nanti kita akan buat beberapa method saja, yaitu index, store dan destroy,
karena akan disesuaikan dengan UI di halaman website nanti.
Jika perintah di atas berhasil di jalankan, maka kita akan mendapatkan 1 file baru dengan
nama SliderResource.php yang berada di dalam folder app/Http/Resources.
Silahkan buka file tersebut dan ubah kode-nya menjadi seperti berikut ini :
207
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}
Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.
208
public $status;
public $message;
Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :
$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.
Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama SliderController.php yang berada di dalam folder
app/Http/Controllers/Api/Admin. Silahkan buka file tersebut dan ubah kode-nya
menjadi seperti berikut ini :
<?php
namespace App\Http\Controllers\Api\Admin;
209
use App\Models\Slider;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Resources\SliderResource;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'image' => 'required|image|mimes:jpeg,jpg,png|max:2000',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
//upload image
$image = $request->file('image');
$image->storeAs('public/sliders', $image->hashName());
//create slider
$slider = Slider::create([
'image'=> $image->hashName(),
'link' => $request->link,
210
]);
if($slider) {
//return success with Api Resource
return new SliderResource(true, 'Data Slider Berhasil
Disimpan!', $slider);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy(Slider $slider)
{
//remove image
Storage::disk('local')->delete('public/sliders/'.basename($slider->image
));
if($slider->delete()) {
//return success with Api Resource
return new SliderResource(true, 'Data Slider Berhasil
Dihapus!', null);
}
Dari perubahan kode di atas, pertama kita melakukan import Model Slider terlebih dahulu.
use App\Models\Slider;
kemudian kita juga import Http Request dari Laravel, dimana akan digunakan untuk
211
menerima sebuah input yang dikirim melalui cookie, form, URL dan lain-lain.
use Illuminate\Http\Request;
Dan kita juga import SliderResource yang sebelumnya sudah kita buat di atas, dan
fungsinya yang akan mentransformasi dari Model menjadi format JSON.
use App\Http\Resources\SliderResource;
Karena kita akan melakukan upload gambar, maka kita juga akan import Facades Storage
dari Laravel.
use Illuminate\Support\Facades\Storage;
kita juga akan membutuhkan sebuah validasi, maka kita perlu melakukan import juga, disini
kita import Facades Validator dari Laravel.
use Illuminate\Support\Facades\Validator;
1. function index
2. function store
3. function destroy
function index
Method ini akan kita gunakan untuk mendapatkan data slider dari database. Dan disni kita
tampilkan data-nya dari yang paling terbaru menggunakan method latest() dan akan
dibatasi data yang di tampilkan perhalaman menggunakan method paginate.
$sliders = Slider::latest()->paginate(5);
Setelah itu, agar varibale $sliders di atas dapat berubah menjadi format JSON, maka kita
perlu melakukan parsing variable tersebut ke dalam SliderResource.
212
//return with Api Resource
return new SliderResource(true, 'List Data Sliders', $sliders);
function store
Method ini akan digunakan untuk proses insert data ke dalam database, dan disini kita juga
akan melakukan proses upload gambar ke dalam server. Sebelum semuanya dijalankan, kita
akan membuat sebuah validasi terlebih dahulu untuk memastikan data yang akan disimpan
sudah sesuai dengan yang diharapkan.
$validator = Validator::make($request->all(), [
'image' => 'required|image|mimes:jpeg,jpg,png|max:2000',
]);
Dari kode validasi di atas, kurang lebih penjelasan detailnya seperti berikut ini :
Jika data yang di insert tidak sesuai dengan validasi di atas, maka akan mengembalikan
sebuah response dengan format JSON yang berisi informasi error validasi, berikut ini kode
yang digunakan.
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
Ketika data yang di masukkan sudah sesuai, maka pertama yang akan dijalankan adalah
proses upload gambar ke dalam server, kurang lebih seperti berikut ini :
213
//upload image
$image = $request->file('image');
$image->storeAs('public/sliders', $image->hashName());
Di atas, pertama kita membuat variable dengan nama $image yang isinya akan mengambil
sebuah request dengan jenis file dan bernama image, setelah itu akan dipindahkan ke
dalam folder /storage/app/public/sliders menggunakan method storeAs dan nama
dari file tersebut akan di ubah menjadi random menggunakan method hashName().
Setelah gambar berhasil di upload di dalam server, langkah selanjutnya adalah melakukan
insert data ke dalam database, kurang lebih seperti berikut ini :
//create slider
$slider = Slider::create([
'image'=> $image->hashName(),
'link' => $request->link,
]);
Dari kode proses insert data di atas, kuranng lebih penjelasannya seperti berikut ini :
Untuk memastikan proses insert dan upload berjalan dengan benar atau berhasil, kita akan
membuat sebuah kondisi untuk mengecek nilai dari variable $slider.
Jika variable $slider bernilai true, yang artinya data berhasil disimpan, maka akan
melakukan return ke dalam SliderResource dengan status true.
if($slider) {
//return success with Api Resource
return new SliderResource(true, 'Data Slider Berhasil Disimpan!',
$slider);
}
214
Tapi, jika variable tersebut bernilai false, yang artinya data gagal disimpan, maka akan
melakukan return ke dalam SliderResource dengan status false.
function destroy
Method ini akan digunakan untuk menghapus data dari database dan gambar yang terkait
akan dihapus juga dari server. Di dalam method ini pertama kita akan melakukan hapus
gambar terlebih dahulu dari server.
//remove image
Storage::disk('local')->delete('public/sliders/'.basename($slider->image
));
Kode di atas, digunakan untuk menghapus sebuah gambar dari slider yang terkait di dalam
folder storage/app/public/sliders.
Setelah proses hapus gambar berhasil, selanjutnya kita melakukan proses hapus data slider
dari database menggunakan kode seperti berikut ini :
$slider->delete()
Dan jika proses hapus data berhasil, maka kita akan melakukan return ke dalam
SliderResource dengan memberikan status true.
Dan jika proses hapus data gagal dilakukan, maka akan melakukan return ke dalam
SliderResource dengan status false.
215
//return failed with Api Resource
return new SliderResource(false, 'Data Slider Gagal Dihapus!', null);
Silahkan tambahkan route di bawah ini di dalam file routes/api.php, di dalam prefix
admin dan group middleware auth:api_admin, lebih tepatnya di bawah route
/customers.
//sliders resource
Route::apiResource('/sliders',
App\Http\Controllers\Api\Admin\SliderController::class, ['except' =>
['create', 'show', 'edit', 'update'], 'as' => 'admin']);
Untuk memastikan apakah route yang sudah kita tambahkan berhasil, kita bisa melakukan
verifikasi dengan menjalankan perintah berikut ini di dalam terminal/CMD :
216
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :
Pertama kita akan melakukan proses uji coba untuk method index, silahkan buka aplikasi
Postman kemudian masukkan URL berikut ini http://localhost:8000/api/admin/sliders dan
untuk method-nya silahkan pilih GET.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data sliders.
217
Uji Coba Store
Sekarang kita lanjutkan untuk melakukan proses uji coba insert data beserta upload
gambar. Silahkan buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/admin/sliders dan untuk method-nya silahkan pilih POST.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Setelah itu silahkan klik tab Body dan pilih form-data kemudian masukkan key dan value
berikut ini :
218
KEY TYPE VALUE
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data slider yang baru saja di insert.
Sekarang kita lanjuitkan untuk proses uji coba hapus data dari database dan sekalian
menghapus file gambar dari server. Silahkan buka aplikasi Postman dan masukkan URL
berikut ini http://localhost:8000/api/admin/sliders/1 dan untuk method-nya silahkan pilih
DELETE.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data slider yang berhasil dihapus.
219
220
Membuat Restful API CRUD Users
Pada tahap kali ini kita akan belajar bagaimana cara membuat Rest API untuk data users.
Users disini merupakan admin atau pengguna yang dapat mengelola konten website, seperti
menambah category, product, dan lain-lain.
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file baru dengan
nama UserResource.php yang berada di dalam folder app/Http/Resources. Silahkan
buka file tersebut dan ubah semua kode-nya menjadi seperti berikut ini :
221
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}
Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.
222
public $status;
public $message;
Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :
$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.
Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama UserController.php yang berada di dalam folder
app/Http/Controllers/Api/Admin. Silahkan buka file tersebut dan ubah semua kode-
nya menjadi seperti berikut ini :
<?php
223
namespace App\Http\Controllers\Api\Admin;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Resources\UserResource;
use Illuminate\Support\Facades\Validator;
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|unique:users',
'password' => 'required|confirmed'
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
//create user
$user = User::create([
224
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password)
]);
if($user) {
//return success with Api Resource
return new UserResource(true, 'Data User Berhasil
Disimpan!', $user);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$user = User::whereId($id)->first();
if($user) {
//return success with Api Resource
return new UserResource(true, 'Detail Data User!', $user);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, User $user)
{
$validator = Validator::make($request->all(), [
'name' => 'required',
225
'email' => 'required|unique:users,email,'.$user->id,
'password' => 'confirmed'
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
if($request->password == "") {
if($user) {
//return success with Api Resource
return new UserResource(true, 'Data User Berhasil
Diupdate!', $user);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy(User $user)
{
if($user->delete()) {
//return success with Api Resource
return new UserResource(true, 'Data User Berhasil Dihapus!',
226
null);
}
Dari perubahan file controller di atas, pertama kita melakukan import untuk Model User.
use App\Models\User;
kemudian kita juga import Http Request dari Laravel, dimana akan digunakan untuk
menerima sebuah input yang dikirim melalui cookie, form, URL dan lain-lain.
use Illuminate\Http\Request;
Dan kita juga import UserResource yang sebelumnya sudah kita buat di atas, dan
fungsinya yang akan mentransformasi dari Model menjadi format JSON.
use App\Http\Resources\UserResource;
kita juga akan membutuhkan sebuah validasi, maka kita perlu melakukan import juga, disini
kita import Facades Validator dari Laravel.
use Illuminate\Support\Facades\Validator;
1. function index
2. function store
3. function show
4. function update
5. function destroy
227
function index
Method ini akan digunakan untuk menampilkan list data user dari database. Didalam
method ini kita juga membuat sebuah kondisi untuk proses pencarian data.
//get users
$users = User::when(request()->q, function($users) {
$users = $users->where('name', 'like', '%'. request()->q . '%');
})->latest()->paginate(5);
Di atas, kita membuat sebuah kondisi pencarian menggunakan method when, jadi ketika
ada sebuah request yang bernama q, maka kita akan melakukan pencarian ke dalam table
users berdasarkan attribute name yang sesuai dengan isi dari request q tersebut.
Setelah itu, kita urutkan data yang ditampilkan berdasarkan data yang paling terbaru,
menggunakan method latest() dan kita batasi perhalaman untuk menampilkan datanya
menggunakan method paginate().
Setelah itu, agar variable $users bisa diubah ke format JSON, maka kita perlu
menggunakan UserResource, kurang lebih seperti berikut ini :
function store
Method ini akan digunakan untuk menangani proses insert data ke dalam database,
sebelum data yang dikirim disimpan ke dalam database, maka akan kita validasi terlebih
dahulu, tujuannya agar data yang di insert bisa sesuai dengan yang diharapkan.
$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|unique:users',
'password' => 'required|confirmed'
]);
Dari kode validasi di atas, kurang lebih penjelasan detailnya seperti berikut ini :
228
KEY VALIDASI KETERANGAN
unique:users data tidak boleh ada yang sama di dalam table users
Jika data yang di kirim belum sesuai dengan ketentuan validasi di atas, maka akan
mengembalikan sebuah response JSON yang berisi informasi validasi, kurang lebih seperti
berikut ini :
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
Akan tetapi, jika data yang akan disimpan sudah sesuai dengan yang diharapkan, maka
akan melakukan insert ke dalam database menggunakan Eloquent, kurang lebih seperti
berikut ini :
//create user
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password)
]);
Dari kode proses insert di atas, kurang lebih penjelasan detailnya seperti berikut ini :
229
ATTRIBUTE VALUE KETERANGAN
Untuk memastikan proses insert berhasil atau tidak, maka kita akan membuat sebuah
kondisi untuk memeriksa hasil dari variable $user, jika bernilai true maka proses insert
berhasil dilakukan, tapi jika hasilnya false maka proses insert gagal dilakukan.
Jika veriable $user bernilai true, maka kita akan mengembalikan ke dalam
UserResource dengan memberikan status true, kurang lebih seperti ini :
if($user) {
//return success with Api Resource
return new UserResource(true, 'Data User Berhasil Disimpan!', $user);
}
Tapi, jika variable tersebut bernilai false, maka akan mengembalikan ke dalam
UserResource dengan status false.
function show
Method ini akan digunakan untuk menampilkan detail data user berdasarkan ID yang di
dapatkan melalui paremeter URL.
$user = User::whereId($id)->first();
Di atas kita melakukan get data user menggunakan Model User dimana data tersebut di
cari berdasarkan parameter $id. Jika data user tersebut di dapatkan, maka kita akan
melakukan return menggunakan UserResource dengan status true.
230
if($user) {
//return success with Api Resource
return new UserResource(true, 'Detail Data User!', $user);
}
Tapi, jika data user tidak ditemulan, maka akan mengembalikan ke dalam UserResource
dengan status false.
function update
Method ini akan kita gunakan untuk melakukan proses update data ke dalam database.
Sama seperti method store, disini kita juga menggunakan sebuah validasi untuk
memastikan data yang akan diupdate itu sudah benar-benar sesuai dengan yang di
harapkan.
$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|unique:users,email,'.$user->id,
'password' => 'confirmed'
]);
231
KEY VALIDASI KETERANGAN
Jika data yang akan diupdate belum sesuai dengan validasi di atas, maka akan melakuakn
return ke dalam JSON dengan informasi error validasi.
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
Setelah itu kita membuat sebuah kondisi lagi untuk memeriksa apakah request dengan
nama password memiliki value atau tidak. Jika IYA, maka kita akan melakukan update data
user dengan password baru, tapi jika TIDAK, maka kita akan update data user tanpa
melakukan update untuk attribute password-nya.
if($request->password == "") {
Kode di atas digunakan untuk melakukan update data user tanpa harus mengupdate
attribute password, karena nilai dari request yang bernama password memiliki value
null atau kosong.
Tapi jika request dengan nama password tersebut memiliki value, maka artinya kita akan
melakukan update data user dengan password baru.
232
Untuk memastikan proses update berhasil, kita akan membuat kondisi untuk variable
$user, jika bernilai true, maka akan kita return ke dalam UserResource dengan status
true.
if($user) {
//return success with Api Resource
return new UserResource(true, 'Data User Berhasil Diupdate!', $user);
}
Tapi, jika variable tersebut bernilai false, maka akan kita return ke dalam UserResource
dengan status false, kurang lebih seperti berikut ini :
function destroy
Method ini kita gunakan untuk proses hapus data user dari database, disini kita akan
menghapus data user tersebut berdasarkan ID yang di dapat melalui parameter URL.
Jika proses delete data berhasil maka kita akan memanggil UserResource dengan status
true, kurang lebih seperti berikut ini :
if($user->delete()) {
//return success with Api Resource
return new UserResource(true, 'Data User Berhasil Dihapus!', null);
}
Tapi, jika proses delete data gagal, maka akan melakukan return ke UserResource dengan
status false, kurang lebih seperti berikut ini :
233
Langkah 3 - Menambahkan Route User
Setelah berhasil membuat controller dan menambahkan method di dalamnya, maka
sekarang kita lanjutkan untuk menambahkan route agar controller user dapat digunakan.
Silahkan tambahkan route di bawah ini di dalam file routes/api.php, di dalam prefix
admin dan group middleware auth:api_admin, lebih tepatnya di bawah route /sliders.
//users resource
Route::apiResource('/users',
App\Http\Controllers\Api\Admin\UserController::class, ['except' =>
['create', 'edit'], 'as' => 'admin']);
Untuk memastikan apakah route yang sudah kita tambahkan di atas berhasil, maka kita bisa
menjalankan perintah berikut ini di dalam terminal/CMD :
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :
234
php artisan route:cache
Pertama kita akan melakukan proses uji coba untuk method index, silahkan buka aplikasi
Postman kemudian masukkan URL berikut ini http://localhost:8000/api/admin/users dan
untuk method-nya silahkan pilih GET.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data users.
235
Uji Coba Method Store
Sekarang kita lanjutkan untuk melakukan proses uji coba insert data ke dalam database.
Silahkan buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/admin/users dan untuk method-nya silahkan pilih POST.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Setelah itu silahkan klik tab Body dan pilih form-data kemudian masukkan key dan value
berikut ini :
236
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data user yang baru saja di insert.
Setelah berhasil melakukan proses insert data user ke dalam database, sekarang kita
lanjutkan untuk menampilkaan detail dari user tersebut. Silahkan buka aplikasi Postman
dan masukkan URL berikut ini http://localhost:8000/api/admin/users/2 dan untuk method-
nya silahkan pilih GET.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi detail data user.
237
Uji Coba Method Update
Sekarang kita lanjutkan untuk uji coba proses update data user ke dalam database. Silahkan
buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/admin/users/2 dan untuk method-nya silahkan pilih PUT.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Setelah itu klik tab Body dan pilih form-data kemudian masukkan key dan value seperti
berikut ini :
238
KEY TYPE VALUE
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi tentang data user yang diupdate.
Sekarang kita lanjutkan untuk melakukan proses hapus data, silahkan buka aplikasi
Postman kemudian masukkan URL berikut ini http://localhost:8000/api/admin/users/2 dan
untuk method-nya silahkan pilih DELETE.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data user telah berhasil dihapus.
239
240
Membuat Restful API Register Customer
Pada materi kali ini, kita semua akan belajar bagaimana cara membuat Rest API untuk
proses register data customer, disini kita tidak akan membuat sebuah Resource lagi, karena
kita bisa menggunakan Resource yang sudah kita buat sebelumnya.
Jika perintah di atas berhasil, maka kita akan mendapatkan 1 file controller baru dengan
nama RegisterController.php yang berada di dalam folder
app/Http/Controllers/Api/Customer. Silahkan buka file tersebut dan ubah kode-nya
menjadi seperti berikut ini :
<?php
namespace App\Http\Controllers\Api\Customer;
use App\Models\Customer;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use App\Http\Resources\CustomerResource;
use Illuminate\Support\Facades\Validator;
241
'name' => 'required|string|max:255',
'email' => 'required|email|unique:customers',
'password' => 'required|confirmed',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}
//create customer
$customer = Customer::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password)
]);
if($customer) {
//return with Api Resource
return new CustomerResource(true, 'Register Customer
Berhasil', $customer);
}
}
}
Dari penambahan kode di atas, pertama kita melakukan import Model Customer, karena
kita akan gunakan untuk proses insert data nanti.
use App\Models\Customer;
Setelah itu, kita juga import Facades Hash, ini akan digunakan untuk melakukan hashing
atau random password saat proses register.
use Illuminate\Support\Facades\Hash;
Dan karena akan mengembalikan response JSON, maka kita juga akan import
242
CustomerResource.
use App\Http\Resources\CustomerResource
Karena kita akan membutuhkan sebuah validasi, maka kita perlu melakukan import juga,
disini kita import Facades Validator dari Laravel.
use Illuminate\Support\Facades\Validator;
Dan di dalam class RegisterController kita menambahkan method baru dengan nama
store, method tersebut yang akan digunakan untuk memproses register customer.
Pertama-tama, kita akan membuat sebuah validasi terlebih dahulu, dimana validasi ini
bertujuan untuk memverifikasi data yang dimasukkan apakah sudah sesuai dengan yang
diharapkan.
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:customers',
'password' => 'required|confirmed',
]);
Dari kode validasi di atas, kurang lebih penjelasan detailnya seperti berikut ini :
Jika data yang di masukkan belum sesuai dengan validasi di atas, maka akan
mengembalikan sebuah response dengan format JSON dengan informasi error validasi.
243
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}
Tapi jika data yang di masukkan sudah sesuai, maka akan melakukan proses insert
menggunakan Model dan eloquent, kurang lebih seperti berikut ini :
//create customer
$customer = Customer::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password)
]);
Untuk memastikan apakah proses register di atas berhasil, maka kita akan membuat kondisi
untuk mengecek hasil dari variable $customer.
Jika variable $customer bernilai true, maka akan melakukan return ke dalam
CustomerResource dengan status true.
if($customer) {
//return with Api Resource
return new CustomerResource(true, 'Register Customer Berhasil',
$customer);
}
Tapi jika variable tersebut bernilai false, maka akan melakukan return dengan
CustomerResource dengan status false.
244
pastikan berada di luar prefix admin, karena kita akan membuat sebuah prefix baru untuk
customer.
//route register
Route::post('/register',
[App\Http\Controllers\Api\Customer\RegisterController::class, 'store'],
['as' => 'customer']);
});
Untuk memastikan route di atas berhasil ditambahkan, kita bisa menjalankan perintah
berikut ini di dalam terminal/CMD :
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :
245
php artisan route:cache
Jika sudah, silahkan klik Send, maka kita akan mendapatkan error validasi, karena kita
belum memasukkan data apapun di dalam Postman.
Sekarang silahkan klik tab Body dan pilih form-data, kemudian masukkan key dan value
berikut ini :
KEY VALUE
Jika sudah, silahkan klik Send lagi dan jika berhasil maka kita akan mendapatkan sebuah
246
response JSON dengan informasi proses registrasi berhasil dilakukan.
Di materi selanjutnya kita akan mempelajari bagaimana cara melakukan konfigurasi JWT
untuk otentikasi di customer dan menggunakan data yang berhasil register di atas untuk
proses otentikasi.
247
Konfigurasi JWT untuk Customer
Sekarang kita akan lanjutkan belajar untuk konfigurasi JWT untuk customer, disini kita tidak
perlu melakukan installasi JWT lagi, karena itu sudah pernah kita lakukan sebelumnya. Jadi
disini tugas kita hanya membuat sebuah guard, provider dan menambahkan beberapa kode
di Model Customer.
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
248
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
Bisa kita perhatikan, di atas kita menambahkan 1 konfigurasi lagi untuk api_customer dan
untuk providernya kita arahkan ke dalam customers.
Sekarang, kita akan lanjutkan untuk membuat provider customers tersebut, masih di
dalam file yang sama yaitu config/auth.php dan kemudian silahkan cari kode berikut ini
:
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
249
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'customers' => [
'driver' => 'eloquent',
'model' => App\Models\Customer::class,
],
],
Di atas, kita menambahkan 1 provider baru dengan nama customers dan untuk driver-nya
kita atur menggunakan Eloquent dan Model-nya kita arahkan ke dalam Customer.
<?php
namespace App\Models;
use Illuminate\Support\Carbon;
use Tymon\JWTAuth\Contracts\JWTSubject; // <-- import
JWTSubject
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable; // <-- import
Auth Laravel
{
use HasFactory;
/**
* fillable
250
*
* @var array
*/
protected $fillable = [
'name', 'email', 'email', 'email_verified_at', 'password',
'remember_token'
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* invoice
*
* @return void
*/
public function invoices()
{
return $this->hasMany(Invoice::class);
}
/**
* reviews
*
* @return void
*/
public function reviews()
{
return $this->hasMany(Review::class);
}
/**
* getCreatedAtAttribute
*
* @param mixed $date
* @return void
*/
public function getCreatedAtAttribute($date)
{
251
$value = Carbon::parse($date);
$parse = $value->locale('id');
return $parse->translatedFormat('l, d F Y');
}
/**
* Get the identifier that will be stored in the subject claim of
the JWT.
*
* @return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be
added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims()
{
return [];
}
}
Setelah itu, kita juga import Auth dari Laravel, karena Model Customer nanti akan
digunakan untuk proses otentikasi.
Dan pada class Customer untuk extends kita atur menjadi Authenticatable dan
252
implements ke JWTSubject.
1. function getJWTIdentifier.
2. function getJWTCustomClaims.
Sekarang, kita sudah bisa menggunakan customer untuk melakukan proses otentikasi dan
generate token JWT.
253
Membuat Restful API Login Customer
Setelah berhasil melakukan konfigurasi JWT untuk customer, maka sekarang kita akan
lanjutkan untuk membuat proses login atau otentikasi untuk customer. Dan disini kita tidak
hanya membuat proses login, tapi ada beberapa fitur lainnya, seperti :
Jika perintah di atas berhasil, maka kita akan mendapatkan 1 file controller baru dengan
nama LoginController.php yang berada di dalam folder
app/Http/Controllers/Api/Customer. Silahkan buka file tersebut dan ubah kode-nya
menjadi seperti berikut ini :
<?php
namespace App\Http\Controllers\Api\Customer;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Facades\JWTAuth;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
254
public function index(Request $request)
{
//set validasi
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'password' => 'required',
]);
//response error validasi
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
}
//response login "success" dengan generate "Token"
return response()->json([
'success' => true,
'user' => auth()->guard('api_customer')->user(),
'token' => $token
], 200);
}
/**
* getUser
*
* @return void
*/
public function getUser()
{
//response data "user" yang sedang login
return response()->json([
'success' => true,
'user' => auth()->guard('api_customer')->user()
], 200);
255
}
/**
* refreshToken
*
* @param mixed $request
* @return void
*/
public function refreshToken(Request $request)
{
//refresh "token"
$refreshToken = JWTAuth::refresh(JWTAuth::getToken());
}
}
Dari perubahan kode di atas, pertama kita import provider Http Request dari Laravel, ini
digunakan untuk menerimaa request yang dikirim melalui cookie, input form dan lain-lain.
256
use Illuminate\Http\Request;
Setelah itu, kita juga import Facades dari JWT, ini akan kita gunakan untuk proses yang
berhubungan dengan Token JWT nantinya.
use Tymon\JWTAuth\Facades\JWTAuth;
Dan kita juga import Facades Validator, yang mana akan kita gunakan untuk proses
validasi input yang akan kita buat di Rest API nanti.
use Illuminate\Support\Facades\Validator;
1. index
2. getUser
3. refreshToken
4. logout
function index
Method inilah yang akan digunakan untuk proses otentikasi untuk customer, pertama-tama
kita akan membuat sebuah validasi terlebih dahulu, fungsinya agar memastikan data yang
dikirim sudah sesuai dengan yang diharapkan.
//set validasi
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'password' => 'required',
]);
Dari pendefinisian validasi di atas, kurang lebih penjelasan detailnya seperti berikut ini :
257
KEY VALIDASI KETERANGAN
Jika data yang dikirim tidak sesuai dengan validasi di atas, maka kita akan mengembalikan
sebuah response dengan format JSON yang berisi informasi error tentang validasi.
Setelah itu kita buat sebuah variable yang bernama $credentials yang berisi sebuah
data email dan password yang dikirim melalui Http request. Kemudian kita lakukan proses
pengecekan jika email dan password tersebut tidak terdaftar di dalam database.
//...
Maka akan mengembalikan sebuah response dengan format JSON yang berisi informasi
tentang proses login gagal atau failed.
Tapi jika email dan password ada di dalam database, maka kita akan mengembalikan
sebuah response dalam format JSON yang berisi informasi data customer dan informasi
token JWT.
258
//response login "success" dengan generate "Token"
return response()->json([
'success' => true,
'user' => auth()->guard('api_customer')->user(),
'token' => $token
], 200);
function getUser
Method ini digunakan untuk menampilkan informasi data customer yang sedang login,
kurang lebih seperti berikut ini :
Di atas kita mendapatkan data customer menggunakan helper auth dari Laravel, dimana
helper auth tersebut kita arahkan ke dalam object guard api_customer, yang memang
proses otentikasinya menggunakan guard API tersebut.
function refreshToken
Method ini kita gunakan untuk proses memperbarui token JWT, alurnya kita generate
sebuah token baru setelah itu token tersebut kita set ke data customer yang sedang login.
//refresh "token"
$refreshToken = JWTAuth::refresh(JWTAuth::getToken());
Kode di atas digunakan untuk melakukan proses generate token baru. Setelah itu hasil dari
token baru tersebut kita set ke dalam data customer yang sedang login.
Kemudian kita set juga untuk header Authorization dengan value Bearer + token baru.
259
//set header "Authorization" dengan type Bearer + "token" baru
$request->headers->set('Authorization','Bearer '.$refreshToken);
Setelah itu kita mengembalikan sebuah response dalam format JSON yang berisi informasi
data customer beserta token baru.
function logout
Method ini digunakan untuk proses menghapus token yang terdaftar di server backend,
untuk mengahapus token kita bisa menggunakan kode seperti berikut ini :
Setelah itu kita mengembalikan sebuah response dalam format JSON dengan status success
true.
Silahkan buka file routes/api.php kemudian masukkan route berikut ini di dalam prefix
260
customer dan tepatnya di bawah route /register.
//route login
Route::post('/login',
[App\Http\Controllers\Api\Customer\LoginController::class, 'index'],
['as' => 'customer']);
//data user
Route::get('/user',
[App\Http\Controllers\Api\Customer\LoginController::class, 'getUser'],
['as' => 'customer']);
//logout
Route::post('/logout',
[App\Http\Controllers\Api\Customer\LoginController::class, 'logout'],
['as' => 'customer']);
});
Di atas kita menambahkan beberapa route. Dan untuk route /user, /refresh, /logout
kita masukkan di dalam middleware auth:api_customer, karena ketiga route tersebut
hanya bisa diakses jika customer sudah melakukan proses otentikasi terlebih dahulu.
Jika route dengan prefix customer ditulis secara lengkap, kurang lebih seperti berikut ini :
261
//group route with prefix "customer"
Route::prefix('customer')->group(function () {
//route register
Route::post('/register',
[App\Http\Controllers\Api\Customer\RegisterController::class, 'store'],
['as' => 'customer']);
//route login
Route::post('/login',
[App\Http\Controllers\Api\Customer\LoginController::class, 'index'],
['as' => 'customer']);
//data user
Route::get('/user',
[App\Http\Controllers\Api\Customer\LoginController::class, 'getUser'],
['as' => 'customer']);
//logout
Route::post('/logout',
[App\Http\Controllers\Api\Customer\LoginController::class, 'logout'],
['as' => 'customer']);
});
});
Untuk memastikan penambahan route di atas berhasil, kita bisa memverifikasinya dengan
menjalankan perintah berikut ini di dalam terminal/CMD :
262
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :
Silahkan teman-teman buka aplikasi Postman terlebih dahulu, setelah aplikasi berhasil
terbuka, silahkan masukkan URL berikut ini http://localhost:8000/api/customer/login dan
untuk method-nya silahkan gunakan POST.
Kemudian memasukkan email dan password yang pernah kita masukkan saat register.
Silahkan kembali lagi ke aplikasi Postman dan klik tab Body kemudian pilih form-data
dan masukkan key dan value berikut ini :
KEY VALUE
email kurnia@gmail.com
password password
263
CATATAN ! : data di atas merupakan data customer yang ada di dalam database dan data
tersebut dibuat saat kita melakukan proses register.
Sekarang, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response yang berisi informasi data customer dan token JWT. Kurang lebih seperti berikut
ini :
INFORMASI : selanjutnya untuk mengakses route yang membutuhkan otentikasi, maka kita
harus menyertakan token yang di dapatkan dari proses login seperti di atas.
Setelah berhasil mendapatkan token dari hasil proses otentikasi, sekarang kita lanjutkan
untuk proses uji coba get data customer, dimana untuk mendapatkan data customer yang
sedang login kita membutuhkan token dari proses otentikasi sebelumnya.
Silahkan buka tab baru di aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/customer/user, dan untuk method-nya silahkan pilih GET. Setelah
itu, silahkan klik tab Headers kemudian masukkan key dan value berikut ini di dalamnya.
KEY VALUE
Accept application/json
Content-Type application/json
264
CATATAN ! : untuk value dari Authorization adalah Bearer kemudian spasi Token,
token tersebut silahkan diambil dari hasil response token di proses login.
Jika sudah, silahakn klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response yang berisi informasi tentang data customer yang sedang login.
Pada tahap kali ini kita semua akan belajar bagaimana cara melakuakn refresh token di
dalam JWT (Json Web Token), yaitu konsepnya melakukan regenerate atau memperbarui
token yang lama menjadi baru dan di set ke customer yang sedang login.
Silahkan buka tab baru di dalam aplikasi Postman, kemudian masukkan URL berikut ini
http://localhost:8000/api/customer/refresh dan untuk method-nya silahkan pilih GET.
Selanjutnya klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dengan berisi informasi data customer beserta token baru. Jika kita perhatikan
token dari hasil login akan di regenerate dengan token baru, kita bisa bandingkan 2 token
tersebut, maka nilainya akan berbeda.
265
CATATAN ! : selanjutnya untuk token yang dipakai adalah token dari hasil Refresh Token.
Tapi jika kita melakukan proses login ulang, maka token yang di pakai adalah token hasil
login tersebut.
Terakhir kita akan lakukan uji coba untuk proses logout, silahkan buka tab baru di Postman
daan masukkan URL berikut ini http://localhost/api/customer/logout dan untuk method-nya
adalah POST.
Selanjutnya klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response berupa informasi bahwa proses logout berhasil.
266
267
Membuat Restful API Dashboard Customer
Setelah berhasil membuat proses otentikasi untuk customer, maka sekarang kita lanjutkan
untuk membuat Rest API untuk menampilkan statistik data dari customer tersebut. Dan
disini kita akan letakkan di dalam controller dashboard.
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
yang berada di dalam folder app/Http/Controllers/Api/Customer. Silahkan buka file
tersebut dan ubah semua kode-nya menjadi seperti berikut ini :
<?php
namespace App\Http\Controllers\Api\Customer;
use App\Models\Invoice;
use App\Http\Controllers\Controller;
268
$expired = Invoice::where('status',
'expired')->where('customer_id',
auth()->guard('api_customer')->user()->id)->count();
$failed = Invoice::where('status',
'failed')->where('customer_id',
auth()->guard('api_customer')->user()->id)->count();
//response
return response()->json([
'success' => true,
'message' => 'Statistik Data',
'data' => [
'count' => [
'pending' => $pending,
'success' => $success,
'expired' => $expired,
'failed' => $failed
]
]
], 200);
}
}
Dari penambahan kode di atas, pertama kita melakukan import untuk Model Invoice,
karena kita akan menggunakan ini untuk mendapatkan data invoice berdasarkan customer
yang sedang login.
use App\Models\Invoice;
269
//count invoice
$pending = Invoice::where('status', 'pending')->where('customer_id',
auth()->guard('api_customer')->user()->id)->count();
Di atas, kita melakukan count ke dalam table invoices sesuai dengan customer yang
sedang login dan kita pisahkan berdasarkan kondisi dari invoice tersebut, yaitu pending,
success, expired da failed.
Setelah itu, kita akan lakukan return ke dalam format JSON, kurang lebih seperti berikut ini :
//response
return response()->json([
'success' => true,
'message' => 'Statistik Data',
'data' => [
'count' => [
'pending' => $pending,
'success' => $success,
'expired' => $expired,
'failed' => $failed
]
]
], 200);
Silahkan buka file routes/api.php kemudian masukkan route berikut ini di dalam prefix
customer dan di dalam midlleware auth:api_customer dan tepatnya di bawah route
/logout.
270
//dashboard
Route::get('/dashboard',
[App\Http\Controllers\Api\Customer\DashboardController::class, 'index'],
['as' => 'customer']);
Untuk memastikan route yang kita buat di atas berhasil, kita bisa menjalankan perintah
berikut ini di dalam terminal/CMD :
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :
Silahkan teman-teman buka aplikasi Postman terlebih dahulu, setelah aplikasi berhasil
terbuka, silahkan masukkan URL berikut ini http://localhost:8000/api/customer/dashboard
271
dan untuk method-nya silahkan gunakan GET.
Setelah itu, silahkan klik tab Headers kemudian masukkan key dan value berikut ini di
dalamnya.
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, silahkan klik Send dan jika berhasil maka kurang lebih hasilnya seperti berikut ini
:
272
Membuat Restful API Invoices Customer
Pada materi kali ini, kita semua akan belajar bagaimana cara membuat Rest API untuk
menampilkan data invoice berdasarkan customer yang sedang login. Dan tentu saja kita
tidak perlu membuat sebuah Resource lagi, kita cukup menggunakan yang sudah ada.
Jika berhasil, maka kita akan mendapatkan 1 file controller baru dengan nama
InvoiceController.php yang berada di dalam folder
app/Http/Controllers/Api/Admin. Silahkan buka file tersebut dan ubah semua kode-
nya menjadi seperti berikut ini :
<?php
namespace App\Http\Controllers\Api\Customer;
use App\Models\Invoice;
use App\Http\Controllers\Controller;
use App\Http\Resources\InvoiceResource;
273
auth()->guard('api_customer')->user()->id)->paginate(5);
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($snap_token)
{
$invoice = Invoice::with('orders.product', 'customer', 'city',
'province')->where('customer_id',
auth()->guard('api_customer')->user()->id)->where('snap_token',
$snap_token)->first();
if($invoice) {
//return success with Api Resource
return new InvoiceResource(true, 'Detail Data Invoice :
'.$invoice->snap_token.'', $invoice);
}
Dari penambahan kode di atas, pertama kita lakukan import Model Invoice.
use App\Models\Invoice;
Setelah itu, kita import juga InvoiceResource karena akan digunakan untuk melakukan
konversi dari Model menjadi format JSON dengan cepat dan baik.
use App\Http\Resources\InvoiceResource;
274
Dan di dalam class InvoiceController kita menambahkan 2 method baru, yaitu :
function index
Method ini akan digunakan untuk menampilkan list data invoice dari dalam database, disini
kita menggunakan Eloquent untuk proses getting datanya.
Kode di atas merupakan Eloquent di Laravel untuk mendapatkan list data menggunakan
Model. Setelah itu, kita membuat kondisi lagi untuk proses pencarian, disini kita
menggunakan method when yang artinya jika ada sebuah request bernama q, maka akan
melakukan proses pencarian data berdasarkan attribute invoice yang sesuai dengan isi
dari request q tersebut.
Dan kita berikan kondisi lagi menggunakan where yaitu mencari data berdasarkan
customer_id dan kita cocokan dengan ID customer yang sedang login.
where('customer_id', auth()->guard('api_customer')->user()->id)
Setelah itu, kita urutkan data yang akan ditampilkan berdasarkan yang paling terbaru, disini
kita menggunakan method latest(). Dan data yang akan ditampilkan akan dibatas
perhalaman 5 record menggunakan method paginate().
275
function show
Method ini akan digunakan untuk menampilkan detail data invoice berdasarkan
snap_token dan ID customer, kurang lebih sepeerti berikut ini :
Kode diatas digunakan untuk mencari data invoice berdasarkan snap_token dan
customer_id. Dan kita juga memanggil beberapa relasi menggunakan eager loaded yaitu
method with, yang mana di dalamnya ada beberapa relasi, diantaranya adalah :
Relasi tersebut merupakan pendefinisian sebuah method yang sebelumnya sudah kita buat
di dalam materi Eloquent Relationships.
Setelah itu, kita membuat kondisi untuk memastikan apakah variable $invoice tersebut
berilai true atau false, jika bernilai true, artinya data ditemukan di dalam database, dan
kita akan melakukan return ke dalam InvoiceResource dengan status true.
if($invoice) {
//return success with Api Resource
return new InvoiceResource(true, 'Detail Data Invoice :
'.$invoice->snap_token.'', $invoice);
}
Tapi, jika variable tersebut bernilai false, artinya data invoice tidak ditemukan di dalam
database, maka kita akan melakukan return ke dalam InvoiceResource dengan status
false.
276
Langkah 2 - Menambahkan Route Invoice
Setelah berhasil membuat controller dan menambahkan method di dalamnya, maka
sekarang kita lanjutkan untuk menambahkan route agar controller invoice dapat diakses.
Silahkan tambahkan route di bawah ini di dalam file routes/api.php, di dalam prefix
customer dan group middleware auth:api_customer, lebih tepatnya di bawah route
/dashboard.
//invoices resource
Route::apiResource('/invoices',
App\Http\Controllers\Api\Customer\InvoiceController::class, ['except' =>
['create', 'store', 'edit', 'update', 'destroy'], 'as' => 'customer']);
Di atas, route-nya kita tambahkan except untuk mengecualikan beberapa method dari
resource, yaitu create, store, edit, update dan destroy, karena kita hanya
membutuhkan route index dan juga show saja.
Untuk memastikan apakah route yang sudah kita tambahkan berhasil, kita bisa melakukan
verifikasi dengan menjalankan perintah berikut ini di dalam terminal/CMD :
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
277
ini di dalam terminal/CMD :
Pertama kita akan lakukan uji coba untuk mendapatkan list data invoices dari database.
Silahkan buka aplikasi Postman kemudian masukkan URL berikut ini
http://localhost:8000/api/customer/invoices dan untuk method-nya silahkan pilih GET.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data invoices.
278
279
Membuat Restful API Review Product
Sekarang kita akan belajar membuat controller yang fungsinya nanti akan digunakan untuk
melakukan proses insert data review yang diberikan oleh customer sesuai dengan produk
yang dibeli. Dan karena sebelumnya kita belum memiliki Resource untuk review, maka disini
kita akan membuatnya terlebih dahulu.
Silahkan jalankan perintah di bawah ini di dalam terminal/CMD untuk membuat resource
baru.
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file baru dengan
nama ReviewResource.php yang berada di dalam folder app/Http/Resources.
Silahkan buka file tersebut dan ubah semua kode-nya menjadi seperti berikut ini :
280
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}
Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.
281
public $status;
public $message;
Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :
$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.
Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
Silahkan jalankan perintah di bawah ini di dalam terminal/CMD dan pastikan sudah berada di
dalam project Laravel-nya.
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama ReviewController.php yang berada di dalam folder
app/Http/Controllers/Api/Customer. Silahkan buka file tersebut dan ubah semua
kode-nya menjadi seperti berikut ini :
282
<?php
namespace App\Http\Controllers\Api\Customer;
use App\Models\Review;
use App\Http\Resources\ReviewResource;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
if($check_review) {
return response()->json($check_review, 409);
}
$review = Review::create([
'rating' => $request->rating,
'review' => $request->review,
'product_id' => $request->product_id,
'order_id' => $request->order_id,
'customer_id' => auth()->guard('api_customer')->user()->id
]);
if($review) {
//return success with Api Resource
return new ReviewResource(true, 'Data Review Berhasil
Disimpan!', $review);
}
283
}
Dari penambahan kode di atas, pertama kita import Model Review terlebih dahulu.
use App\Models\Review;
Setelah itu, kita juga import ReviewResource, karena akan digunakan untuk
mentransformasi dari Model menjadi format JSON dengan cepat dan baik.
use App\Http\Resources\ReviewResource;
kita Juga import provider Http Request dari Laravel, ini digunakan untuk menerimaa
request yang dikirim melalui cookie, input form dan lain-lain.
use Illuminate\Http\Request;
if($check_review) {
return response()->json($check_review, 409);
}
Kode di atas digunakan untuk mengecek apakah ada data yang sama antara order_id dan
product_id di dalam database dengan data yang dikirim oleh request, jika benar sudah
ada maka akan mengembalikan sebuah response JSON dengan memberikan status code
409, artinya data sudah ada di dalam database.
Jika data belum ada di dalam database, maka akan melakukan insert ke dalam database
menggunakan Model, kurang lebih seperti berikut ini :
284
$review = Review::create([
'rating' => $request->rating,
'review' => $request->review,
'product_id' => $request->product_id,
'order_id' => $request->order_id,
'customer_id' => auth()->guard('api_customer')->user()->id
]);
Dari proses insert data di atas, kurang lebih penjelasannya seperti berikut ini :
Setelah itu, kita membuat kondisi untuk memastikan apakah proses insert data di atas
berhasil atau tidak. Jika variable $review bernilai true, maka akan kita return dengan
ReviewResource dengan status true.
if($review) {
//return success with Api Resource
return new ReviewResource(true, 'Data Review Berhasil Disimpan!',
$review);
}
Tapi, jika variable tersebut bernilai false, maka kita akan return dengan ReviewResource
dengan status false.
285
//return failed with Api Resource
return new ReviewResource(false, 'Data Review Gagal Disimpan!', null);
Silahkan buka file routes/api.php kemudian masukkan route berikut ini di dalam prefix
customer dan di dalam midlleware auth:api_customer dan tepatnya di bawah route
/invoices.
//review
Route::post('/reviews',
[App\Http\Controllers\Api\Customer\ReviewController::class, 'store'],
['as' => 'customer']);
Di atas, kita membuat route dengan method POST, karena akan kita gunakan untuk
mengirim data ke dalam server. Untuk memverifikasi apakah route tersebut berhasil, kita
bisa menjalankan perintah berikut ini di dalam termina/CMD :
286
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :
287
Membuat Restful API Categories Web
Sekarang kita akan lanjutkan belajar membuat Rest API untuk halaman depan website, tentu
saja karena akan ditampilkan di halaman website, maka Rest API yang akan kita buat ini
bersifat public, artinya dapat diakses oleh siapa saja tanpa perlu melakukan proses
otentikasi dan generate token.
Jika perintah di atas berhasil dijalankan, maka akan mendapatkan 1 file controller baru
dengan nama CategoryController.php yang berada di dalam folder
app/Http/Controllers/Api/Web. Silahkan buka file tersebut dan ubah semua kode-nya
menjadi seperti berikut ini :
<?php
namespace App\Http\Controllers\Api\Web;
use App\Models\Category;
use App\Http\Controllers\Controller;
use App\Http\Resources\CategoryResource;
288
$categories);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($slug)
{
$category = Category::with('products.category')
//get count review and average review
->with('products', function ($query) {
$query->withCount('reviews');
$query->withAvg('reviews', 'rating');
})
->where('slug', $slug)->first();
if($category) {
//return success with Api Resource
return new CategoryResource(true, 'Data Product By Category
: '.$category->name.'', $category);
}
Dari penambahan kode di atas, pertama kita melakukan import Model Category, karena
kita akan menggunakan model tersebut untuk mendapaaatkan data dari database.
use App\Models\Category;
Setelah itu, kita juga melakukan import CategoryResource, ini nantinya yang akan
digunakan untuk melakukan transformasi data dari model menjadi format JSON dengan
cepat dan baik.
289
use App\Http\Resources\CategoryResource;
1. function index - method ini akan digunakan untuk menampilkan list data
category.
2. function show - method ini akan digunakan untuk menampilkan data product
berdasarkan category.
function index
Di dalam method ini kita memanggil model Category untuk melakukan get data dari
database, kurang lebih seperti berikut ini :
//get categories
$categories = Category::latest()->get();
Kode di atas digunakan untuk mendapatkan dari table categories dan data yang akan
ditampilkan akan diurutkan berdasarkan yang paling terbaru menggunakan methof
latest().
Agar data tersebut bisa menjadi format JSON, maka kita akan return menggunakan
CategoryResource, kurang lebih seperti berikut ini :
function show
Method ini akan kita gunakan untuk menampilkan list data product berdasarkan data
category yang di buka, disini kita melakukan banyak sekali kondisi.
290
$category = Category::with('products.category')
Kode di atas merupakan Eloquent untuk menampilkan sebuah data dari database, dan
disini kita menambahkan beberapa eager loaded untuk memanggil beberapa relasi.
Category::with('products.category')
Kode di atas artinya Model Category akan memanggil relasi products dan dari relasi
products akan memanggil relasi category. kurang lebih alurnya seperti ini : Model Category
-> products -> category.
Dan hasil yang di dapatkan kurang lebih akan menjadi seperti ini :
- detail category
- product 1
- nama category
- product 2
- nama category
- product 3
- nama category
Kemudian untuk menghitung jumlah review dan average, kurang lebih seperti berikut ini :
291
->with('products', function ($query) {
$query->withCount('reviews'); // <-- count
"reviews"
$query->withAvg('reviews', 'rating'); // <-- average "rating"
})
Kode di atas artinya Model Category akan memanggil relasi products dan di dalam relasi
product kita extract lagi, fungsinya agar kita dapat memanggil beberapa relasi turunan yang
ada di dalam relasi products dari model Category, seperti memanggil relasi reviews.
Dan hasil yang di dapatkan kurang lebih akan menjadi seperti ini :
- detail category
- product 1
- jumlah review
- average rating
- product 2
- jumlah review
- average rating
- product 3
- jumlah review
- average rating
Dan kita cari detail data category tersebut berdasarkan parameter slug yang di dapatkan
dari URL browser. Kurang lebih seperti berikut ini :
->where('slug', $slug)->first();
Jika variable $category bernilai true, artinya data category ditemukan di dalam database,
maka kita akan melakuakn return ke dalam CategoryResource dengan status true.
292
//return success with Api Resource
return new CategoryResource(true, 'Data Product By Category :
'.$category->name.'', $category);
Tapi, jika variable tersebut bernilai false, maka kita akan return juga dan kita berikan
status false di dalam CategoryResource.
Silahkan tambahkan route di bawah ini di dalam file routes/api.php dan jangan di
masukkan di dalam prefix admin maupun customer, karena kita akan membuat prefix lagi
untuk web.
//categories resource
Route::apiResource('/categories',
App\Http\Controllers\Api\Web\CategoryController::class, ['except' =>
['create', 'store', 'edit', 'update', 'destroy'], 'as' => 'web']);
});
Jadi di dalam file routes/api.php kita memiliki 3 prefix, kurang lebih seperti ini :
293
/routes/api.php
- prefix "admin"
- route
- route
...
- prefix "customer"
- route
- route
...
- prefix "web"
- route
- route
...
Untuk memastikan penambahan route kita di atas berhasil, kita bisa memverivikasinya
dengan menjalankan perintah berikut ini di dalam terminal/CMD :
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :
294
php artisan route:cache
Sekarang kita lanjutkan untuk melakukan uji coba detail category, pastikan sudah
menambahkan data product terlebih dahulu ya dan sekarang kita akan lakukan uji coba
category yang di dalamnya memiliki data product.
Dalam contoh data yang saya gunakan adalah category Aksesoris, maka untuk uji
cobanya kurang lebih seperti berikut ini, silahkan buka aplikasi Postman dan masukkan
URL berikut ini http://localhost:8000/api/web/categories/aksesoris. Dan untuk method-nya
silahkan pilih GET.
295
CATATAN ! : silahkan ganti aksesoris dengan slug category yang kalian miliki. Dan
pastikan ada product yang menggunakan category tersebut.
296
Membuat Restful API Products Web
Setelah berhasil membuat Rest API untuk category, maka sekarang kita akan lanjutkan
untuk membuat Rest API untuk data product, dan tentu saja karena akan ditampilkan di
halaman depan website, maka Rest API tersebut akan bersifat public. Disini kita juga akan
membuat 2 method, yang pertama untuk menampilkan list data product, dan yang kedua
adalah detail dari data product tersebut.
Jika berhasil, maka kita akan mendapatkan 1 file controller baru dengan nama
ProductController.php yang berada di dalam folder
app/Http/Controllers/Api/Web. Silahkan buka file tersebut dan ubah kode-nya
menjadi seperti berikut ini :
<?php
namespace App\Http\Controllers\Api\Web;
use App\Models\Product;
use App\Http\Controllers\Controller;
use App\Http\Resources\ProductResource;
297
->withAvg('reviews', 'rating')
->withCount('reviews')
//search
->when(request()->q, function($products) {
$products = $products->where('title', 'like', '%'.
request()->q . '%');
})->latest()->paginate(8);
//return with Api Resource
return new ProductResource(true, 'List Data Products',
$products);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($slug)
{
$product = Product::with('category', 'reviews.customer')
//count and average
->withAvg('reviews', 'rating')
->withCount('reviews')
->where('slug', $slug)->first();
if($product) {
//return success with Api Resource
return new ProductResource(true, 'Detail Data Product!',
$product);
}
Dari penambahan kode di atas, pertama kita lakukan import untuk Model Product.
use App\Models\Product;
Dan juga kita import ProductResource, karena nantinya akan digunakan untuk melakukan
298
transformasi data dari Model menjadi format JSON.
use App\Http\Resources\ProductResource;
function index
Method ini akan digunakan untuk menampilkan list data products dari database, dan disini
kita juga akan memanggil beberapa relasi yang terkait dengan data product, seperti
review dan category.
Product::with('category')
Kode di atas, artinya kita akan menampilkan data product beserta relasi category, method
with digunakan untuk memanggil sebuah relasi di dalam Eloquent atau biasa disebut
dengan eager loaded.
Setelah itu, kita juga akan melakukan count dan average relasi reviews, kurang lebih
seperti berikut ini :
Kemudian kita menambahkan kondisi lagi menggunakan method when, jadi jika ada sebuah
request dengan nama q, maka kita akan mencari data product berdasarkan attribute title
dengan dicocokan isi dari request q.
->when(request()->q, function($products) {
$products = $products->where('title', 'like', '%'. request()->q .
'%');
})
299
Kemudian kita urutkan data yang akan di tampilkan berdasarkan yang paling terbaru
menggunakan method latest(). Dan datanya akan dibatasi perhalaman 8 record
menggunakan method paginate().
Agar data products dapat menghasilkan format JSON, maka kita akan return ke dalam
ProductResource, kurang lebih seperti berikut ini :
function show
Method ini akan kita gunakan untuk menampilkan detail data product berdasarkan slug
yang ada di URL. Disini kita juga akan memanggil banyak relasi.
Product::with('category', 'reviews.customer')
Kode di atas artinya Model Product memanggil relasi category dan memanggil relasi
reviews dan dari relasi reviews memanggil ke relasi customer. Jadi kurang lebih seperti
berikut ini :
Setelah itu, kita juga akan melakukan count dan average di dalam relasi reviews, kurang
lebih seperti berikut ini :
Jika data product ditemukan, maka kita akan return ke ProductResource dengan status
true.
Tapi, jika data tidak ditemukan di dalam database, maka kita akan mereturn ke dalam
300
ProductResource dengan status false.
//products resource
Route::apiResource('/products',
App\Http\Controllers\Api\Web\ProductController::class, ['except' =>
['create', 'store', 'edit', 'update', 'destroy'], 'as' => 'web']);
Untuk memastikan penambahan route kita di atas berhasil, kita bisa memverivikasinya
dengan menjalankan perintah berikut ini di dalam terminal/CMD :
301
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan list data
product beserta relasi yang terkait, seperti category, count review dan average rating.
Kurang lebih seperti berikut ini :
Kita lanjutkan untuk melakukan uji coba method show, dan disini untuk nilai slug silahkan
disesuaikan dengan data yang teman-teman miliki. Dalam contoh ini saya menggunakan
slug mi-37w-dual-port-car-charger.
302
Silahkan buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/web/products/mi-37w-dual-port-car-charger dan untuk method-nya
silahkan pilih GET.
Jika sudah, silahkan klik Send dan jika berhasil maka akan mendpatkan response JSON
kurang lebih seperti berikut ini :
303
Membuat Restful API Sliders Web
Pada materi kali ini kita semua akan belajar bagaiamana cara membuat Rest API untuk data
sliders, dan jenis yang akan buat adalah public, artinya siapa saja bisa mengakses Rest API
tersebut.
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
yang bernama SliderController.php yang berada di dalam folder
app/Http/Controllers/Api/Web. Silahkan buka file tersebut dan ubah kode-nya
menjadi seperti berikut ini :
304
<?php
namespace App\Http\Controllers\Api\Web;
use App\Models\Slider;
use App\Http\Controllers\Controller;
use App\Http\Resources\SliderResource;
Di atas kita melakukan import Model Slider, karena kita akan gunakan untuk
mendapatkan data slider dari database.
use App\Models\Slider;
Setelah itu, kita juga import SliderResource, karena akan digunakan untuk mengubah
model menjadi format JSON dengan cepat dan mudah.
use App\Http\Resources\SliderResource;
305
//get sliders
$sliders = Slider::latest()->get();
Di atas kita melakukan get data dari database menggunakan Model dan data yang akan
ditampilkan akan diurutkan berdasarkan yang terbaru menggunakan method latest().
Setelah itu, agar menjadi format JSON, maka kita perlu melakukan return menggunakan
SliderResource, kurang lebih seperti berikut ini :
//sliders route
Route::get('/sliders',
[App\Http\Controllers\Api\Web\SliderController::class, 'index'], ['as'
=> 'web']);
Untuk memastikan penambahan route kita di atas berhasil, kita bisa memverivikasinya
dengan menjalankan perintah berikut ini di dalam terminal/CMD :
306
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :
307
Dari gambar di atas kita tidak menemukan data slider apapun, karena kita belum
menambahkannya, untuk itu jika ingin melihat hasilnya, silahkan menambahkan data slider
melalui Rest API di admin.
308
Installasi dan Konfigurasi Raja Ongkir
Sekarang kita akan belajar bagaimana cara konfigurasi RajaOngkir di dalam project
Laravel, nanti konsepnya kita akan mengambil data provinsi dan kota/kabupaten untuk kita
insert ke dalam database menggunakan seeder, tujuannya agar setiap request tidak
menembak API RajaOngkir secara langsung, yang akbibatnya proses menjadi lambat.
Jadi untuk request ke data provinsi dan kota/kabupaten akan diambil dari database kita
sendiri dan untuk proses biaya perhitungan ongkos kirim baru kita request ke API
RajaOngkir.
Setelah berhasil mendaftar, silahkan login dan klik menu API Key, maka kita akan
mendapatkan API KEY tersebut. Kurang lebih seperti berikut ini :
309
RAJAONGKIR_API_KEY=api_key_kamu_di_paste_disini
'rajaongkir' => [
'key' => env('RAJAONGKIR_API_KEY')
]
Jika file config/services.php di tulis secara lengkap, maka kurang lebih seperti berikut
ini :
310
<?php
return [
/*
|-------------------------------------------------------------------
-------
| Third Party Services
|-------------------------------------------------------------------
-------
|
| This file is for storing the credentials for third party services
such
| as Mailgun, Postmark, AWS and more. This file provides the de
facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
],
'postmark' => [
'token' => env('POSTMARK_TOKEN'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'rajaongkir' => [
'key' => env('RAJAONGKIR_API_KEY')
]
];
311
Langkah 3 - Membuat Seeder Data Provinsi
Setelah berhasil mengatur konfigurasi Raja Ongkir di dalam project Laravel, sekarang kita
lanjutkan untuk mengambil data provinsi yang ada di Raja Ongkir dan akan kita pindahkan
di database milik kita sendiri.
Silahkan jalankan perintah berikut ini di dalam terminal/CMD untuk membuat seeder
provinsi :
Jika perintah di atas berhasil di jalankan, maka kita akan mendapatkan 1 file seeder baru
dengan nama ProvinceSeeder.php yang berada di dalam folder database/seeders.
Silahkan buka file tersebut dan ubah kode-nya menjadi seperti berikut ini :
312
<?php
namespace Database\Seeders;
use App\Models\Province;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Http;
}
}
}
Di atas, pertama kita melakukan import Model Province, karena kita akan menggunakan
model ini untuk melakukan insert data.
use App\Models\Province;
Setelah itu, kita juga import Facades Http Client dari Laravel, atau biasa disebut dengan
Guzzle, ini akan digunakan untuk melakukan fetching ke dalam sebuah Rest API.
313
use Illuminate\Support\Facades\Http;
Dan di dalam method run kita menambahkan beberapa kode, kurang lebih seperti berikut
ini :
Setelah data di dapatkan, kita lakukan perulangan menggunakan foreach dan di dalamnya
kita melakukan insert data menggunakan Model.
Sekarang kita akan belajar menjalankan seeder yang sudah kita buat di atas, silahkan
jalankan perintah berikut ini di dalam terminal/CMD :
composer dump-autoload
314
php artisan db:seed --class=ProvinceSeeder
Jika berhasil, silahkan cek di dalam table provinces, maka kita sudah mendapatkan
hasilnya seperti berikut ini :
Jika berhaasil, maka kita akan mendapatkan 1 file seeder baru dengan nama
CitySeeder.php yang berada di dalam folder database/seeders. Silahkan buka file
tersebut dan ubah semua kode-nya menjadi seperti berikut ini :
315
<?php
namespace Database\Seeders;
use App\Models\City;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Http;
}
}
}
Di atas, kita melakukan import Model City, karena akan kita gunakan untuk melakukan
insert data ke dalam database.
use App\Models\City;
316
Setelah itu, kita juga import Facades Http Client dari Laravel, atau biasa disebut dengan
Guzzle, ini akan digunakan untuk melakukan fetching ke dalam sebuah Rest API.
use Illuminate\Support\Facades\Http;
Dan di dalam method run kita menambahkan beberapa kode, kurang lebih seperti berikut
ini :
Setelah data di dapatkan, kita lakukan perulangan menggunakan foreach dan di dalamnya
kita melakukan insert data menggunakan Model.
Sekarang kita akan belajar menjalankan seeder yang sudah kita buat di atas, silahkan
jalankan perintah berikut ini di dalam terminaal/CMD :
317
composer dump-autoload
Jika berhasil, silahkan cek di dalam table cities, maka kita sudah mendapatkan hasilnya
seperti berikut ini :
318
Membuat Restful API Raja Ongkir
Setelah berhasil mendapatkan data provinsi dan kota/kabupaten untuk Raja Oangkir, maka
sekarang kita akan lanjutkan membuat Rest API untuk menampilkan data provinsi,
kota/kabupaten dan menghitung biaya ongkos kirim.
Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan pastikan sudah berada di
dalam project Laravel.
Jika perintah di atas berhasil, maka kita akan mendapatkan 1 file baru dengan nama
RajaOngkirResource.php di dalam folder app/Http/Resources. Silahkan buka file
tersebut dan ubah semua kode-nya menjadi seperti berikut ini :
319
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}
Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.
320
public $status;
public $message;
Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :
$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.
Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
Silahkan jalankan perintah berikut ini di dalam terminal/CMD untuk membuat controller baru
:
Jike perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama RajaOngkirController.php yang berada di dalam folder
app/Http/Controllers/Api/Web. Silahkan buka file tersebut dan ubaah kode-nya
menjadi seperti berikut ini :
321
<?php
namespace App\Http\Controllers\Api\Web;
use App\Models\City;
use App\Models\Province;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Http;
use App\Http\Resources\RajaOngkirResource;
322
: '.$province->name.'', $cities);
}
/**
* checkOngkir
*
* @param mixed $request
* @return void
*/
public function checkOngkir(Request $request)
{
//Fetch Rest API
$response = Http::withHeaders([
//api key rajaongkir
'key' => config('services.rajaongkir.key')
])->post('https://api.rajaongkir.com/starter/cost', [
//send data
'origin' => 113, // ID kota Demak
'destination' => $request->destination,
'weight' => $request->weight,
'courier' => $request->courier
]);
Dari penambahan kode di atas, pertama kita import Model City dan Porivince, karena
kita akan menggunakan model tersebut untuk mendapatkan data dari database.
use App\Models\City;
use App\Models\Province;
Selanjutnya, agar data yang kita dapatkan dari Model dapat diubah menjadi format JSON,
maka kita juga akan melakukan import RajaOngkirResource.
323
use App\Http\Resources\RajaOngkirResource;
Dan karena kita akan melakukan fetching ke Rest API Raja Ongkir untuk menghitung biaya
ongkos kirim, maka kita juga akan import Facades Http Client atau Guzzle.
use Illuminate\Support\Facades\Http;
function getProvinces
Method ini akan kita gunakan untuk mendapatkan list data provinsi di seluruh Indonesia
melalui Model.
Setelah itu, agar dapat bisa berubah menjadi format JSON, maka kita melakukan
transformasi menggunakan RajaOngkirResource.
function getCities
Method ini akan kita gunakan untuk mendapatkan list data kode/kabupaten berdasarkan ID
provinsi tertentu.
324
//get province name
$province = Province::where('province_id',
$request->province_id)->first();
Di atas akan kita gunakan untuk mendapatkan detail data provinsi berdasarkan
province_id yang dikirimkan oleh request.
Setelah itu, kita akan mencari data kota/kabupaten berdasarkan province_id juga, kurang
lebih seperti berikut ini :
Setelah itu, kita akan return menggunakan RajaOngkirResource agar data yang ada di
Model berubah menjadi format JSON.
function checkOngkir
Di dalam method ini akan digunakan untuk menghitung biaya ongkos kirim, disini kita
melakukan request ke dalam endpoint https://api.rajaongkir.com/starter/cost dengan
method POST. Dan kita juga mengirim 4 data di dalamnya, yaitu :
Dan di dalam headers kita juga menambahkan API KEY dari Raja Ongkir, yang sebelumnya
sudah kita buat di dalam file .env di di konfig melalui config/services.php
Setelah itu, agar kita dapat hasil dari perhitungan biaya ongkos kirim dengan format JSON,
325
maka kita perlu melakukan return menggunakan RajaOngkirResource.
//rajaongkir
Route::get('/rajaongkir/provinces',
[App\Http\Controllers\Api\Web\RajaOngkirController::class,
'getProvinces'], ['as' => 'web']);
Route::post('/rajaongkir/cities',
[App\Http\Controllers\Api\Web\RajaOngkirController::class, 'getCities'],
['as' => 'web']);
Route::post('/rajaongkir/checkOngkir',
[App\Http\Controllers\Api\Web\RajaOngkirController::class,
'checkOngkir'], ['as' => 'web']);
Untuk memastikan penambahan route kita di atas berhasil, kita bisa memverivikasinya
dengan menjalankan perintah berikut ini di dalam terminal/CMD :
326
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :
327
Uji Coba Method getCities
Setelah itu, kita akan lakukan uji coba untuk mendapatkan list data kota/kabupaten
berdasarkan ID provinsi. Dan disini saya akan mencoba untuk menampilkan list dari provinsi
Bali dengan ID 1.
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan list data
kota/kabupaten dari provinsi Bali, kurang lebih seperti berikut ini :
328
Uji Coba Method CheckOngkir
Kita lanjutkan untuk melakukan uji coba proses hitung biaya ongkos kirim, silahkan buka
aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/web/rajaongkir/checkOngkir dan untuk method-nya silahkan pilih
POST.
Setelah itu silahkan klik tab Body dan pilih form-data dan masukkan key dan value berikut
ini :
KEY VALUE
courier jne jenis kurir, tersedia untuk jne, pos dan tiki
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan list biaya
ongkos kirim dari kota Demak ke kota Denpasar.
329
330
Membuat Restful API Carts
Sekarang akan kita lanjutkan belajar bagaimana cara membuat Restful API untuk data cart,
disini kita akan membuat beberapa method yang akan digunakan di dalam fitur cart, antara
lain :
Dan untuk Rest API yang akan kita buat ini akan bersifat private, yang artinya hanya bisa
diakses dan digunakan jika customer sudah melakukan proses otentikasi atau login terlebih
dahulu.
Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan pastikan sudah berada di
dalam project Laravel-nya.
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file baru dengan
nama CartResource.php yang berada di dalam folder app/Http/Resources. Silahkan
buka file tersebut dan ubah kode-nya menjadi seperti berikut ini :
331
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}
Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.
332
public $status;
public $message;
Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :
$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.
Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama CartController.php yang berada di dalam folder
app/Http/Controllers/Api/Web. Silahkan buka file tersebut dan ubah semua kode-nya
menjadi seperti berikut ini :
<?php
namespace App\Http\Controllers\Api\Web;
333
use App\Models\Cart;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Resources\CartResource;
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$carts = Cart::with('product')
->where('customer_id',
auth()->guard('api_customer')->user()->id)
->latest()
->get();
//return with Api Resource
return new CartResource(true, 'List Data Carts :
'.auth()->guard('api_customer')->user()->name.'', $carts);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$item = Cart::where('product_id',
$request->product_id)->where('customer_id',
auth()->guard('api_customer')->user()->id);
334
//check if product already in cart and increment qty
if ($item->count()) {
$item = $item->first();
//sum weight
$weight = $request->weight * $item->qty;
$item->update([
'price' => $price,
'weight' => $weight
]);
} else {
}
//return with Api Resource
return new CartResource(true, 'Success Add To Cart', $item);
}
/**
* getCartPrice
*
* @return void
*/
public function getCartPrice()
{
$totalPrice = Cart::with('product')
->where('customer_id',
auth()->guard('api_customer')->user()->id)
->sum('price');
335
//return with Api Resource
return new CartResource(true, 'Total Cart Price', $totalPrice);
}
/**
* getCartWeight
*
* @return void
*/
public function getCartWeight()
{
$totalWeight = Cart::with('product')
->where('customer_id',
auth()->guard('api_customer')->user()->id)
->sum('weight');
Dari penambahan kode di atas, pertama kita melakukan import Model Cart, karena kita
akan gunakan untuk mengambil data, hapus data dan lain-lain.
use App\Models\Cart;
336
Kemudian kita juga import CartResource, fungsinya akan digunakan untuk melakukan
transformasi dari data model menjadi format JSON.
use App\Http\Resources\CartResource;
function __construct
Method ini akan pertama kali dijalankan ketika sebuah class di panggil dan disini kita akan
manfaatkan untuk mengatur middleware auth:api_customer, artinya class dan method
yang ada di dalamnya hanya bisa di akses jika sudah melakukan proses otentikasi / login.
$this->middleware('auth:api_customer');
function index
Method ini akan digunakan untuk menampilkan list data cart dari customer yang sedang
login. Kurang lebih seperti berikut ini :
$carts = Cart::with('product')
->where('customer_id',
auth()->guard('api_customer')->user()->id)
->latest()
->get();
Di atas kita juga memanggil relasi product, dan menampilkan data berdasarkan
customer_id.
Setelah itu, agar datanya dapat berubah menjadi format JSON, maka kita perlu melakukan
337
return dengan CartResource, kurang lebih seperti berikut ini :
function store
Method ini akan digunakan untuk melakukan proses insert data cart ke dalam database,
disini kita membuat sebuah kondisi, apabila data product yang di masukkan sama, maka
kita cukup melakukan update quantity-nya saja.
$item = Cart::where('product_id',
$request->product_id)->where('customer_id',
auth()->guard('api_customer')->user()->id);
Kode di atas digunakan untuk melakukan check data di dalam table carts, apakah
product_id dan customer_id yang dikirm sudah ada di dalam table atau tidak.
Jika data tersebut ada, maka kita cukup melakukan update quantity-nya saja, kurang lebih
seperti berikut ini :
$item = $item->first();
//sum weight
$weight = $request->weight * $item->qty;
$item->update([
'price' => $price,
'weight' => $weight
]);
Di atas kita melakukan update attribute qty, price dan weight, karena data yang dikirim
sebelumnya sudah ada. Tapi jika data product_id dan customer_id belum ada di dalam
338
table carts, maka kita akan melakukan proses insert , kurang lebih seperti berikut ini :
Setelah itu, agar datanya dapat menampilkan format JSON, maka kita perlu melakukan
return menggunakan CartResource. Kurang lebih seperti berikut ini :
function getCartPrice
Method ini akan kita gunakan untuk melakukan hitung total harga yang ada di dalam table
carts berdasarkan customer yang sedang login, kurang lebih seperti berikut ini :
$totalPrice = Cart::with('product')
->where('customer_id', auth()->guard('api_customer')->user()->id)
->sum('price');
Di atas, kita melakukan sum ke dalam attribute price, dan kita hanya menghitung data
berdasarkan customer yang sedang login menggunakan where. Setelah itu kita return
menggunakan CartResource agar data yang di dapatkan berubah menjadi format JSON.
function getCartWeight
Method ini sama dengan getCartPrice bedanya disini kita melakukan sum ke dalam
attributte weight dan tujuannya untuk mendapatkan jumlah berat dari product yang ada di
339
dalam cart.
$totalWeight = Cart::with('product')
->where('customer_id',
auth()->guard('api_customer')->user()->id)
->sum('weight');
Kemudian kita akan return menggunakan CartResource, kurang lebih seperti berikut ini :
function removeCart
Method ini akan digunakan untuk melakukan hapus data dari table carts, kurang lebih
seperti berikut ini :
$cart = Cart::with('product')
->whereId($request->cart_id)
->first();
Kode di atas digunakan untuk mencari data cart berdasarkan ID yang dikirim melalui
request, jika data tersebut berhasil ditemukan, maka kita akan menghapusnya menggunakn
kode seperti berikut ini :
$cart->delete();
Dan setelah itu kita akan return menggunakan CartResource, kurang lebih seperti berikut
ini :
340
Langkah 3 - Menambahkan Route Cart
Kita lanjutkan untuk menambahkan route baru untuk Cart, silahkan buka file
routes/api.php kemudian tambahkan route berikut ini di dalam prefix web dan tepatnya
di bawah route /rajaongkir/checkOngkir.
//get cart
Route::get('/carts',
[App\Http\Controllers\Api\Web\CartController::class, 'index'], ['as' =>
'web']);
//store cart
Route::post('/carts',
[App\Http\Controllers\Api\Web\CartController::class, 'store'], ['as' =>
'web']);
//get cart price
Route::get('/carts/total_price',
[App\Http\Controllers\Api\Web\CartController::class, 'getCartPrice'],
['as' => 'web']);
//remove cart
Route::post('/carts/remove',
[App\Http\Controllers\Api\Web\CartController::class, 'removeCart'],
['as' => 'web']);
Untuk memastikan penambahan route kita di atas berhasil, kita bisa memverivikasinya
dengan menjalankan perintah berikut ini di dalam terminal/CMD :
341
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
342
response dalam format JSON yang berisi informasi data carts.
Sekarang kita lanjutkan untuk membuat fungsi add to cart atau memasukkan data cart ke
dalam database. Silahkan buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/web/carts dan untuk method-nya silahkan pilih POST.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Setelah itu, silahkan buka tab Body dan pilih form-data, kemudian masukkan key dan
value berikut ini :
343
KEY VALUE KETERANGAN
price harga
Jika sudah silahkan klik Send dan jika berhasil maka kita akan mendapatkan response dalam
format JSON yang berisi informasi data cart yang telah di insert.
Sekarang kita lanjutkan untuk melakukan uji coba mendapatkan total price atau harga yang
ada di dalam table carts sesuai dengan customer yang login.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
344
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data total price carts.
Sama seperti sebelumnya, disini kita akan coba untuk mendapatkan total weight / berat
yang ada di dalam table carts sesuai dengan customer yang login.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data total weight carts.
345
Uji Coba Method removeCart
Terakhir kita akan lakukan uji coba untuk melakukan hapus data cart dari database,
silaahkan buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/web/carts/remove dan untuk method-nya silahkan pilih POST.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :
KEY VALUE
Accept application/json
Content-Type application/json
Setelah itu, silahkan buka tab Body dan pilih form-data, kemudian masukkan key dan
value berikut ini :
KEY VALUE
cart_id 1
Jika sudah silahkan klik Send dan jika berhasil maka kita akan mendapatkan response dalam
format JSON yang berisi informasi data cart yang telah di hapus.
346
347
Installasi dan Konfigurasi Midtrans
Midtrans merupakan salah satu layanan payment gateway di Indonesia yang sangat
populer dan banyak digunakan karena kemudahan dalam integrasinya. Midtrans termasuk
perusahaan yang berada di dalam Gojek group.
Dengan menggunakan layanan payment gateway di dalam website yang kita buat, maka
akan mempermudah kita dalam menangani pembayaran yang dilakukan oleh customer
secara otomatis dan tanpa perlu melakukan konfirmasi pembayaran secara manual. Dan
enaknya lagi kita sudah disediakan layanan dengan nama SNAP PAY, yang akan
menampilkan beberapa metode pembayaran yang bisa di pilih oleh customer saat
melakukan checkout.
348
CATATAN:
Dari ilustrasi gambar alur kerja payment gatewat (Midtrans) di atas, kurang lebih detail
konsepnya seperti berikut ini :
349
browser.
5. Pengguna memverifikasi detailnya kemudian mengklik tombol bayar dengan kode
JavaScript snap.pay(SNAP_TOKEN). Pengguna kemudian mengisi detail
pembayaran dan mengklik tombol konfirmasi.
6. Snap Pay mengirimkan detail pembayaran ke Snap Backend.
7. Snap Backend memproses detailnya dan meresponse dengan status pembayaran.
kemudian Snap Pay memanggil kembali dengan status yang di dapatkan.
8. Snap Backend membuat notifikasi ke dalam Merchant Backend terkait status
biaya.
Silahkan buka URL berikut ini dan lakukan pendaftaran dengan mengisi semua informasi
yang dibutuhkan : https://account.midtrans.com/register
Silahkan diisi semua informasi yang dibutuhkan. Dan untuk BUSINESS NAME bisa diisi
sembarang, karena masih untuk proses pembelajaran. Jika sudah berhasil register, maka
kita akan di arahkan kedalam halaman dashboard Midtrans.
350
Setelah itu, sekarang klik menu SETTINGS > ACCESS KEYS, maka sekarang kita sudah
mendapatkan kredensial Merchant ID, Client Key dan Server Key.
351
composer require midtrans/midtrans-php:2.3.2
Silahkan tunggu proses installasi sampai selesai. Dan pastikan harus terhubung dengan
internet, karena package akan diunduh secara online.
MIDTRANS_SERVERKEY=paste_server_key_midtrans_disini
MIDTRANS_CLIENTKEY=paste_client_key_midtrans_disini
Setelah itu kita akan tambahkan config, dimana config ini akan kita gunakan untuk
mendapatkan data dari konfigurasi Midtrans dari file .env di atas.
//midtrans
'midtrans' => [
// Midtrans server key
'serverKey' => env('MIDTRANS_SERVERKEY'),
// Midtrans client key
'clientKey' => env('MIDTRANS_CLIENTKEY'),
// Isi false jika masih tahap development dan true jika sudah di
production, default false (development)
'isProduction' => env('MIDTRANS_IS_PRODUCTION', false),
'isSanitized' => env('MIDTRANS_IS_SANITIZED', true),
'is3ds' => env('MIDTRANS_IS_3DS', true),
]
Jika file config/services.php di tulis secara lengkap, maka kurang lebih seperti berikut
ini :
<?php
352
return [
/*
|-------------------------------------------------------------------
-------
| Third Party Services
|-------------------------------------------------------------------
-------
|
| This file is for storing the credentials for third party services
such
| as Mailgun, Postmark, AWS and more. This file provides the de
facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
],
'postmark' => [
'token' => env('POSTMARK_TOKEN'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'rajaongkir' => [
'key' => env('RAJAONGKIR_API_KEY')
],
//midtrans
'midtrans' => [
// Midtrans server key
'serverKey' => env('MIDTRANS_SERVERKEY'),
// Midtrans client key
'clientKey' => env('MIDTRANS_CLIENTKEY'),
// Isi false jika masih tahap development dan true jika sudah di
production, default false (development)
'isProduction' => env('MIDTRANS_IS_PRODUCTION', false),
353
'isSanitized' => env('MIDTRANS_IS_SANITIZED', true),
'is3ds' => env('MIDTRANS_IS_3DS', true),
]
];
Sekarang, kita sudah bisa menggunakan Midtrans di dalam project Laravel yang kita
kembangkan.
354
Membuat Restful API Checkout
Sekarang kita akan lanjutkan belajar bagaimana cara membuat Rest API yang fungsinya
untuk proses checkout, di dalam proses checkout nanti kita akan request ke dalam
Midtrans untuk melakukan generate token. Dan token tersebut yang akan digunakan untuk
menampilkan SNAP PAY dari Midtrans agar customer dapat memilih method pembayaran
yang diinginkan.
Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan pastikan sudah berada di
dalam project Laravel-nya.
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file resource baru
dengan nama CheckoutResource.php di dalam folder app/Http/Resources. Silahkan
buka file tersebut dan ubah kode-nya menjadi seperti berikut ini :
355
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}
Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.
356
public $status;
public $message;
Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :
$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.
Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
Jika berhasil, maka kita akan mendapatkan 1 file controller baru dengan nama
CheckoutController.php yang berada di dalam folder
app/Http/Controllers/Api/Web. Silahkan buka file tersebut dan ubah kode-nya
menjadi seperti berikut ini :
<?php
namespace App\Http\Controllers\Api\Web;
357
use Midtrans\Snap;
use App\Models\Cart;
use App\Models\Invoice;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use App\Http\Resources\CheckoutResource;
/**
* algorithm generate no invoice
*/
$length = 10;
358
$random = '';
for ($i = 0; $i < $length; $i++) {
$random .= rand(0, 1) ? rand(0, 9) : chr(rand(ord('a'),
ord('z')));
}
//generate no invoice
$no_invoice = 'INV-'.Str::upper($random);
//store invoice
$invoice = Invoice::create([
'invoice' => $no_invoice,
'customer_id' =>
auth()->guard('api_customer')->user()->id,
'courier' => $request->courier,
'courier_service' => $request->courier_service,
'courier_cost' => $request->courier_cost,
'weight' => $request->weight,
'name' => $request->name,
'phone' => $request->phone,
'city_id' => $request->city_id,
'province_id' => $request->province_id,
'address' => $request->address,
'grand_total' => $request->grand_total,
'status' => 'pending',
]);
}
//remove cart by customer
Cart::with('product')
->where('customer_id',
auth()->guard('api_customer')->user()->id)
->delete();
359
// Buat transaksi ke midtrans kemudian save snap tokennya.
$payload = [
'transaction_details' => [
'order_id' => $invoice->invoice,
'gross_amount' => $invoice->grand_total,
],
'customer_details' => [
'first_name' => $invoice->name,
'email' =>
auth()->guard('api_customer')->user()->email,
'phone' => $invoice->phone,
'shipping_address' => $invoice->address
]
];
//update snap_token
$invoice->snap_token = $snapToken;
$invoice->save();
});
}
}
Di atas, pertama kita import Snap dari Midtrans, ini akan kita gunakan untuk proses
generate SNAP_TOKEN.
use Midtrans\Snap;
Setelah itu, kita juga melakukan import untuk Model Cart dan Invoice.
360
use App\Models\Cart;
use App\Models\Invoice;
Selanjutnya, kita juga melakukan import untuk helpers string dari Laravel, ini akan kita
gunakan nantinya untuk membuat generate nomor invoice.
use Illuminate\Support\Str;
Kemudian kita juga melakukan import untuk Http Request dari Laravel, ini digunakan untuk
menerima input yang dikirim melalui form, cookie, url dan lain-lain.
use Illuminate\Http\Request;
Dan kita juga import Facades DB, atau Query Builder, disini akan kita gunakan untuk
memanggil fungsi Database Transaction, dengan menggunakan fungsi ini, maka untuk
proses checkout akan menjadi lebih aman.
use Illuminate\Support\Facades\DB;
Dan agar data yang di masukkan dapat mengembalikan dalam format JSON, tentu kita akan
melakukan import CheckoutResource.
use App\Http\Resources\CheckoutResource;
function __construct
Method ini merupakan method yang pertama kali di akses jika sebuah class di jalankan, dan
disini kita manfaatkan untuk meletakkan middleware auth:api_customer untuk
memastikan jika class ini hanya bisa diakses jika customer sudah melakukan proses
361
otentikasi. Dan kita juga inisialisasi Midtrans yang berisi config yang sebelumnya sudah
kita buat, dengan tujuan agar bisa global digunakan di dalam class.
//set middleware
$this->middleware('auth:api_customer');
function store
Method ini akan kita gunakan untuk melakukan proses checkout pembelian, dan disini kita
masukkan ke dalam fungsi Database Transaction, dengan fungsi ini maka akan
meminimalisir adanya kesalahan transaksi, jika transaksi gagal maka akan dirollback dan
jika transaksi berhasil maka baru dilakukan commit.
//...
Di dalam Database Transaction, pertama kita melakukan generate nomor invoice terlebih
dahulu. Kurang lebih seperti berikut ini :
362
/**
* algorithm generate no invoice
*/
$length = 10;
$random = '';
for ($i = 0; $i < $length; $i++) {
$random .= rand(0, 1) ? rand(0, 9) : chr(rand(ord('a'), ord('z')));
}
//generate no invoice
$no_invoice = 'INV-'.Str::upper($random);
Setelah itu, kita melakukan insert data invoice ke dalam database, kurang lebih seperti
berikut ini :
//store invoice
$invoice = Invoice::create([
'invoice' => $no_invoice,
'customer_id' => auth()->guard('api_customer')->user()->id,
'courier' => $request->courier,
'courier_service' => $request->courier_service,
'courier_cost' => $request->courier_cost,
'weight' => $request->weight,
'name' => $request->name,
'phone' => $request->phone,
'city_id' => $request->city_id,
'province_id' => $request->province_id,
'address' => $request->address,
'grand_total' => $request->grand_total,
'status' => 'pending',
]);
Dari proses insert data invoice di atas, kurang lebih penjelasannya seperti berikut ini :
mengambil dari
variable
$no_invoice yang
invoice $no_invoice
isinya adalah hasil
generate kode
random
363
ATTRIBUTE VALUE KETERANGAN
mengambil ID
customer_id auth()->guard('api_customer')->user()->id customer yang
sedang login
Setelah itu, kita akan melakukan insert data lagi dari data carts ke dalam table orders.
Dan kita juga menghapus data carts yang terkait dengan customer.
364
//remove cart by customer
Cart::with('product')
->where('customer_id', auth()->guard('api_customer')->user()->id)
->delete();
Kemudian, kita akan membuat sebuah payload, yang isinya nanti akan kita kirim ke server
Midtrans untuk dibuatkan sebuah SNAP_TOKEN. Kurang lebih isinya seperti berikut ini :
transaction_details
customer_details
transaction_details
Akan berisi data order_id yang isinya adalah nomor invoice, kemudian untuk
gross_amount kita isi dengan data dari grand_total.
customer_details
Akan berisi data first_name yang akan diisi dengan name *customer yang sedang login.
Dan untuk email akan diisi dengan email dari customer yang sedang login.
Setelah berhasil membuat payload, sekarang kita lanjutkan untuk melakukan generate
SNAP_TOKEN berdasarkan data payload di atas. Kurang lebih seperti berikut ini :
365
//create snap token
$snapToken = Snap::getSnapToken($payload);
Setelah itu, kita akan melakukan update attribute snap_token dari data invoice yang
diinsert di atas dengan isi SNAP_TOKEN dari Midtrans.
//update snap_token
$invoice->snap_token = $snapToken;
$invoice->save();
Kemudian kita buat response dengan key snap_token yang isinya adalah SNAP_TOKEN
dari Midtrans.
$this->response['snap_token'] = $snapToken;
Terakhir, karena data sudah berhasil di commit oleh Database Transaction, maka kita akan
membuat sebuah response dengan isi snap_token di atas.
//checkout route
Route::post('/checkout',
[App\Http\Controllers\Api\Web\CheckoutController::class, 'store'], ['as'
=> 'web']);
Untuk memastikan penambahan route kita di atas berhasil, kita bisa memverivikasinya
366
dengan menjalankan perintah berikut ini di dalam terminal/CMD :
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :
367
Membuat Restful API Notifikasi Handler Payment
Gateway
Ketika melakukan pembayaran di dalam Midtrans, maka Midtrans akan mengirim sebuah
webhook yang berisi informasi pembayaran ke dalam website kita. Disini kita perlu
membuat sebuah notifikasi handler yang berfungsi untuk menerima sebuah reqesut
webhook yang kirim dari Midtrans tersebut
Di dalam notifikasi handler kita bisa melakukan beberapa kondisi yang bisa disesuaikan
dengan keinginan, misalnya saja jika pembayaran sukses, makan akan melakukan update
stock, update status pembayaran dan lain-lain.
Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama NotificationHandlerController.php yang berada di dalam folder
app/Http/Controllers/Api/Web. Silahkan buka file tersebut dan ubah semua kode-nya
menjadi seperti berikut ini :
<?php
namespace App\Http\Controllers\Api\Web;
use App\Models\Invoice;
use App\Models\Product;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
368
*/
public function index(Request $request)
{
$payload = $request->getContent();
$notification = json_decode($payload);
$validSignatureKey = hash("sha512", $notification->order_id .
$notification->status_code . $notification->gross_amount .
config('services.midtrans.serverKey'));
if ($notification->signature_key != $validSignatureKey) {
return response(['message' => 'Invalid signature'], 403);
}
$transaction = $notification->transaction_status;
$type = $notification->payment_type;
$orderId = $notification->order_id;
//data tranaction
$data_transaction = Invoice::where('invoice',
$orderId)->first();
if ($transaction == 'capture') {
//nothing
/**
* update invoice to success
*/
$data_transaction->update([
'status' => 'success'
]);
$product =
Product::whereId($order->product_id)->first();
$product->update([
369
'stock' => $product->stock - $order->qty
]);
}
} elseif($transaction == 'pending'){
/**
* update invoice to pending
*/
$data_transaction->update([
'status' => 'pending'
]);
/**
* update invoice to failed
*/
$data_transaction->update([
'status' => 'failed'
]);
/**
* update invoice to expired
*/
$data_transaction->update([
'status' => 'expired'
]);
/**
* update invoice to failed
*/
$data_transaction->update([
'status' => 'failed'
]);
}
}
370
}
Di atas kita melakukan import Model Invoice dan Product, karena kita akan
menggunakan kedua model tersebut untuk manipulasi data ke database.
use App\Models\Invoice;
use App\Models\Product;
Kemudian kita juga melakukan import untuk Http Request dari Laravel, ini digunakan untuk
menerima input yang dikirim melalui form, cookie, url dan lain-lain.
use Illuminate\Http\Request;
Di dalam method ini, kita akan melakukan beberapa kondisi, pertama kita akan
mendapatkan content JSON yang dikirim melalui Midtrans dengan kode seperti berikut ini :
$payload = $request->getContent();
$notification = json_decode($payload);
Kemudian kita membuat variable yang isinya adalah data signatur yang akan kita cocokan
dengan variable $notification di atas.
Jika nilai dari $notification dan $validSignaturKey tidak sama, maka akan
melakukan return dengan status code 403.
371
if ($notification->signature_key != $validSignatureKey) {
return response(['message' => 'Invalid signature'], 403);
}
Jika data signatur ditemukan, maka kita akan melakukan query ke dalam table invoices
berdasarkan data order ID/invoice yang dikirim dari Midtrans.
$transaction = $notification->transaction_status;
$type = $notification->payment_type;
$orderId = $notification->order_id;
//data tranaction
$data_transaction = Invoice::where('invoice', $orderId)->first();
Setelah itu, kita akan melakukan pengecekan kondisi pembayaran, kurang lebih kode-nya
seperti berikut ini :
if ($transaction == 'capture') {
//nothing
/**
* update invoice to success
*/
$data_transaction->update([
'status' => 'success'
]);
$product = Product::whereId($order->product_id)->first();
$product->update([
372
'stock' => $product->stock - $order->qty
]);
}
} elseif($transaction == 'pending'){
/**
* update invoice to pending
*/
$data_transaction->update([
'status' => 'pending'
]);
/**
* update invoice to failed
*/
$data_transaction->update([
'status' => 'failed'
]);
/**
* update invoice to expired
*/
$data_transaction->update([
'status' => 'expired'
]);
/**
* update invoice to failed
*/
$data_transaction->update([
'status' => 'failed'
]);
373
}
/**
* update invoice to success
*/
$data_transaction->update([
'status' => 'success'
]);
$product = Product::whereId($order->product_id)->first();
$product->update([
'stock' => $product->stock - $order->qty
]);
}
Untuk memastikan penambahan route kita di atas berhasil, kita bisa memverivikasinya
dengan menjalankan perintah berikut ini di dalam terminal/CMD :
374
375
Apa itu Nuxt.js ?
Nuxt.js merupakan web application framework berbasis Vue.js, Node.js, Webpack dan
Babel.js yang bersifat open source dan di rancang untuk pembuatan sebuah aplikasi atau
website Universal yang bisa di render dari sisi server atau menjadi sebuah website statis.
Jika kita mengembangkan sebuah aplikasi maupun website berbasis Vue.js yang
menggunakan SPA atau Single Page Application tentu saja kita akan kesusahan dalam
mengatasi permasalah pada SEO (search engine optimization), oleh sebab itu kita bisa
menggunakan Nuxt.js untuk mengatasi permasalahan ini.
Sejarah Nuxt.js
Nuxt.js pertama kali dikembangkan oleh 2 orang dari negara Prancis, yaitu Alexandre
Chopin dan Sébastien Chopin, yang mana dirilis perdana pada tanggal 26 Oktober 2016.
Sekarang Nuxt.js sudah dikembangkan oleh banyak developer handal diseluruh dunia.
MODULAR
Nuxt.js didasarkan pada arsitektur modular yang efektif. Kita dapat memilih lebih dari
50 modul untuk membuat pengembangan aplikasi yang lebih cepat dan lebih mudah.
Kita tidak harus "reinventing the wheel" untuk mendapatkan manfaat PWA,
menambahkan Google Analytics ke halaman website atau menghasilkan peta
situs/sitemap.
RENDERING
Dengan menggunakan Nuxt.js maka kita bisa memilih mode apa yang akan kita
gunakan di dalam aplikasi atau website yang sedang dikembangkan. Mode paling
populer di Nuxt.js adalah SSR atau juga bisa disebut dengan "Universal", dimana
aplikasi yang dikembangkan akan di render disisi server menggunakan Node.js.
Kita juga bisa memilih menggunakan Mode Statis, yang artinya aplikasi atau website
kita tidak memerlukan server tetapi masih memiliki manfaat SEO karena Nuxt akan
melakukan pra-render terhadap semua halaman website dan menyertakan HTML
yang diperlukan.
STRUKTUR FOLDER
376
Secara default aplikasi Nuxt.js dimaksudkan untuk memberikan titik awal yang bagus
untuk aplikasi kecil dan besar. Kita bebas untuk mengatur aplikasi kita sesuai
kebutuhan dan dapat membuat direktori lain jika kita membutuhkannya.
CATATAN ! : untuk mempelajari Nuxt.js setidaknya kita harus mengetahui sedikit tentang
Vue.js
377
Installasi Nuxt.js
Di dalam Nuxt.js kita memiliki 2 cara untuk memulai membuat sebuah project baru, yaitu
menggunakan perintah create-nuxt-app atau membuatnya secara manual.
CATATAN! : tidak wajib mengikuti langkah-langkah di bawah ini. Hanya sebagai gambaran
bagaimana cara membuat project baru di Nuxt.js
Untuk memulai dengan cepat, kita dapat menggunakan perintah create-nuxt-app. Pastikan
kita telah menginstal npx (npx tersedia secara default sejak npm 5.2.0) atau npm v6.1 atau
yarn. Berikut ini beberapa cara menggunakan create-nuxt-app untuk memulai membuat
project baru.
YARN
NPX
NPM
Ini akan menanyakan beberapa pertanyaan (nama, opsi Nuxt, kerangka UI, TypeScript,
linter, kerangka pengujian, dll.), Ketika dijawab, itu akan menginstal semua dependensi.
Langkah selanjutnya adalah menuju ke folder project dan menjalankannya:
378
cd <project-name>
Untuk memulai, buat direktori kosong dengan nama project yang kita inginkan dan masuk
ke dalamnya:
mkdir <project-name>
cd <project-name>
touch package.json
Buka file package.json di text editor dan isi dengan konten JSON ini:
379
{
"name": "my-app",
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"generate": "nuxt generate",
"start": "nuxt start"
}
}
File package.json seperti kartu ID project kita. Jika kita tidak tahu apa itu file
package.json, kita bisa membaca sekilas di dokumentasi npm.
Setelah package.json dibuat, tambahkan nuxt ke project kita melalui npm atau yarn
seperti dibawah ini:
Perintah ini akan ditambahkan nuxt sebagai dependency pada project kita dan akan
ditambahkan ke package.json kita secara otomatis. Direktori node_modules juga akan
dibuat yang mana semua paket yang terinstal dan dependensi mereka disimpan.
Nuxt.js mengubah setiap *.vue file di dalam direktori pages sebagai route untuk aplikasi.
mkdir pages
380
touch pages/index.vue
Halaman ini penting untuk dipanggil index.vue karena ini akan menjadi halaman default
yang ditampilkan Nuxt saat aplikasi dijalankan. Ini adalah beranda dan harus disebut index.
<template>
<h1>Hello world!</h1>
</template>
Jalankan project kita dengan mengetik perintah berikut di bawah ini di terminal/CMD:
Di atas kita telah belajar bagaimana memulai membuat project Nuxt baru dengan 2 cara,
yaitu dengan perintah create-nuxt-app dan membuatnya secara manual.
Di dalam buku ini nanti kita akan praktek menggunakan yang cara create-nuxt-app,
karena lebih simple dan mudah.
381
Memahami Struktur Folder di Nuxt.js
Setelah sebelumnya kita sudah belajar bagaimana cara installasi di Nuxt Js, maka sekarang
kita akan belajar memahami struktur folder yang ada di dalam Nuxt.js. Berikut ini penjelasan
tentang folder-folder yang ada di dalam Nuxt.js setelah kita berhasil melakukan installasi.
.nuxt
Direktori .nuxt bisa disebut juga sebagai direktori build. Direktori ini dibuat secara
dinamis dan disembunyikan secara default. Di dalam direktori ini, kita dapat
menemukan file yang dibuat secara otomatis saat menjalankan nuxt dev atau
menggunakan artefak build ketika menjalankan nuxt build. Memodifikasi file-file ini
sangat bagus untuk keperluan debugging, tetapi ingat bahwa itu adalah file yang
dihasilkan setelah kita menjalankan perintah dev atau build. Jadi apapun yang
disimpan di .nuxt akan dibuat ulang.
Direktori .nuxt tidak boleh dimasukkan ke sistem version control (GIT) kita dan
harus diabaikan melalui file .gitignore karena akan dibuat secara otomatis saat
menjalankan nuxt dev atau nuxt build.
assets
Direktori assets berisi asset kita yang belum terkompilasi seperti berkas Style/Sass,
gambar atau font.
382
components
layouts
Layouts adalah bagian yang sangatlah membantu ketika kita ingin mengubah
tampilan dan nuansa pada aplikasi Nuxt.js, baik memasukkan sidebar maupun
memiliki layout berbeda untuk perangkat mobile dan desktop.
middleware
node_modules
Direktori ini akan berisi library atau package yang kita install di dalam aplikasi atau
website menggunakan NPM atau Yarn.
pages
Direktori pages berisikan semua halaman dan route dari aplikasi kita. Nuxt.js
membaca semua file .vue dalam direktori ini dan secara otomatis membuat
konfigurasi route di dalam aplikasi atau website kita.
plugins
Direktori plugins mengandung plugins Javascript yang ingin kita jalankan sebelum
memulai aplikasi Vue.js. Lokasi ini merupakan tempat untuk menambahkan plugins
Vue dan untuk memasukkan fungsi-fungsi atau konstanta-konstanta (constants).
383
Setiap kali kita membutuhkan penggunaan Vue.use(), kita harus membuat berkas
dalam direktori plugins/ dan menambahkan route ke plugins dalam
nuxt.config.js.
static
Direktori static langsung dipetakan ke dasar (root) server dan mengandung file-file
yang kemungkinan besar tidak diubah-ubah. File-file yang berada dalam direktori
akan secara otomatis dijalankan oleh Nuxt dan dapat diakses melalui URL di project
kita. Contohnya seperti berikut ini :
store
Direktori store mengandung file Vuex Store. Vuex Store sudah termasuk dalam
Nuxt.js, tetapi dimatikan secara bawaan. Membuat file index.js dalam direktori ini
akan mengaktifkan Vuex store tersebut secara otomatis.
nuxt.config
Di atas merupakan penjelasan singkat tentang fungsi-fungsi dari folder dan file yang ada di
dalam Nuxt.js.
384
Rendering
Di dalam Nuxt.js terdapat 2 jenis rendering yang bisa kita gunakan, yaitu SSR atau biasa
disebut Server Side Rendering dan Static Site.
situs dirender di server setiap kali pengguna meminta halaman, oleh karena itu
diperlukan server untuk dapat melayani halaman pada setiap permintaan.
Static sites
sangat mirip dengan aplikasi yang dirender sisi server dengan perbedaan utama
adalah bahwa situs statis dirender pada waktu dimuat pertama kali, oleh karena itu
tidak diperlukan server. Berpindah dari satu halaman ke halaman lain berada di sisi
klien.
Untuk mengaktifkan dari salah satu rendering di atas, kita bisa masuk di dalam file
nuxt.config.js. Kemudian kita bisa atur jenis yang ingin kita gunakan.
export default {
ssr: true
}
Static Site
export default {
ssr: false
}
385
Target Deployment
Di dalam Nuxt.js kita juga bisa melakukan konfigurasi untuk target deployment dari aplikasi
atau website yang kita bangun, disini kita bisa membuat 2 pilihan, yaitu dengan Hosting
Statis dan Server Hosing.
Hosting Statis
Untuk hosting statis (hosting tanpa pe-render-an di sisi server), kita perlu
menambahkan properti target dengan nilai static pada file nuxt.config.js.
export default {
target: 'static'
}
Server Hosting
Untuk hosting server, properti target harus bernilai server (ini merupakan nilai
bawaan dari properti target), ini biasanya digunakan jika aplikasi atau website
menggunakan mode SSR atau Server Side Rendering.
export default {
target: 'server'
}
Dari sini bisa kita simpulkan, jika ingin melakukan deploy aplikasi atau website yang tidak
membutuhkan SEO, kita bisa menggunakan target static tapi jika ingin aplikasi yang
dibuat lebih SEO friendly, maka kita bisa gunakan target server.
386
Routing
Nuxt.js akan secara otomatis membuat konfigurasi vue-router berdasarkan turunan file di
dalam direktori pages. Ketika kita membuat file .vue di direktori pages, maka kita akan
memiliki routing paling basic tanpa tambahan konfigurasi lainnya.
Terkadang kita mungkin perlu membuat route dinamis atau route nested atau route yang
perlu konfigurasi lebih lanjut. Pada bagian ini kita akan belajar untuk mengoptimalkan
konfigurasi router.
Basic Routes
Dari sebuah direktori seperti ini :
pages/
--| user/
-----| index.vue
-----| one.vue
--| index.vue
387
router: {
routes: [
{
name: 'index',
path: '/',
component: 'pages/index.vue'
},
{
name: 'user',
path: '/user',
component: 'pages/user/index.vue'
},
{
name: 'user-one',
path: '/user/one',
component: 'pages/user/one.vue'
}
]
}
Dynamic Routes
Untuk membuat route dinamis kita perlu membuat file yang diawali dengan nama (_)
sebelum .vue atau sebelum nama dari direktori. Kita juga dapat memberikan nama file
atau direktori apapun yang kita mau namun kita harus memberikan awalan dengan garis
bawah.
pages/
--| _slug/
-----| comments.vue
-----| index.vue
--| users/
-----| _id.vue
--| index.vue
388
router: {
routes: [
{
name: 'index',
path: '/',
component: 'pages/index.vue'
},
{
name: 'users-id',
path: '/users/:id?',
component: 'pages/users/_id.vue'
},
{
name: 'slug',
path: '/:slug',
component: 'pages/_slug/index.vue'
},
{
name: 'slug-comments',
path: '/:slug/comments',
component: 'pages/_slug/comments.vue'
}
]
}
Dengan konsep seperti ini maka kita tidak perlu sudah payah lagi dalam menangani sebuah
routing di dalam aplikasi berbasis Vue.js, karena semua sudah otomatis tergenerate sesuai
dengan file-file yang ada di dalam folder pages.
389
Meta Tags dan SEO
Nuxt.js memberikan kita 3 cara berbeda untuk menambahkan data meta ke aplikasi Anda:
Secara Global
Nuxt.js memungkinkan kita untuk mendefinisikan semua tag <meta> bawaan untuk aplikasi
di dalam file nuxt.config.js dengan menggunakan properti head. Ini sangat berguna
untuk menambahkan tag title dan description bawaan untuk tujuan SEO atau untuk
menyetel viewport atau untuk menambahkan favicon.
export default {
head: {
title: 'Judul situs web saya',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1'
},
{
hid: 'description',
name: 'description',
content: 'Deskripsi untuk website saya'
}
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
}
}
Hal ini akan membuat semua halaman aplikasi atau website memiliki judul dan deskripsi
yang sama
Secara Lokal
Kita juga bisa menambahkan judul dan meta untuk setiap halaman menggunakan method
head di dalam tag script pada setiap komponen page.
390
<script>
export default {
head: {
title: 'Ini halaman beranda',
meta: [
{
hid: 'description',
name: 'description',
content: 'Deskripsi untuk halaman beranda'
}
],
}
}
</script>
Contoh di atas menggunakan head sebagai objek untuk menyetel judul dan deskripsi untuk
halaman atau component itu saja.
391
<template>
<h1>{{ title }}</h1>
</template>
<script>
export default {
data() {
return {
title: 'Halaman beranda'
}
},
head() {
return {
title: this.title,
meta: [
{
hid: 'description',
name: 'description',
content: 'Deskripsi untuk halaman beranda'
}
]
}
}
}
</script>
Contoh di atas menggunakan head sebagai fungsi untuk menyetel judul dan deskripsi hanya
untuk halaman atau component itu saja. Dan dengan menggunakan head sebagai fungsi,
kita bisa mengakses data dan properti-properti computed.
392
Data Fetching
Di dalam Nuxt.js kita memiliki 2 cara untuk mendapatkan data dari sebuah API atau
application programming interface. Disini kita dapat menggunakan metode fetch atau
metode asyncData.
export default {
async fetch() {
console.log(this)
}
}
Hook fetch harus mengembalikan sebuah promise (baik secara eksplisit atau implisit
menggunakan async / await) yang akan diselesaikan saat :
Hal tersebut mengekspos $fetchState pada tingkatan komponen dengan properti sebagai
berikut ini:
Kita juga memiliki akses ke this.$fetch(), ini berguna jika kita ingin memanggil hook
fetch di dalam komponen. Contohnya seperti berikut ini :
393
<template>
<p v-if="$fetchState.pending">Fetching mountains...</p>
<p v-else-if="$fetchState.error">An error occurred :(</p>
<div v-else>
<h1>Nuxt Mountains</h1>
<ul>
<li v-for="mountain of mountains">{{ mountain.title }}</li>
</ul>
<button @click="$fetch">Refresh</button>
</div>
</template>
<script>
export default {
data() {
return {
mountains: []
}
},
async fetch() {
this.mountains = await fetch(
'https://api.nuxtjs.dev/mountains'
).then(res => res.json())
}
}
</script>
Caching
<template>
<nuxt keep-alive />
</template>
Kita juga dapat melakukan spesifikasi pada props yang dikirimkan ke <keep-alive>
dengan mengirimkan keep-alive-props ke komponen <nuxt>.
394
<nuxt keep-alive :keep-alive-props="{ max: 10 }" />
<script>
export default {
data() {
return {
posts: []
}
},
activated() {
// Call fetch again if last fetch more than 30 sec ago
if (this.$fetchState.timestamp <= Date.now() - 30000) {
this.$fetch()
}
},
async fetch() {
this.posts = await fetch('https://api.nuxtjs.dev/posts').then(res
=>
res.json()
)
}
}
</script>
Melakukan navigasi ke halaman yang sama tidak akan memanggil fetch kembali jika
terakhir memanggil fetch masih kurang dari 30 detik yang lalu.
395
asyncData hanya dapat digunakan pada halaman (pages) dan kita tidak memiliki akses ke
this di dalam hook tersebut.
asyncData adalah hook lain untuk fetching data universal. Tidak seperti hook fetch,
yang mengharuskan kita untuk mengatur sebuah properti di dalam component untuk
menyimpan async state, sederhananya di dalam asyncData akan mengembalikan nilai
state langsung di dalam component. Berikut ini contohnya :
<template>
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.description }}</p>
</div>
</template>
<script>
export default {
async asyncData({ params, $http }) {
const post = await
$http.$get(`https://api.nuxtjs.dev/posts/${params.id}`)
return { post }
}
}
</script>
Tidak seperti fetch, promise yang dikembalikan oleh hook asyncData diselesaikan selama
transisi route. Nuxt akan menunggu hook asyncData selesai sebelum menavigasi ke
halaman berikutnya atau menampilkan pesan kesalahan.
396
Berkenalan Dengan Vuex
Ketika kita mengembangkan aplikasi dengan Vue Js, terkadang kita akan kesulitan saat ingin
mengelola data yang berulang-ulang di dalam semua component. Karena kita akan
mendeklarasikan berulang-ulang juga untuk mengelola data tersebut. Dengan begitu, maka
aplikasi yang kita kembangkan menjadi tidak efisien, karena terlalu banyak melakukan hal
yang sama. Dengan permasalahan tersebut, maka Vuex bisa menjadi jawaban atas semua
masalah di atas. Jadi apa itu Vuex ?
Jadi secara sederhana, Vuex merupakan state manajemen pattern dan library untuk Vue Js
yang digunakan untuk membuat store secara centralized untuk semua komponen yang ada
di dalam aplikasi.
397
Inti dari Vuex adalah sebuah store yang akan digunakan sebagai kontainer dari semua
state yang ada di dalam aplikasi. Di dalam store memilki beberapa bagian-bagian dengan
fungsinya masing-masing, diantaranya adalah :
State
State merupakan sebuah objek yang digunakan untuk mendefinisikan dan menyimpan
semua data yang ada di dalam aplikasi secara reactive. Berikut ini contoh menggunakan
state untuk menyimpan sebuah data :
//state
export const state = () => ({
//state "name"
name: 'Fika Ridaul Maulayya',
//state "gender"
gender: 'male'
})
398
Sekarang, kita dapat mengakses state name dan gender langsung secara global di dalam
semua component aplikasi. Contoh menggunakan state di dalam component kita bisa
seperti berikut ini :
computed: {
name() {
return this.$store.state.name // state "name"
}
gender() {
return this.$store.state.gender // state "gender"
}
}
Dan untuk menampilkan di template, kita bisa menggunakan kode seperti berikut ini :
<h1>Nama Lengkap: {{ name }}</h1> <!-- output "Fika Ridaul Maulayya" -->
Getters
Getters digunakan untuk mendapatkan data dari state, lalu apa bedanya dengan
mengakses state langsung ? bukankah kita bisa langsung mengambil data dari state seperti
yang di lakukan di dalam contoh di atas ?. Jadi dengan menggunakan Getters kita bisa
mengolah data yang ada di dalam state sebelum digunakan di dalam component. Kurang
lebih contohnya seperti berikut ini :
399
//state
export const state = () => ({
//state "todos"
todos: [
{
title: 'Task 1',
completed: false
},
{
title: 'Task 2',
completed: true
},
{
title: 'Task 3',
completed: false
}
]
})
//getters
export const getters = () => ({
//getter get length todos status completed (true)
doneTodos(state){
return state.todos.filter(function(item){
return item.completed == true
}).length
},
//getter get length todos status pending (false)
pendingTodos(state){
return state.todos.filter(function(item){
return item.completed == false
}).length
},
})
Sekarang, kita dapat mengakses Getters doneTodos dan pendingTodos langsung secara
global di dalam semua component aplikasi. Contoh menggunakan Getters di dalam
component kita bisa seperti berikut ini :
400
computed: {
done() {
return this.$store.getters.doneTodos // getters "doneTodos"
}
pending() {
return this.$store.getters.pendingTodos // getters "pendingTodos"
}
}
Dan untuk menampilkan di template, kita bisa menggunakan kode seperti berikut ini :
Mutations
Mutations merupakan salah satu store yang digunakan untuk melakukan perubahan nilai
padat state secara reactive. Jadi mutation ini bisa dibilang mirip event yang ada di dalam
store. Mutation terdiiri dari sebuah string nama dan handlernya.
401
//state
export const state = () => ({
//state "count"
count: 1
})
//mutations
export const mutations = () => ({
increment (state) {
// mutate state
state.count++
}
})
Di atas, kita punya sebuah mutations dengan nama increment yang memiliki handler,
dimana jika di panggil maka akan menambah nilai dari state count sebanyak 1. Kita bisa
memanggil mutations di dalam component seperti berikut ini :
this.$store.commit('increment')
Actions
Actions bisa di ibaratkan sama dengan mutations, tapi tentu saja ada perbedaannya,
jadi konsepnya adalah actions akan memanggil mutation dan mutation yang akan
mengubah state-nya. Dan actions ini bersifat asynchronous. Kurang lebih alurnya seperti
berikut ini :
402
//state
export const state = () => ({
//state "count"
count: 1
})
//mutations
export const mutations = () => ({
//mutation "SET_COUNT"
SET_COUNT(state) {
state.count++
}
})
//actions
export const actions = () => ({
increment({ commit }) {
//commit ke mutation SET_COUNT
commit.commit('SET_COUNT')
}
})
Di atas, kita mempunyai actions yang bernama increment, yang mana di dalamnya
melakukan sebuah commit ke dalam mutation yang bernama SET_COUNT dan di dalam
SET_COUNT kita menambah nilai dari state count sebanyak 1.
this.$store.dispatch('increment')
403
Membuat Project Baru di Nuxt.js
Sekarang kita akan mulai untuk membuat project baru dengan Nuxt.js, yang mana project
ini akan dijadikan sebagai frontend dari website toko online yang sedang kita kembangkan.
Fungsinya yaitu mengkonsumsi atau menggunakan Rest API yang sebelumnya sudah kita
buat di dalam Laravel.
Sekarang kita bisa memembuat project baru di Nuxt.js dengan menggunakan NPX (npx
tersedia secara default sejak npm 5.2.0). Silahkan masuk ke dalam folder dimana kita akan
menyimpan project-nya. Dan jalankan perintah berikut ini di dalam terminal/CMD :
Perintah di atas digunakan untuk membuat project Nuxt.js baru dengan nama nuxt-
ecommerce dengan versi 3.7.0. Jika keluar pilihan pertanyaan, bisa ikuti langkah-
langkanya seperti berikut ini :
PERINTAH AKSI
404
PERINTAH AKSI
Setelah itu, silahkan tunggu proses installasi Nuxt.js sampai selesai, disini akan memakan
waktu lebih lama jika sebelumnya belum pernah melakukan installasi Nuxt.js.
cd nuxt-ecommerce
Perintah cd di atas digunakan untuk masuk ke dalam folder nuxt-ecommerce. Setelah itu,
jalankan perintah berikut ini :
405
Silahkan tunggu proses compile sampai selesai dan jika sudah selsai, maka aplikasi Nuxt.js
kita akan dijalankan menggunakan port 3000 di dalam localhost.
axios: {},
axios: {
baseURL: 'http://localhost:8000'
},
Di atas kita atur untuk Base URL dari API kita adalah http://localhost:8000, dimana domain
406
tersebut merupakan aplikasi Laravel kita yang sedang berjalan.
407
Kustomisasi Progress Bar di Nuxt.js
Secara default di dalam Nuxt.js sudah tersedian sebuah progress bar dan akan ditampilkaan
saat kita melakukan perpindahan antar route. Disini kita dapat menyesuaikan,
menonaktifkan atau dapat membuat component sendiri untuk progress bar.
export default {
loading: {
color: 'white', // <-- color
height: '5px' // <-- height
}
}
Kode di atas digunakan untuk menampilkan progress bar berwarna putih dan mempunyai
tinggi 5 pixel.
Jika file nuxt.config.js ditulis secara lengkap, kurang lebih seperti berikut ini :
export default {
loading: {
color: 'white', // <-- color
height: '5px' // <-- height
},
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'nuxt-ecommerce',
htmlAttrs: {
lang: 'en'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1'
},
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: 'telephone=no' }
408
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
],
409
Konfigurasi SSR dan Target Deployment
Sekarang kita akan konfigurasi di dalam project Nuxt.js untuk jenis rendering yang
digunakan dan target deployment untuk production.
// Target Deployment
target: 'server',
Dan jika file nuxt.config.js ditulis secara lengkap, maka kurang lebih seperti berikut ini :
export default {
// Target Deployment
target: 'server',
loading: {
color: 'white', // <-- color
height: '5px' // <-- height
410
},
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
],
411
axios: {
baseURL: 'http://localhost:8000'
},
412
Integrasi Dengan CSS dan JavaScript External (Template
Dashboard CoreUI)
Pada tahap kali ini kita semua akan belajar bagaimana cara mengintegrasikan CSS dan
JavaScript dari sebuah teemplate dashboard di dalam Nuxt.js. Dan kali ini kita semua akan
menggunakan template dashboard yang bernama Core UI.
https://drive.google.com/file/d/10NgnoLbqGC1DD5_xUV8dNHr7bcaoI6p7/view?usp=sharing
Jika sudah berhasil terunduh, silahkan extract file-nya dan kita akan mendapatkan 3 folder
di dalamnya.
413
Langkah 3 - Konfigurasi JavaScript dan Image
Sekarang, kita lanjutkan untuk asset JavaScript dan Image. Silahkan copy folder
javascript dan images dari hasil extract dan paste ke dalam folder static yang berada
di dalam project Nuxt.js. Kurang lebih seperti berikut ini :
414
Langkah 4 - Konfigurasi Global Assets di Nuxt.js
Sekarang kita lanjutkan untuk konfigurasi dari file assets yang sudah kita tambahkan di atas
agar dapat digunakan secara global di dalam project Nuxt.js. Silahkan buka file
nuxt.config.js, kemudian kode-nya menjadi seperti berikut ini :
export default {
// Target Deployment
target: 'server',
loading: {
color: 'white', // <-- color
height: '5px' // <-- height
},
415
href:
'https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600
;700&display=swap'
},
{
rel: 'stylesheet',
href:
'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.
css'
},
],
script: [
{ src: '/js/coreui.bundle.min.js' },
{ src: 'https://app.sandbox.midtrans.com/snap/snap.js', 'data-
client-key': 'paste_client_Key_midtrans_disini' },
]
},
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
],
416
axios: {
baseURL: 'http://localhost:8000'
},
Dari perubahan kode di atas, kita menambahkan array script, yang mana di dalanya kita
memanggil file JavaScript yang berada di dalam folder static/js, file tersebut adalah :
/js/coreui.bundle.min.js
https://app.sandbox.midtrans.com/snap/snap.js
CATATAN ! : untuk snap.js silahkan isi data-client-key dengan client key yang di
dapatkan dari Midtrans.
Di atas kita juga menambahkan 2 file CSS dari folder assets/css, kedua file tersebut
adalah :
/css/style.min.css
/css/custom.css
Tambahan, untuk menggunakan template Core UI di dalam Nuxt.js, maka kita perlu
melakukan installasi Core Icon secara manual. Silahkan jalankan perintah berikut ini di
dalam terminal/CMD :
Sampai disini maka kita sudah berhasil melakukan integrasi file-file assets dari template
dashboard Core UI di dalam project Nuxt.js.
417
INFORMASI !
Silahkan lakukan restart server Nuxt.js agar assets yang sudah kita tambahkan dapat
dicompile ulang.
Untuk melakukan restart, cukup jalankan CTRL + C di dalam terminal/CMD yang
menjalankan project-nya dan jalankan npm run dev lagi untuk menjalankan project-nya
418
Installasi dan Konfigurasi Nuxt Auth
Pada materi kali ini kita semua akan belajar bagaimana cara installasi dan konfigurasi
module Nuxt Auth di dalam Nuxt.js. Disini kita nanti akan membuat 2 strategy atau level
untuk proses login-nya, yaitu admin dan customer.
Pertama kita akan lakukan installasi module nuxt auth terlebih dahulu, silahkan jalankan
perintah di bawah ini di dalam terminal/CMD dan tentunya di dalam project Nuxt.js :
npm i @nuxtjs/auth-next@5.0.0-1622918202.e815752
Silahkan tunggu proses installasi sampai selesai dan akan membutuhkan beberapa waktu.
Setelah proses installasi selsai, kita akan lanjutkan untuk proses konfigurasinya. Sekarang
silahkan buka file nuxt.config.js, kemudian cari kode berikut ini :
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
],
Setelah itu, kita akan tambahkan module nuxt auth di dalam konfigurasi modules,
silahkan ubah kode di atas menjadi seperti berikut ini :
419
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
//https://dev.auth.nuxtjs.org/
'@nuxtjs/auth-next',
],
Kemudian kita akan menambahkan lagi untuk konfigurasi dari strategy otentikasinya di
dalam file nuxt.config.js dan tepatnya di bawah array modules.
Disini kita akan menggunakan strategy local dari module nuxt auth dan membuat 2
strategy, yaitu admin dan customer. Kurang lebih seperti berikut ini :
auth: {
strategies: {
//strategy "admin"
admin: {
scheme: 'local',
token: {
property: 'token',
required: true,
type: 'Bearer'
},
user: {
property: 'user',
// autoFetch: true
},
endpoints: {
login: {
url: '/api/admin/login',
method: 'post',
propertyName: 'token'
},
logout: {
url: '/api/admin/logout',
method: 'post'
},
user: {
url: '/api/admin/user',
420
method: 'get',
propertyName: 'user'
}
},
},
//strategy "customer"
customer: {
scheme: 'local',
token: {
property: 'token',
required: true,
type: 'Bearer'
},
user: {
property: 'user',
// autoFetch: true
},
endpoints: {
login: {
url: '/api/customer/login',
method: 'post',
propertyName: 'token'
},
logout: {
url: '/api/customer/logout',
method: 'post'
},
user: {
url: '/api/customer/user',
method: 'get',
propertyName: 'user'
}
},
},
},
},
Dari penambahan kode di atas, kita membuat 2 jenis strategy, yaitu : admin dan
customer, kedua strategy tersebut memiliki konfigurasi yang sama dan yang membedakan
hanya pada URL endpoint-nya saja. Dari konfigurasi di atas, kurang lebih detailnya seperti
berikut ini :
Strategy Admin
421
NAMA ENDPOINT / URL METHOD KETERANGAN
Strategy Customer
Jika file nuxt.config.js di tulis secara lengkap, kurang lebih seperti berikut ini :
export default {
// Target Deployment
target: 'server',
loading: {
color: 'white', // <-- color
height: '5px' // <-- height
},
422
meta: [{
charset: 'utf-8'
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1'
},
{
hid: 'description',
name: 'description',
content: ''
}
],
link: [{
rel: 'icon',
type: 'image/x-icon',
href: '/images/logo.png'
},
{
rel: 'stylesheet',
href:
'https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600
;700&display=swap'
},
{
rel: 'stylesheet',
href:
'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.
css'
},
],
script: [
{ src: '/js/coreui.bundle.min.js' },
{ src: 'https://app.sandbox.midtrans.com/snap/snap.js', 'data-
client-key': 'SB-Mid-client-bWcHM3-QSyGV2hhw' },
]
},
423
plugins: [
],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
//https://dev.auth.nuxtjs.org/
'@nuxtjs/auth-next',
],
auth: {
strategies: {
//strategy "admin"
admin: {
scheme: 'local',
token: {
property: 'token',
required: true,
type: 'Bearer'
},
user: {
property: 'user',
// autoFetch: true
},
endpoints: {
login: {
url: '/api/admin/login',
method: 'post',
propertyName: 'token'
},
logout: {
url: '/api/admin/logout',
method: 'post'
},
user: {
424
url: '/api/admin/user',
method: 'get',
propertyName: 'user'
}
},
},
//strategy "customer"
customer: {
scheme: 'local',
token: {
property: 'token',
required: true,
type: 'Bearer'
},
user: {
property: 'user',
// autoFetch: true
},
endpoints: {
login: {
url: '/api/customer/login',
method: 'post',
propertyName: 'token'
},
logout: {
url: '/api/customer/logout',
method: 'post'
},
user: {
url: '/api/customer/user',
method: 'get',
propertyName: 'user'
}
},
},
},
},
425
}
426
Membuat Middleware Role
Setelah berhasil melakukan installasi dan konfigurasi Nuxt Auth di dalam project Nuxt.js,
maka sekarang kita akan lanjutkan untuk membuat beberapa middleware, yaitu :
Middleware ini akan kita gunakan di dalam halaman login admin maupun customer, jadi
ketika user sudah berhasil login dan mengakses halaman login lagi, maka akan di arahkan
ke halaman dashboard admin atau customer.
Silahkan buat folder baru dengan nama middleware di dalam project Nuxt.js, setelah itu
silahkan buat file baru dengan nama authenticated.js di dalam folder tersebut dan
masukkan kode berikut ini :
return redirect('/admin/dashboard')
return redirect('/customer/dashboard')
}
}
427
Di atas kita melakukan check apakah $auth.loggedIn bernilai true, jika IYA maka user
tersebut sudah berhasil melakukan login. Dan di dalamnya kita melakukan pengecekan
sebuah kondisi dari strategy yang digunakan.
Jika strategy bernilai admin, maka kita akan lakukan redirect / arahakan ke halaman
dashboard admin, yaitu /admin/dashboard.
return redirect('/admin/dashboard')
Tapi, jika nilai dari strategy-nya adalah customer, maka kita akan redirect / arahkan ke
halaman dashboard cutsomer, yaitu /customer/dashboard.
428
//check role customer
if($auth.strategy.name == "customer") {
return redirect('/customer/dashboard')
Sekarang kita lanjutkan membuat middleware untuk pengecekan role admin, silahkan buat
file baru dengan nama isAdmin.js di dalam folder middleware dan masukkan kode
berikut ini :
429
Di atas kita menambahkan kode untuk middleware isAdmin, di dalamnya kita membuat 2
kondisi, yang pertama untuk pengecekan jika user belum login.
Jika nilai dari $auth.loggedIn bernilai false, maka akan di arahkan ke halaman
/admin/login.
Kemudian untuk kondisi kedua, kita melakukan pengecekan apakah nama strategy-nya
admin atau bukan. Kurang lebih seperti berikut ini :
Jika nama strategy bukan admin, maka akan di redirect ke halaman /admin/login dan
jika admin maka akan return true, artinya di izinkan.
430
Langkah 3 - Membuat Middleware isCustomer
Middleware ini sama dengan yang di atas, bedanya digunakan untuk customer dan semua
isinya sama. Silahkan buat file baru dengan nama isCustomer.js di dalam folder
middleware dan masukkan kode berikut ini :
431
Installasi dan Konfigurasi Vue Star Rating
karena akan membangun sebuah website toko online, maka kita perlu menambahkan fitur
rating dan ulasan di dalam website tersebut. Dan di dalam Vue.js / Nuxt.js ada library atau
package yang akan mepermudah kita dalam pembuatannya. Library tersebut bernama Vue
Star Rating.
Silahkan buat folder baru dengan nama plugins dan di dalam folder tersebut silahkan buat
file baru dengan nama vue-star-rating.js dan masukkan kode berikut ini :
Vue.component('vue-star-rating', VueStarRating);
432
Di atas pertama kita melakukan import Vue dari vue.
Kemudian, kita register atau membuat component dengan nama vue-star-rating yang
di ambil dari VueStarRating di dalam Vue.js
Vue.component('vue-star-rating', VueStarRating);
433
plugins: [
],
plugins: [
{ src: '~/plugins/vue-star-rating.js', mode: 'client' },
],
Di atas kita register di dalam config plugins dengan mode client. Sekarang kita sudah
bisa menggunakan component tersebut secara global di dalam project Nuxt.js kita.
434
Installasi dan Konfigurasi Chart.js
Karena akan membuat tampilkan dashboard yang interaktif dengan menambahkan grafik
atau chart, maka kita juga akan melakukan installasi dan konfigurasi library tambahan di
dalam Nuxt.js, yaitu VueChart.js dan Cart.js.
Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan pastikan sudah berada di
dalam project Nuxt.js-nya.
Setelah berhasil menambahkan library atau package di dalam project, maka sekarang kita
lanjutkan untuk melakukan konfigurasi di dalam folder plugins, agar library tersebut dapat
digunakan secara global.
Silahkan buat file baru dengan nama chart.js di dalam folder plugins dan masukkan
kode berikut ini di dalamnya.
Vue.component('line-chart', {
extends: Line,
//props
props: ['data'],
435
//method
methods: {
//format price
formatPrice(value) {
let val = (value/1).toFixed(0).replace('.', ',')
return val.toString().replace(/\B(?=(\d{3})+(?!\d))/g,
".")
},
},
//computed
computed: {
options() {
return {
responsive: true,
maintainAspectRatio: false,
scales: {
yAxes: [{
ticks: {
callback: (value, index, values) => {
return `Rp. ${this.formatPrice(value)}`;
},
},
}],
},
tooltips: {
enabled: true,
callbacks: {
label: ((tooltipItems, data) => {
console.log(this)
return `Rp. ${this.formatPrice(tooltipItems.yLabel)}`
})
}
},
beginZero: true
}
},
},
//mounted
mounted() {
this.renderChart(this.data, this.options)
}
})
436
Langkah 3 - Register Plugin di Nuxt Config
Sekarang, agar plugin di atas dapat digunakan secara global, maka kita perlu melakukan
register di dalam file nuxt config. Silahkan buka file nuxt.config.js kemudian cari kode
berikut ini :
plugins: [
{ src: '~/plugins/vue-star-rating.js', mode: 'client' },
],
plugins: [
{ src: '~/plugins/vue-star-rating.js', mode: 'client' },
{ src: '~/plugins/chart.js', mode: 'client' },
]
Di atas di melakukan register chart.js dengan mode client di dalam config plugins.
Dan sekarang kita sudah bisa menggunakan plugin tersebut secara global.
437
Installasi CKEDITOR dan Sweet Alert 2
Pada materi kali ini kita semua akan belajar bagaimana cara menambahkan library atau
package baru di dalam Nuxt.js, yaitu CKEDITOR dan Sweet Alert2.
Dan sekarang kita akan menambahkan library ini di dalam project Nuxt.js yang nantinya
akan digunakan untuk membuat kontent / deskripsi dari data product. Silahkan jalankan
perintah dibawah ini di dalam terminal/CMD dan pastikan sudah berada di dalam project
Nuxt.js-nya.
Sekarang kita akan lakukan installasi Sweet Alert2 di dalam project Nuxt.js, silahkan
jalankan perintah berikut ini di dalam terminal/CMD :
438
dapat digunakan secara global di dalam project Nuxt.js.
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
//https://dev.auth.nuxtjs.org/
'@nuxtjs/auth-next',
],
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
//https://dev.auth.nuxtjs.org/
'@nuxtjs/auth-next',
//https://github.com/avil13/vue-sweetalert2
'vue-sweetalert2/nuxt',
],
439
Membuat Global Helpers dengan Mixins
Sekarang kita akan lanjutkan untuk membuat fungsi sendiri atau helpers yang akan kita
gunakan secara berulang-ulang di dalam project Nuxt.js. Disini kita akan menggunakan
Mixins untuk membuat helpers yang sifatnya global.
const mixin = {
methods: {
//format price
formatPrice(value) {
let val = (value/1).toFixed(0).replace('.', ',')
return val.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".")
},
//calculate discount
calculateDiscount(product) {
return product.price - (product.price
*(product.discount)/100)
},
},
Vue.mixin(mixin)
440
Di atas, pertama kita import Vue dari vue.
Setelah itu, kita buat variable dengan nama mixin dan di dalamnya kita buat 2 method
baru, yaitu :
Setelah itu, kita register variable mixin tersebut di dalam Instanece Vue.mixin, tujuannya
agar bisa digunakan secara global di dalam project.
Vue.mixin(mixin)
441
plugins: [
{ src: '~/plugins/vue-star-rating.js', mode: 'client' },
{ src: '~/plugins/chart.js', mode: 'client' },
],
plugins: [
{ src: '~/plugins/vue-star-rating.js', mode: 'client' },
{ src: '~/plugins/chart.js', mode: 'client' },
{ src: '~/plugins/mixins.js' },
],
442
Membuat Proses Login Admin
Di dalam materi kali ini kita semua akan belajar bagaimana cara membuat proses otentikasi
untuk admin di dalam project Nuxt.js. Setelah sebelumnya kita sudah melakukan installasi
dan konfigurasi module Nuxt Auth, maka sekarang kita tinggal menggunakan dan
mengimplementasikannya.
Silahkan buat folder baru dengan nama layouts dan di dalam folder tersebut silahkan buat
file baru dengan nama auth.vue, kemudian masukkan kode berikut ini di dalamnya.
443
<template>
<div class="c-app flex-row align-items-center">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-4">
<Nuxt />
</div>
</div>
</div>
</div>
</template>
<script>
export default {
}
</script>
<style>
body {
font-family: 'Quicksand', sans-serif;
font-size: initial!important;
}
.form-control {
height: initial;
font-size: 1rem;
}
.c-app {
background-color: #BCCAD8!important;
}
</style>
444
Di dalam template, kita menambahkan kode <Nuxt />, dimana kode tersebut akan
digunakan untuk merender sebuah view yang akan menggunakan layout ini.
Dan pada style, kita menambahkan sedikit kode untuk custom CSS agar tampilan dari
website yang kita buat lebih cantik dan bagus.
<template>
<div class="fade-in">
<div class="text-center mb-4">
<nuxt-link to="/" class="text-black">
<img src="/images/logo.png" width="50">
<h3 class="mt-2 font-weight-bold">MI STORE</h3>
</nuxt-link>
</div>
<div class="card-group">
<div class="card border-top-orange border-0 shadow-sm rounded">
<div class="card-body">
<h1>Login</h1>
<p class="text-muted">Sign In to your account</p>
445
<div v-if="validation.message" class="mt-2">
<b-alert show variant="danger">{{ validation.message }}</b-
alert>
</div>
<form @submit.prevent="login">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-envelope"></i>
</span>
</div>
<input class="form-control" v-model="user.email" :class="{
'is-invalid': validation.email }" type="email" placeholder="Email
Address">
</div>
<div v-if="validation.email" class="mt-2">
<b-alert show variant="danger">{{ validation.email[0]
}}</b-alert>
</div>
<div class="input-group mb-4">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-lock"></i>
</span>
</div>
<input class="form-control" v-model="user.password"
:class="{ 'is-invalid': validation.password }" type="password"
placeholder="Password">
</div>
<div v-if="validation.password" class="mt-2">
<b-alert show variant="danger">{{ validation.password[0]
}}</b-alert>
</div>
<div class="row">
<div class="col-12">
<button class="btn btn-warning shadow-sm rounded-sm px-4
w-100" type="submit">LOGIN</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
446
<script>
export default {
//middleware
middleware: 'authenticated',
//layout
layout: 'auth',
//meta
head() {
return {
title: 'Login - Administrator',
}
},
data() {
return {
//state user
user: {
email: '',
password: '',
},
//validation
validation: []
}
},
methods: {
async login() {
await this.$auth.loginWith('admin', {
data: {
email: this.user.email,
password: this.user.password
}
})
.then(() => {
//redirect
this.$router.push({
name: 'admin-dashboard'
})
})
447
.catch(error => {
//assign validation
this.validation = error.response.data
})
}
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan middleware authenticated.
//middleware
middleware: 'authenticated',
Setelah itu, kita atur layout induk yang digunakan, disini kita akan atur dengan layout yang
sebelumnya sudah kita buat, yaitu uath.
//layout
layout: 'auth',
Setelah itu, kita juga menambahkan konfigurasi untuk meta tag title. Fungsinya untuk
mengubah title yang ada di website kita.
//meta
head() {
return {
title: 'Login - Administrator',
}
},
Dan di dalam data function kita membuat 2 state baru, yaitu user dan validation.
448
data() {
return {
//state user
user: {
email: '',
password: '',
},
//validation
validation: []
}
},
Untuk state user di dalamnya berisi key lagi, yang tujuannya untuk menampung data yang
di ketik di dalam form. Dan untuk state validation akan digunakan untuk menampung
response validasi yang di dapatkan dari Rest API.
Kemudian kita membuat 1 method baru dengan nama login. Di dalam method tersebut
kita melakukan proses login dengan konfigurasi module Nuxt Auth.
await this.$auth.loginWith('admin', {
data: {
email: this.user.email,
password: this.user.password
}
})
Di atas, kita melakukan proses login menggunakan strategy admin dan data yang
dikirimkan diambil dari state user.email dan user.password yang mana isinya diambil
dari input form.
Jika proses otentikasi berhasil, maka kita akan di redirect atau di arahkan ke dalam route
yang bernama admin-dashboard.
449
.then(() => {
//redirect
this.$router.push({
name: 'admin-dashboard'
})
})
Tapi, jika proses otentikasi gagal, maka akan melakukan assign sebuah response dari Rest
API ke dalam state validation.
.catch(error => {
//assign validation
this.validation = error.response.data
})
Di dalam template, pada bagian form action kita arahkan ke dalam method yang sudah kita
buat di atas, yaitu login.
<form @submit.prevent="login">
//...
</form>
Dan untuk input form, kita menggunakan directive v-model untuk menerima inputan dari
browser, yang mana isinya kita arahkan ke state user dengan masing-masing key.
Input Email
Input Password
450
<input class="form-control" v-model="user.password" :class="{ 'is-
invalid': validation.password }" type="password" placeholder="Password">
Untuk menampilkan error validasi, kita bisa dengan seperti berikut ini :
451
Sekarang silahkan klik LOGIN tanpa mengisi input apapun, maka kita akan mendapatkan
sebuah error validasi kurang lebih seperti berikut ini :
Validasi tersebut kita dapatkaan dari Rest API Laravel, sekarang kita coba memasukkaan
data email dan password yang belum tersedia di dalam table users. Jika berhasil maka
kita akan mendapatkan sebuah pesan error login gagal kurang lebih seperti berikut ini :
Error tersebut muncul, karena data email dan password yang kita masukkan belum
terdaftar di dalam table users. Sekarang kita lanjutkan untuk memasukkan email dan
password yang ada di dalam table users, silahkan masukkan email dan password
berikut ini :
452
INPUT VALUE
email admin@gmail.com
password password
CATATAN ! : silahkan disesuaikan dengan data email dan password yang kalian buat.
Setelah itu silahkan klik LOGIN dan jika berhasil maka kita akan mendapatkan tampilan
seperti berikut ini :
Di atas sebenarnya kita sudah berhasil melakukan proses otentikasi, error tersebut muncul
karena kita belum memiliki halaman dashboard.
453
Menampilkan Data Products
Setelah berhasil membuat Vuex untuk Admin Product, sekarang kita akan mencoba
membuat sebuah component / view yang digunakan untuk menampilkan data.
Konsepnya nanti component atau view tersebut akan memanggil action di dalam Vuex
untuk melakukan http request ke dalam endpoint, setelah data di dapatkan, kita bisa ambil
data tersebut dengan cara memanggil state yang ada di dalam Vuex.
Kita juga akan belajar untuk membuat fitur pencarian data dan membuat pagination untuk
membatasi jumlah data yang di tampilkan di setiap halaman, tujuannya agar data tidak di
tampilkan semua.
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-layer-
group"></i> PRODUCTS</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-products-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control"
placeholder="cari berdasarkan nama product">
454
<div class="input-group-append">
<button class="btn btn-warning"><i
class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Products - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [{
label: 'Product Name',
key: 'title',
},
{
label: 'Category Name',
key: 'category.name'
455
},
{
label: 'Stock',
key: 'stock',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/product/getProductsData')
},
//computed
computed: {
//products
products() {
return this.$store.state.admin.product.products
},
},
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.
//layout
layout: 'admin',
456
Setelah itu, kita juga atur untuk meta title dari view atau component ini.
//meta
head() {
return {
title: 'Products - Administrator',
}
},
Setelah itu, di dalam data function kita menambahkan 1 state baru dengan nama fields,
state tersebut akan di gunakan untuk membuat header dari sebuah table.
//table header
fields: [{
label: 'Product Name',
key: 'title',
},
{
label: 'Category Name',
key: 'category.name'
},
{
label: 'Stock',
key: 'stock',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
Dan kita menggunakan hook asyncData untuk memanggil atau dispatch ke dalam action
yang ada di Vuex, yaitu getProductsData. Action tersebut yang sudah kita buat di materi
sebelumnya, dan berada di dalam file store/admin/product.js.
457
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/product/getProductsData')
},
Jika proses fetching http request berhasil dilakukan oleh action di Vuex, maka kita akan
memanggil data dari hasil response tersebut di dalam state yang ada di Vuex, yaitu
products.
Disini kita menggunakan properti computed untuk memanggil state products yang ada di
dalam Vuex. Kurang lebih seperti berikut ini :
//computed
computed: {
//products
products() {
return this.$store.state.admin.product.products
},
},
Setelah itu, kita tinggal menampilkan data yang kita dapatkan dari computed di dalam
template. Disini kita menggunakan component dari Bootstrap Vue, yaitu <b-table>,
kurang lebih seperti berikut ini :
Di atas, untuk properti :items merupakan isi dari table yang akan ditampilkan dan disini
kita berikan sebuah value dari computer properti, yaitu products.data.
Dan untuk properti :fields akan digunakan untuk menampilkan header dari table dan
value-nya akan kita isi dengan state fields yang sebelumnya sudah kita buat di dalam
data function.
458
Langkah 2 - Mengaktifkan Link Menu
Setelah berhasil membuat view atau component, maka secara otomatis Nuxt.js juga akan
membuatkan kita route dari file yang sudah kita buat di dalam halaman pages. Sekarang
kita akan mengaktifkan menu tersebut di dalam file component Sidebar.
<li class="c-sidebar-nav-item">
<nuxt-link :to="{name: 'admin-products'}" class="c-sidebar-nav-
link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
layers"></use>
</svg> Products
</nuxt-link>
</li>
Di atas yang semula masih menggunakan a href="#" kita ubah menjadi <nuxt-link dan
untuk route-nya kita arahkan ke dalam admin-products.
Sekarang silahkan klik menu Products yang ada di menu Sidebar atau bisa langsung ke
URL berikut ini http://localhost:3000/admin/products, maka jika berhasil kita akan
mendapatkan tampilan seperti berikut ini :
459
Langkah 3 - Membuat Fitur Pencarian
Sekarang kita lanjutkan untuk membuat fitur pencarian di dalam table, fitur ini akan
mempermudah kita jika ingin mencari data tertentu.
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-layer-
group"></i> PRODUCTS</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-products-
460
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama product">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Products - Administrator',
}
},
//data function
data() {
return {
//table header
461
fields: [{
label: 'Product Name',
key: 'title',
},
{
label: 'Category Name',
key: 'category.name'
},
{
label: 'Stock',
key: 'stock',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
//state search
search: ''
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/product/getProductsData')
},
//computed
computed: {
//products
products() {
return this.$store.state.admin.product.products
},
},
//method
methods: {
//method "searchData"
searchData() {
462
this.$store.commit('admin/product/SET_PAGE', 1)
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita menambahkan state di dalam data function,
yang bertujuan untuk menampung input form pencarian.
//state search
search: ''
Setelah itu, di dalam template kita melakukan sedikit perubahan di dalam form pencarian,
kurang lebih seperti berikut ini :
Setelah itu, kita membuat method searchData, kurang lebih seperti berikut ini :
463
//method "searchData"
searchData() {
Di atas kita menjalankan 2 perintah, yang pertama melakukan commit langsung ke dalam
mutation SET_PAGE dengan paremeter / value 1.
Dan yang kedua, melakukan dispatch ke dalam action Vuex yang bernama
getProductsData dan kita parsing sebuah parameter yaitu this.search yang isinya
adalah kata kunci yang di dapatkan dari input form.
Sekarang silahkan refresh / reload halaman products dan jika berhasil maka kita bisa
mencoba proses pencarian kurang lebih seperti berikut ini :
464
Silahkan buka file pages/admin/products/index.vue, kemudian ubah semua kode-nya
menjadi seperti berikut ini :
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-layer-
group"></i> PRODUCTS</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-products-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama product">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>
465
aria-controls="my-table"></b-pagination>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Products - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [{
label: 'Product Name',
key: 'title',
},
{
label: 'Category Name',
key: 'category.name'
},
{
label: 'Stock',
key: 'stock',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
466
],
//state search
search: ''
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/product/getProductsData')
},
//computed
computed: {
//products
products() {
return this.$store.state.admin.product.products
},
},
//method
methods: {
//method "searchData"
searchData() {
467
}
</script>
<style>
</style>
Dari perubahan kode di atas, kita menambahkan component dari Bootstrap Vue yang
bernama <b-paginate> dan isinya akan diambil dari properti computed yang kita buat
sebelumnya, dimana datanya di ambil dari state products yang ada di dalam Vuex.
Dan ketika navigasi pagination di klik, maka akan menjalankan event @change, yang
mengarah ke dalam method changePage.
Sekarang kita bisa mencoba dengan melakukan refresh di halaman products atau bisa
melihatnya melalui link http://localhost:3000/admin/products dan jika berhasil maka kita
akan mendapatkan tampilan kurang lebih seperti berikut ini :
468
469
Membuat Proses Insert Data Product
Setelah berhasil menampilkan data, membuat pencarian dan sekaligus membuat fitur
pagination, maka sekarang kita akan lanjutkan belajar bagaimana cara membuat proses
insert data product ke dalam database menggunakan Rest API dan Vuex di dalam project
Nuxt.js.
//state
export const state = () => ({
//products
products: [],
//page
page: 1,
})
//mutations
export const mutations = {
//mutation "SET_PRODUCTS_DATA"
SET_PRODUCTS_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
470
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//store product
storeProduct({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
//success
.then(() => {
//resolve promise
resolve()
471
})
//error
.catch(error => {
reject(error)
})
})
},
//store product
storeProduct({ dispatch, commit }, payload) {
//...
}
Di dalam action tersebut kita melakukan http request menggunakan Axios dengan method
POST ke dalam endpoint /api/admin/products dan kita tambahkan parameter payload
yang nantinya berisi data yang dikirimkan dari component / view.
Jika proses http request untuk sending data di atas berhasil dilakukan, maka kita akan
menjalankan dispatch ke dalam action yang bernama getProductsData, tujuannya
untuk memperbarui data yang akan ditampilkan.
472
semua list category dari database.
//state
export const state = () => ({
//categories
categories: [],
//page
page: 1,
//category
category: {}
})
//mutations
export const mutations = {
//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
//mutation "SET_CATEGORY_DATA"
SET_CATEGORY_DATA(state, payload) {
473
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//store category
storeCategory({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
//success
.then(() => {
//resolve promise
474
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
//set promise
return new Promise((resolve, reject) => {
//success
.then(response => {
//resolve promise
resolve()
})
})
},
//update category
updateCategory({ dispatch, commit }, { categoryId, payload }) {
//set promise
return new Promise((resolve, reject) => {
475
payload)
//success
.then(() => {
//resolve promise
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
//destroy category
destroyCategory({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
//delete to Rest API "/api/admin/categories/:id" with method
"DELETE"
this.$axios.delete(`/api/admin/categories/${payload}`)
//success
.then(() => {
//resolve promise
resolve()
})
})
},
476
getListAllCategories({ commit, state }, payload) {
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//...
}
Di dalam action tersebut, kita melakukan http request menggunakan Axios dengan method
GET ke dalam endpoint /api/web/categories.
Jika berhasil, maka akan kita commit ke dalam mutation yang bernama
477
SET_CATEGORIES_DATA dengan memberikan parameter berupa data response dari Rest
API.
Silahkan buat folder baru dengan nama create di dalam folder page/admin/products
dan di dalam folder create tersebut silahkan buat file baru dengan nama index.vue dan
masukkan kode berikut ini di dalamnya :
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> ADD NEW PRODUCT</span>
</div>
<div class="card-body">
<form @submit.prevent="storeProduct">
<div class="form-group">
<label>GAMBAR</label>
<input type="file" @change="handleFileChange"
class="form-control">
<div v-if="validation.image" class="mt-2">
<b-alert show variant="danger">{{
validation.image[0] }}</b-alert>
</div>
</div>
478
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>NAMA PRODUCT</label>
<input type="text" v-model="product.title"
placeholder="Masukkan Nama Product"
class="form-control">
<div v-if="validation.title" class="mt-2">
<b-alert show variant="danger">{{
validation.title[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>CATEGORY</label>
<select class="form-control" v-
model="product.category_id">
<option value="">-- select category --
</option>
<option v-for="category in categories"
:key="category.id" :value="category.id">{{ category.name }}</option>
</select>
<div v-if="validation.category_id" class="mt-2">
<b-alert show variant="danger">{{
validation.category_id[0] }}</b-alert>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>WEIGHT (Gram)</label>
<input type="number" v-model="product.weight"
placeholder="Masukkan Berat Product (Gram)"
class="form-control">
<div v-if="validation.weight" class="mt-2">
<b-alert show variant="danger">{{
validation.weight[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
479
<label>STOCK</label>
<input type="number" v-model="product.stock"
placeholder="Masukkan Stock Product"
class="form-control">
<div v-if="validation.stock" class="mt-2">
<b-alert show variant="danger">{{
validation.stock[0] }}</b-alert>
</div>
</div>
</div>
</div>
<div class="form-group">
<label>DESCRIPTION</label>
<client-only placeholder="loading...">
<ckeditor-nuxt v-model="product.description"
:config="editorConfig" />
</client-only>
<div v-if="validation.description" class="mt-2">
<b-alert show variant="danger">{{
validation.description[0] }}</b-alert>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>PRICE</label>
<input type="number" v-model="product.price"
placeholder="Masukkan Harga Product"
class="form-control">
<div v-if="validation.price" class="mt-2">
<b-alert show variant="danger">{{
validation.price[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>DISCOUNT (%)</label>
<input type="number" v-model="product.discount"
placeholder="Masukkan Discount Product (%)"
class="form-control">
<div v-if="validation.discount" class="mt-2">
<b-alert show variant="danger">{{
validation.discount[0] }}</b-alert>
480
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Add New Product - Administrator',
}
},
components: {
'ckeditor-nuxt': () => {
if (process.client) {
return import('@blowstack/ckeditor-nuxt')
}
},
},
data() {
return {
481
//state product
product: {
image: '',
title: '',
category_id: '',
description: '',
weight: '',
price: '',
stock: '',
discount: ''
},
//state validation
validation: [],
//config CKEDITOR
editorConfig: {
removePlugins: ['Title'],
}
}
},
//hook "asyncData"
async asyncData({ store }) {
//computed
computed: {
//categories
categories() {
return this.$store.state.admin.category.categories
},
},
methods: {
//get image
let image = this.product.image = e.target.files[0]
//check fileType
if (!image.type.match('image.*')) {
482
//if fileType not allowed, then clear value and set null
e.target.value = ''
},
//method "storeProduct"
async storeProduct() {
//define formData
let formData = new FormData();
formData.append('image', this.product.image)
formData.append('title', this.product.title)
formData.append('category_id', this.product.category_id)
formData.append('description', this.product.description)
formData.append('weight', this.product.weight)
formData.append('price', this.product.price)
formData.append('stock', this.product.stock)
formData.append('discount', this.product.discount)
//success
.then(() => {
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
483
timer: 2000
})
})
//error
.catch(error => {
}
</script>
<style>
.ck-editor__editable {
min-height: 200px;
}
</style>
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.
//layout
layout: 'admin',
Setelah itu, kita juga atur untuk meta title dari view atau component ini.
484
//meta
head() {
return {
title: 'Add New Product - Administrator',
}
},
Kemudian kita mendaftarkan CKEDITOR di dalam properti components agar kita dapat
menggunakan CKEDITOR tersebut di dalam template.
'ckeditor-nuxt': () => {
if (process.client) {
return import('@blowstack/ckeditor-nuxt')
}
},
Dan di dalam data function kita membuat beberapa state, diantara yaitu product yang di
di dalamnya terdapat beberapa key lagi, state ini akan kita gunakan untuk menampung
sebuah value yang di kirim dari form sebelum dikirimkan ke Vuex.
//state product
product: {
image: '',
title: '',
category_id: '',
description: '',
weight: '',
price: '',
stock: '',
discount: ''
},
Masih di dalam data function, untuk state validation akan digunakan untuk menampung
sebuah error validasi yang di dapatkan dari Rest API.
485
//state validation
validation: [],
Dan kita membuat sedikit konfigurasi tambahan untuk CKEDITOR, yaitu menghapus plugin
title, karena kita tidak membutuhkan plugin tersebut di dalam kasus ini.
//config CKEDITOR
editorConfig: {
removePlugins: ['Title'],
}
Setelah itu, kita menggunakan hook asyncData untuk melakukan dispatch atau memanggil
sebuah action yang bernama getListAllCategories yang berada di dalam file
store/admin/category.js. Action tersebut akan digunakan untuk mendapatkan list
data category melalui Rest API.
//hook "asyncData"
async asyncData({ store }) {
Setelah itu, kita akan ambil data category tersebut menggunakan properti computed, dan di
dalamnya akan mengambil langsung ke dalam state yang bernama categories yang
berada di dalam store/admin/category.js.
//computed
computed: {
//categories
categories() {
return this.$store.state.admin.category.categories
},
},
486
handleFileChange
storeProduct
handleFileChange
Method ini akan dijalankan ketika input file di dalam template diubah, karena di dalam input
tersebut kita memberikan event @change yang mengarah ke dalam method ini.
Di damana di dalam method handleFileChange pertama kita akan melakukan assign file
yang dipilih dari komputer ke dalam state category.image.
//get image
let image = this.product.image = e.target.files[0]
Setelah itu, kita membuat kondisi untuk memeriksa apakah file yang diupload tersebut
menggunakan ekstensi image atau bukan. Jika file yang di pilih buka format image, maka
kita akan melakukan beberapa aksi, yang pertama mengosongkan input file di formnya
terlebih dahulu.
//if fileType not allowed, then clear value and set null
e.target.value = ''
Kemudian kita tampilkan sebuah alert atau pesan bahwa file yang akan di upload tidak
sesuai dengan yang di harapkan.
487
//show sweet alert
this.$swal.fire({
title: 'OOPS!',
text: "Format File Tidak Didukung!",
icon: 'error',
showConfirmButton: false,
timer: 2000
})
storeProduct
Method ini akan kita gunakan untuk menampung data yang diinputkan dari form sebelum
data tersebut dikirimkan ke dalam Vuex. Pertama kita melakukan inisialisasi sebuah
FormData.
//define formData
let formData = new FormData();
Kemudian, kita membuat beberapa append formData yang isinya adalah data yang di
dapatkan dari input form.
formData.append('image', this.product.image)
formData.append('title', this.product.title)
formData.append('category_id', this.product.category_id)
formData.append('description', this.product.description)
formData.append('weight', this.product.weight)
formData.append('price', this.product.price)
formData.append('stock', this.product.stock)
formData.append('discount', this.product.discount)
Setelah itu, kita akan kirim formData di atas ke dalam action Vuex yang bernama
storeProduct.
Jika proses sending data berhasil, maka kita akan menampilkan alert menggunakan Sweet
488
Alert2.
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
Dan kita arahkan atau redirect ke dalam route yang bernama admin-products.
Tapi, jika proses sending data gagal dilakukan, maka kita akan melakukan assign error
validasi yang di dapatkan dari Rest API ke dalam state yang bernama validation.
Di dalam template, untuk form action kita arahkan ke dalam method yang sudah kita buat di
atas, yaitu storeProduct.
<form @submit.prevent="storeProduct">
//...
</form>
Untuk menampilkan select option categories, kita ambil datanya dari computed properti,
dan kita menggunakan directive v-for, karena data yang ditampilkan lebih dari satu atau
jamak.
489
<option v-for="category in categories" :key="category.id"
:value="category.id">{{ category.name }}</option>
490
491
Membuat Proses Edit dan Update Data Product
Setelah berhasil membuat proses insert data ke dalam database dan juga menampilkan
data, maka sekarang kita akan lanjutkan untuk membuat fitur edit dan update data ke
dalam database menggunakan Rest API dan Vuex di dalam Nuxt.js.
//state
export const state = () => ({
//products
products: [],
//page
page: 1,
//product
product: {}
})
//mutations
export const mutations = {
//mutation "SET_PRODUCTS_DATA"
SET_PRODUCTS_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
492
//mutation "SET_PRODUCT_DATA"
SET_PRODUCT_DATA(state, payload) {
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//store product
storeProduct({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
493
//success
.then(() => {
//resolve promise
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
//set promise
return new Promise((resolve, reject) => {
//success
.then(response => {
//resolve promise
resolve()
})
})
},
//update product
updateProduct({ dispatch, commit }, { productId, payload }) {
494
//set promise
return new Promise((resolve, reject) => {
//success
.then(() => {
//resolve promise
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
Dari perubahan kode di atas, pertama kita menambahkan 1 state baru dengan nama
product, state tersebut nantinya akan kita gunakan untuk menyimpan detail data product.
//product
product: {}
495
//mutation "SET_PRODUCT_DATA"
SET_PRODUCT_DATA(state, payload) {
getDetailProduct
Action ini akan kita gunakan untuk mendapatkan detail data product berdasarkan ID, disini
kita melakukan http request menggunakan Axios dengan method GET ke dalam endpoint
/api/admin/products/:id.
Jika data berhasil ditemukan di dalam database, maka kita akan lakukan commit ke dalam
mutation yang bernama SET_PRODUCT_DATA dengan memberikan parameter berupada
data response yang di dapatkan dari Rest API.
updateProduct
Action ini akan kita gunakan untuk melakukan proses update data ke dalam database. Disini
kita melakukan http request ke dalam endpoint /api/admin/products/:id
menggunakan Axios dengan method POST. Disini kita juga menambahkan parameter
payload yang isinya nanti diambil dari component / view.
496
//store to Rest API "/api/admin/products/:id" with method "POST"
this.$axios.post(`/api/admin/products/${productId}`, payload)
Jika proses sending data berhasil dilakukan, maka kita akan menjalankan dispatch atau
memanggil sebuah action yang bernama getProductsData, tujuannya agar kita
melakukan fetching ulang dengan data yang baru.
Di atas kita menambahkan 1 button baru untuk kita gunakan dalam proses edit data,
dimana di dalam button tersebut kita arahkan ke dalam route yang bernama admin-
products-edit-id dan kita tambahkan juga parameter berupa data ID params: {id:
row.item.id}.
497
Sekarang jika kita coba refresh halaman index products, maka kita akan mendapatkan
kurang lebih tampilannya seperti berikut ini :
Silahkan buat folder baru dengan nama edit di dalam folder pages/admin/products dan
di dalam folder edit silahkan buat file baru dengan nama _id.vue dan masukkan kode
berikut ini :
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> EDIT PRODUCT</span>
</div>
<div class="card-body">
<form @submit.prevent="updateProduct">
498
<div class="form-group">
<label>GAMBAR</label>
<input type="file" @change="handleFileChange"
class="form-control">
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>NAMA PRODUCT</label>
<input type="text" v-model="product.title"
placeholder="Masukkan Nama Product"
class="form-control">
<div v-if="validation.title" class="mt-2">
<b-alert show variant="danger">{{
validation.title[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>CATEGORY</label>
<select class="form-control" v-
model="product.category_id">
<option value="">-- select category --
</option>
<option v-for="category in categories"
:key="category.id" :value="category.id">{{ category.name }}</option>
</select>
<div v-if="validation.category_id" class="mt-2">
<b-alert show variant="danger">{{
validation.category_id[0] }}</b-alert>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>WEIGHT (Gram)</label>
<input type="number" v-model="product.weight"
placeholder="Masukkan Berat Product (Gram)"
class="form-control">
<div v-if="validation.weight" class="mt-2">
<b-alert show variant="danger">{{
499
validation.weight[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>STOCK</label>
<input type="number" v-model="product.stock"
placeholder="Masukkan Stock Product"
class="form-control">
<div v-if="validation.stock" class="mt-2">
<b-alert show variant="danger">{{
validation.stock[0] }}</b-alert>
</div>
</div>
</div>
</div>
<div class="form-group">
<label>DESCRIPTION</label>
<client-only placeholder="loading...">
<ckeditor-nuxt v-model="product.description"
:config="editorConfig" />
</client-only>
<div v-if="validation.description" class="mt-2">
<b-alert show variant="danger">{{
validation.description[0] }}</b-alert>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>PRICE</label>
<input type="number" v-model="product.price"
placeholder="Masukkan Harga Product"
class="form-control">
<div v-if="validation.price" class="mt-2">
<b-alert show variant="danger">{{
validation.price[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>DISCOUNT (%)</label>
500
<input type="number" v-model="product.discount"
placeholder="Masukkan Discount Product (%)"
class="form-control">
<div v-if="validation.discount" class="mt-2">
<b-alert show variant="danger">{{
validation.discount[0] }}</b-alert>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Edit Product - Administrator',
}
},
components: {
'ckeditor-nuxt': () => {
if (process.client) {
return import('@blowstack/ckeditor-nuxt')
501
}
},
},
data() {
return {
//state product
product: {
image: '',
title: '',
category_id: '',
description: '',
weight: '',
price: '',
stock: '',
discount: ''
},
//state validation
validation: [],
//config CKEDITOR
editorConfig: {
removePlugins: ['Title'],
}
}
},
//hook "asyncData"
async asyncData({ store, route }) {
//computed
computed: {
//categories
categories() {
return this.$store.state.admin.category.categories
},
},
502
//mounted
mounted() {
this.product.title =
this.$store.state.admin.product.product.title
this.product.category_id =
this.$store.state.admin.product.product.category_id
this.product.weight =
this.$store.state.admin.product.product.weight
this.product.stock =
this.$store.state.admin.product.product.stock
this.product.description =
this.$store.state.admin.product.product.description
this.product.price =
this.$store.state.admin.product.product.price
this.product.discount =
this.$store.state.admin.product.product.discount
},
methods: {
//get image
let image = this.product.image = e.target.files[0]
//check fileType
if (!image.type.match('image.*')) {
//if fileType not allowed, then clear value and set null
e.target.value = ''
},
503
//method "updateProduct"
async updateProduct() {
//define formData
let formData = new FormData();
formData.append('image', this.product.image)
formData.append('title', this.product.title)
formData.append('category_id', this.product.category_id)
formData.append('description', this.product.description)
formData.append('weight', this.product.weight)
formData.append('price', this.product.price)
formData.append('stock', this.product.stock)
formData.append('discount', this.product.discount)
formData.append("_method", "PATCH")
//success
.then(() => {
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Diupdate!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
})
//error
.catch(error => {
504
})
}
}
}
</script>
<style>
.ck-editor__editable {
min-height: 200px;
}
</style>
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.
//layout
layout: 'admin',
Setelah itu, kita juga atur untuk meta title dari view atau component ini.
//meta
head() {
return {
title: 'Edit Product - Administrator',
}
},
Kemudian kita mendaftarkan CKEDITOR di dalam properti components agar kita dapat
menggunakan CKEDITOR tersebut di dalam template.
'ckeditor-nuxt': () => {
if (process.client) {
return import('@blowstack/ckeditor-nuxt')
}
},
Dan di dalam data function kita membuat beberapa state, diantara yaitu product yang di
505
di dalamnya terdapat beberapa key lagi, state ini akan kita gunakan untuk menampung
sebuah value yang di kirim dari form sebelum dikirimkan ke Vuex.
//state product
product: {
image: '',
title: '',
category_id: '',
description: '',
weight: '',
price: '',
stock: '',
discount: ''
},
Masih di dalam data function, untuk state validation akan digunakan untuk menampung
sebuah error validasi yang di dapatkan dari Rest API.
//state validation
validation: [],
Dan kita membuat sedikit konfigurasi tambahan untuk CKEDITOR, yaitu menghapus plugin
title, karena kita tidak membutuhkan plugin tersebut di dalam kasus ini.
//config CKEDITOR
editorConfig: {
removePlugins: ['Title'],
}
Setelah itu, kita menggunakan hook asyncData untuk memanggil atau melakukan dispatch
ke dalam action Vuex, disini kita melakukan dispatch ke dalam 2 action, yaitu :
506
//hook "asyncData"
async asyncData({ store, route }) {
Setelah itu, kita akan ambil data category tersebut menggunakan properti computed, dan di
dalamnya akan mengambil langsung ke dalam state yang bernama categories yang
berada di dalam store/admin/category.js.
//computed
computed: {
//categories
categories() {
return this.$store.state.admin.category.categories
},
},
Dan di dalam properti mounted kita melakukan assign state yang sudah kita buat di dalam
data function dengan value yang di dapatkan daari state Vuex. Data inilah yang akan
ditampilkan di dalam form edit.
507
//mounted
mounted() {
this.product.title = this.$store.state.admin.product.product.title
this.product.category_id =
this.$store.state.admin.product.product.category_id
this.product.weight = this.$store.state.admin.product.product.weight
this.product.stock = this.$store.state.admin.product.product.stock
this.product.description =
this.$store.state.admin.product.product.description
this.product.price = this.$store.state.admin.product.product.price
this.product.discount =
this.$store.state.admin.product.product.discount
},
handleFileChange
updateProduct
handleFileChange
Method ini akan dijalankan ketika input file di dalam template diubah, karena di dalam input
tersebut kita memberikan event @change yang mengarah ke dalam method ini.
Di damana di dalam method handleFileChange pertama kita akan melakukan assign file
yang dipilih dari komputer ke dalam state category.image.
//get image
let image = this.product.image = e.target.files[0]
Setelah itu, kita membuat kondisi untuk memeriksa apakah file yang diupload tersebut
menggunakan ekstensi image atau bukan. Jika file yang di pilih buka format image, maka
kita akan melakukan beberapa aksi, yang pertama mengosongkan input file di formnya
terlebih dahulu.
508
//if fileType not allowed, then clear value and set null
e.target.value = ''
Kemudian kita tampilkan sebuah alert atau pesan bahwa file yang akan di upload tidak
sesuai dengan yang di harapkan.
updateProduct
Method ini akan kita gunakan untuk menampung data yang dikirim dari form sebelum kita
kirimkan ke dalam Vuex untuk di proses menggunakan Rest API.
Pertama-tama kita akan inisialisasi sebuah FormData terlebih dahulu, tujuannya untuk
menampung data yang ada ke dalam 1 tempat.
//define formData
let formData = new FormData();
Setelah itu, kita melakukan append beberapa data di dalam formData, dimana isinya
adalah state yang kita buat di dalam data function dan value-nya adalah input dari form.
509
formData.append('image', this.product.image)
formData.append('title', this.product.title)
formData.append('category_id', this.product.category_id)
formData.append('description', this.product.description)
formData.append('weight', this.product.weight)
formData.append('price', this.product.price)
formData.append('stock', this.product.stock)
formData.append('discount', this.product.discount)
formData.append("_method", "PATCH")
Untuk proses update, jangan lupa menambahkan key _method dengan value PATCH.
Setelah itu, kita kirimkan formData tersebut ke dalam action yang ada di Vuex dengan
nama updateProduct.
Di atas kita mengirim 2 data ke dalam Vuex, yaitu product ID yang di dapatkan dari URL
dan payload yang berisi formData.
Jika proses sending data berhasil dilakukan di dalam Vuex, maka kita akan menampilkan
sebuah alert menggunakan Sweet Alert2.
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Diupdate!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
Dan akan kita arahkan / redirect ke dalam route yang bernama admin-products.
510
//redirect route "admin-products"
this.$router.push({
name: 'admin-products'
})
tapi, jika data gagal dikirimkan, maka akan melakukan assign error response validasi dari
Rest API ke dalam state validation.
Setelah itu, silahkan diubah isi sesuai dengan kebutuhan dan untuk gambar bisa kita isi atau
kosongkan, setelah itu silahkan klik UPDATE dan jika berhasil maka kita akan mendapatkan
pesan berhasil update seperti berikut ini :
511
512
Membuat Proses Delete Data Product
Sekarang kita akan lanjutkan untuk membuat proses delete data product di dalam Nuxt.js,
disini kita akan menambahkan sebuah action terlebih dahulu, action inilah yang akan
digunakan untuk melakukan http request ke dalam server menggunakan Rest API untuk
menjalankan proses delete data.
//state
export const state = () => ({
//products
products: [],
//page
page: 1,
//product
product: {}
})
//mutations
export const mutations = {
//mutation "SET_PRODUCTS_DATA"
SET_PRODUCTS_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
513
},
//mutation "SET_PRODUCT_DATA"
SET_PRODUCT_DATA(state, payload) {
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//store product
storeProduct({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
514
this.$axios.post('/api/admin/products', payload)
//success
.then(() => {
//resolve promise
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
//set promise
return new Promise((resolve, reject) => {
//success
.then(response => {
//resolve promise
resolve()
})
})
},
515
//update product
updateProduct({ dispatch, commit }, { productId, payload }) {
//set promise
return new Promise((resolve, reject) => {
//success
.then(() => {
//resolve promise
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
//destroy products
destroyProduct({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
//delete to Rest API "/api/admin/products/:id" with method
"DELETE"
this.$axios.delete(`/api/admin/products/${payload}`)
//success
.then(() => {
//resolve promise
516
resolve()
})
})
Dari penambahan kode di atas, kita menambahkan 1 action baru dengan nama
destroyProduct.
//destroy products
destroyProduct({ dispatch, commit }, payload) {
//...
}
Di dalam action tersebut, kita melakukan http request menggunakan Axios dengan method
DELETE ke dalam endpoint api/admin/products/:id.
Jika proses delete berhasil dilakukan, maka kita akan melakukan dispatch atau menjalankan
sebuah action lagi dengan nama getProductsData, tujuannya agar setelah proses delete,
data yang ditampilkan bisa diperbarui.
517
berikut ini :
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-layer-
group"></i> PRODUCTS</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-products-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama product">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>
518
</template>
</b-table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Products - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [{
label: 'Product Name',
key: 'title',
},
{
label: 'Category Name',
key: 'category.name'
},
{
label: 'Stock',
key: 'stock',
519
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
//state search
search: ''
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/product/getProductsData')
},
//computed
computed: {
//products
products() {
return this.$store.state.admin.product.products
},
},
//method
methods: {
//method "searchData"
searchData() {
520
//dispatch on action "getProductsData"
this.$store.dispatch('admin/product/getProductsData',
this.search)
},
//method "destroyProduct"
destroyProduct(id) {
this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
}).then((result) => {
if (result.isConfirmed) {
//feresh data
this.$nuxt.refresh()
//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Dihapus!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
})
}
})
}
}
}
</script>
<style>
521
</style>
Di atas kita menambahkan 1 button baru, dimana button tersebut kita berikan event
@click yang mengarah ke dalam method yang bernama destroyProduct dan di dalam
method tersebut kita berikan parameter berupa ID dari product.
this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
})
Jika button konfirmasi di klik, maka kita akan melakukan dispatch atau memanggil sebuah
action dengan nama destroyProduct yang mana action tersebut berada di dalam
store/admin/product.js. Dan di dalam dispatch kita tambahkan parameter ID dari
data product.
Jika proses delete data menggunakan Vuex sukses dilakukan, maka kita akan melakukan
refresh project Nuxt.js menggunakan kode berikut ini :
522
//feresh data
this.$nuxt.refresh()
Setelah itu, kita tampilkan alert menggunakan Sweet Alert2 untuk status sukses delete data.
//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Dihapus!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
Jika kita klik button DELETE di salah satu data yang ada, maka akan menampilkan sebuah
konfirmasi kurang lebih seperti berikut ini :
523
Dan jika kita klik YA, HAPUS!, maka data akan berhasil dihapus dan kita mendapatkan
hasil seperti berikut ini :
524
konfigurasi Vuex Admin Invoice
Sebelum kita menampilkan data invoice dan detail datanya, maka kita perlu melakukan
konfigurasi Vuex-nya terlebih dahulu, yaitu membuat state, mutation dan juga action.
Tujuan menggunakan Vuex tidak lain karena kemudahan dalam proses maintenance dan
pengembangan kedepannya.
//state
export const state = () => ({
//invoices
invoices: [],
//page
page: 1,
})
//mutations
export const mutations = {
//mutation "SET_INVOICES_DATA"
SET_INVOICES_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
525
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
Dari penambahan kode di atas, pertama kita menambahkan 2 state baru, yaitu :
invoices - yang akan digunakan untuk menyimpan data invoices yang di dapatkan
dari Rest API.
page - yang digunakan untuk menyimpan nomor pagination halaman.
//invoices
invoices: [],
//page
page: 1,
526
SET_INVOICES_DATA - digunakan untuk mengubah isi dari state invoices.
SET_PAGE - diguanakn untuk mengubah isi dari state page.
SET_INVOICES_DATA
Mutation ini akan digunakan untuk mengubah isi dari state invoices dengan data yang
dikirimkan melalui action. Dimana data tersebut akan di dapatkan dari hasil response Rest
API.
//mutation "SET_INVOICES_DATA"
SET_INVOICES_DATA(state, payload) {
SET_PAGE
Mutation ini akan digunakan untuk mengubah isi dari state page, dan datanya akan diambil
dari component / view yang mengirimkan parameter berupa nomor pagination.
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
//...
}
Di dalam action tersebut, pertama kita membuat sebuah variable baru dengan nama
search dan isinya akan diambil dari parameter yang bernama payload dan isinya nanti
akan di dapatkan dari component / view.
527
//search
let search = payload ? payload : ''
Setelah itu, kita melakukan http request menggunakan Axios dengan method GET ke
dalam endpoint /api/admin/invoices?q=""&page="". Untuk parameter q akan diisi
dari variable search dan untuk parameter page akan diambil dari state yang bernama
page.
Jika proses fetching http request berhasil dilakukan, maka kita akan lakukan commit ke
dalam mutation yang bernama SET_INVOICES_DATA dengan menambahkan parameter
berupa data response yang di dapatkan dari Rest API.
528
Menampilkan Data Invoices
Setelah menambahkan state, mutation dan action di dalam Vuex, maka sekarang kita
bisa gunakan untuk menampilkan data di dalam component / view. Setelah data berhasil
ditampilkan, kita juga akan belajar membuat fitur pencarian data beserta pagination, yaitu
menampilkan navigasi untuk berpindah-pindah ke halaman lain.
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-shopping-
cart"></i> INVOICE</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control"
placeholder="cari berdasarkan no. invoice">
<div class="input-group-append">
<button class="btn btn-warning"><i class="fa
fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>
529
<template v-slot:cell(grand_total)="row">
Rp. {{ formatPrice(row.item.grand_total) }}
</template>
<template v-slot:cell(status)="row">
<button v-if="row.item.status == 'pending'"
class="btn btn-sm btn-primary"><i class="fa fa-circle-notch fa-
spin"></i> {{ row.item.status }}</button>
<button v-if="row.item.status == 'success'"
class="btn btn-sm btn-success"><i class="fa fa-check-circle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'expired'"
class="btn btn-sm btn-warning-2"><i class="fa fa-exclamation-
triangle"></i> {{ row.item.status }}</button>
<button v-if="row.item.status == 'failed'"
class="btn btn-sm btn-danger"><i class="fa fa-times-circle"></i> {{
row.item.status }}</button>
</template>
</b-table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Invoices - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [
{
530
label: 'No. Invoice',
key: 'invoice'
},
{
label: 'Customer',
key: 'customer.name'
},
{
label: 'Grand Total',
key: 'grand_total'
},
{
label: 'Status Payment',
key: 'status',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/invoice/getInvoicesData')
},
//computed
computed: {
//invoices
invoices() {
return this.$store.state.admin.invoice.invoices
},
},
}
</script>
<style>
</style>
531
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.
//layout
layout: 'admin',
Setelah itu, kita juga atur untuk meta title dari view atau component ini.
//meta
head() {
return {
title: 'Invoices - Administrator',
}
},
Di dalam data function, kita membuat state baru dengan nama fields, state tersebut
nantinya akan digunakan dan di generate menjadi sebuah header di dalam table.
532
//table header
fields: [{
label: 'No. Invoice',
key: 'invoice'
},
{
label: 'Customer',
key: 'customer.name'
},
{
label: 'Grand Total',
key: 'grand_total'
},
{
label: 'Status Payment',
key: 'status',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
Dan untuk memanggil action Vuex, kita menggunakan hook asyncData dan di dalamnya
kita melakukan dispatch ke dalam action Vuex yang bernama getInvoicesData.
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/invoice/getInvoicesData')
},
Jika proses http request di dalam Vuex berhasil, maka kita sudah bisa mendapatkan datanya
dengan cara memanggil state yang ada di dalam Vuex.
Sekarang kita akan coba mengambil datanya menggunakan computed properti, kurang
lebih seperti berikut ini :
533
//computed
computed: {
//invoices
invoices() {
return this.$store.state.admin.invoice.invoices
},
},
Di atas, kita membuat method dengan nama invoices dan di dalamnya kita mengambil
data dari state yang bernama invoices yang berada di dalam
store/admin/invoice.js.
Dan untuk menampilkan data tersebut di dalam template, kita menggunakan component
dari Boostrap Vue, yaitu <b-table>. Kurang lebih seperti berikut ini :
//...
</b-table>
Di atas, ada properti yang bernama :items, properti tersebut akan berisi data yang ada di
dalam table dan isinya kita ambilkan dari method invoices yang ada di dalam properti
computed.
Dan untuk properti :fields akan digunakan untuk melakukan render table header dan
untuk isinya akan diambilkan dari state yang bernama fields yang kita buat di dalam data
function.
Dan di dalam <b-table> kita melakukan custom 2 data untuk di tampilkan, yang pertama
untuk menampilkan attribute grand_total, kita tampilkan menggunakan helper
formatPrice.
<template v-slot:cell(grand_total)="row">
Rp. {{ formatPrice(row.item.grand_total) }}
</template>
Dan yang kedua, kita gunakan untuk menampilkan button untuk status invoice.
534
<template v-slot:cell(status)="row">
<button v-if="row.item.status == 'pending'" class="btn btn-sm btn-
primary"><i class="fa fa-circle-notch fa-spin"></i> {{ row.item.status
}}</button>
<button v-if="row.item.status == 'success'" class="btn btn-sm btn-
success"><i class="fa fa-check-circle"></i> {{ row.item.status
}}</button>
<button v-if="row.item.status == 'expired'" class="btn btn-sm btn-
warning-2"><i class="fa fa-exclamation-triangle"></i> {{ row.item.status
}}</button>
<button v-if="row.item.status == 'failed'" class="btn btn-sm btn-
danger"><i class="fa fa-times-circle"></i> {{ row.item.status
}}</button>
</template>
535
<li class="c-sidebar-nav-item">
<nuxt-link :to="{name: 'admin-invoices'}" class="c-sidebar-nav-
link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
cart"></use>
</svg> Invoices
</nuxt-link>
</li>
Di atas yang semula masih menggunakan a href="#" kita ubah menjadi <nuxt-link dan
untuk route-nya kita arahkan ke dalam admin-invoices.
Sekarang silahkan klik menu Invoices yang ada di menu Sidebar atau bisa langsung ke
URL berikut ini http://localhost:3000/admin/invoices, maka jika berhasil kita akan
mendapatkan tampilan seperti berikut ini :
Di atas, tidak menampilkan data apapun, karena kita memang belum memiliki data di dalam
table invoices.
536
Silahkan buka pages/admin/invoices/index.vue kemudian ubah semua kode-nya
menjadi seperti berikut ini :
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-shopping-
cart"></i> INVOICE</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan no. invoice">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>
537
triangle"></i> {{ row.item.status }}</button>
<button v-if="row.item.status == 'failed'"
class="btn btn-sm btn-danger"><i class="fa fa-times-circle"></i> {{
row.item.status }}</button>
</template>
</b-table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Invoices - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [
{
label: 'No. Invoice',
key: 'invoice'
},
{
label: 'Customer',
key: 'customer.name'
},
{
label: 'Grand Total',
key: 'grand_total'
},
{
538
label: 'Status Payment',
key: 'status',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
//state search
search: ''
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/invoice/getInvoicesData')
},
//computed
computed: {
//invoices
invoices() {
return this.$store.state.admin.invoice.invoices
},
},
//method
methods: {
//method "searchData"
searchData() {
539
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita menambahkan state di dalam data function,
yang bertujuan untuk menampung input form pencarian.
//state search
search: ''
Setelah itu, di dalam template kita melakukan sedikit perubahan di dalam form pencarian,
kurang lebih seperti berikut ini :
Setelah itu, kita membuat method searchData, kurang lebih seperti berikut ini :
540
//method "searchData"
searchData() {
Di atas kita menjalankan 2 perintah, yang pertama melakukan commit langsung ke dalam
mutation SET_PAGE dengan paremeter / value 1.
Dan yang kedua melakukan dispatch ke dalam action Vuex yang bernama
getInvoicesData dan kita parsing sebuah parameter yaitu this.search yang isinya
adalah kata kunci yang di dapatkan dari input form.
INFORMASI : Karena kita belum memiliki data apapun, maka kita belum bisa mencobanya
saat ini.
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-shopping-
cart"></i> INVOICE</span>
</div>
<div class="card-body">
541
<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan no. invoice">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
542
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Invoices - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [
{
label: 'No. Invoice',
key: 'invoice'
},
{
label: 'Customer',
key: 'customer.name'
},
{
label: 'Grand Total',
key: 'grand_total'
},
{
label: 'Status Payment',
key: 'status',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
543
//state search
search: ''
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/invoice/getInvoicesData')
},
//computed
computed: {
//invoices
invoices() {
return this.$store.state.admin.invoice.invoices
},
},
//method
methods: {
//method "searchData"
searchData() {
//method "changePage"
changePage(page) {
544
}
</script>
<style>
</style>
Dari perubahan kode di atas, kita menambahkan component dari Bootstrap Vue yang
bernama <b-paginate> dan isinya akan diambil dari properti computed yang kita buat
sebelumnya, dimana datanya di ambil dari state invoices yang ada di dalam Vuex.
Kemudian kita membuat 1 method baru dengan nama changePage, kurang lebih seperti
berikut ini :
//method "changePage"
changePage(page) {
Method di atas akan dijalankan ketika kita melakukan klik pada navigasi pagination. Dan di
dalam method tersebut kita menjalankan 2 perintah.
Yang pertama, kita melakukan commit ke dalam mutation yang bernama SET_PAGE yang
berada di dalam store/admin/invoice.js dan untuk parameternya berupa nomor
pagination yang di dapatkan dari component <b-paginate>.
545
//commit to mutation "SET_PAGE"
this.$store.commit('admin/invoice/SET_PAGE', page)
Yang kedua, kita melakukan dispatch ke dalam action yang bernama getInvoicesData,
tujuannya agar data yang ditampilkan sesuai dengan nomor pagination-nya. Dan kita
tambahkan parameter this.search apabila kita melakukan navigasi di dalam hasil
pencarian.
Sekarang kita bisa mencoba dengan melakukan refresh di halaman invoices atau bisa
melihatnya melalui link http://localhost:3000/admin/invoices dan jika berhasil maka kita
akan mendapatkan tampilan kurang lebih seperti berikut ini :
546
Menampilkan Detail Data Invoice
Sebelum membuat component / view untuk menampilkan detail data invoice, maka kita
harus menambahkan sebuah state, mutation dan action terlebih dahulu di dalam Vuex.
//state
export const state = () => ({
//invoices
invoices: [],
//page
page: 1,
//invoice
invoice: {}
})
//mutations
export const mutations = {
//mutation "SET_INVOICES_DATA"
SET_INVOICES_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
547
//mutation "SET_INVOICE_DATA"
SET_INVOICE_DATA(state, payload) {
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//set promise
return new Promise((resolve, reject) => {
548
//success
.then(response => {
//resolve promise
resolve()
})
})
},
Di atas, pertama kita menambahkan 1 state lagi dengan nama invoice. State tersebut
nantinya akan kita gunakan untuk menyimpan detail data invoice.
//invoice
invoice: {}
//mutation "SET_INVOICE_DATA"
SET_INVOICE_DATA(state, payload) {
Dan di dalam action, kita menambahkan 1 method bari dengan nama getDetailInvoice.
549
//get detail invoice
getDetailInvoice({ commit }, payload) {
//...
}
Di dalam action tersebut, kita melakukan http request menggunakan Axios dengan method
GET ke dalam endpoint /api/admin/invoices/:id.
Jika proses http request tersebut berhasil dilakukan, maka kita akan melakukan commit ke
dalam mutation yang bernama SET_INVOICE_DATA dengan memberikan parameter berupa
data response dari Rest API.
Silahkan buat folder baru dengan nama show di dalam folder pages/admin/invoices dan
di dalam folder show silahkan buat file baru dengan nama _id.vue, kemudian masukkan
kode berikut ini :
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-shopping-
550
cart"></i> DETAIL INVOICE</span>
</div>
<div class="card-body">
551
</td>
<td>:</td>
<td>
{{ invoice.city.name }}
</td>
</tr>
<tr>
<td>
PROVINCE
</td>
<td>:</td>
<td>
{{ invoice.province.name }}
</td>
</tr>
<tr>
<td>
ADDRESS
</td>
<td>:</td>
<td>
{{ invoice.address }}
</td>
</tr>
<tr>
<td>
GRAND TOTAL
</td>
<td>:</td>
<td>
Rp. {{ formatPrice(invoice.grand_total) }}
</td>
</tr>
<tr>
<td>
STATUS
</td>
<td>:</td>
<td>
<button v-if="invoice.status == 'pending'"
class="btn btn-primary"><i class="fa fa-circle-
notch fa-spin"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'success'"
class="btn btn-success"><i class="fa fa-check-
circle"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'expired'"
552
class="btn btn-warning-2"><i class="fa fa-
exclamation-triangle"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'failed'"
class="btn btn-danger"><i class="fa fa-times-
circle"></i> {{ invoice.status }}</button>
</td>
</tr>
</table>
</div>
</div>
553
formatPrice(order.price) }}</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Detail Invoices - Administrator',
}
},
//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('admin/invoice/getDetailInvoice',
route.params.id)
},
//computed
computed: {
invoice() {
return this.$store.state.admin.invoice.invoice
}
},
}
</script>
<style>
554
</style>
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.
//layout
layout: 'admin',
Setelah itu, kita juga atur untuk meta title dari view atau component ini.
//meta
head() {
return {
title: 'Detail Invoices - Administrator',
}
},
Kemudian di dalam hook asyncData kita melakukan dispatch ke dalam action yang
bernama getDetailInvoice dengan memberikan parameter berupa invoice ID yang di
dapatkan dari URL browser.
//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('admin/invoice/getDetailInvoice',
route.params.id)
},
Jika proses di dalam Vuex berhasil dilakukan, maka sekarang kita tinggal memanggil
datanya dari state invoice yang ada di dalam Vuex.
Disini kita membuat computed properti yang di dalamnya kita membuat method yang
bernama invoice dan isinya memanggil state yang ada di dalam Vuex yang bernama
invoice.
555
//computed
computed: {
invoice() {
return this.$store.state.admin.invoice.invoice
}
},
Dan untuk menampilkan di dalam template, contohnya kita bisa seperti berikut ini :
<tr>
<td>
FULL NAME
</td>
<td>:</td>
<td>
{{ invoice.name }}
</td>
</tr>
Di atas, digunakan untuk menaggil attribute name dari method yang kita buat di dalam
computed properti yang bernama invoice.
Di dalam response JSON invoice kita memiliki turunan JSON lagi yaitu data orders, dan
untuk menampilkan data orders tersebut kita harus menggunakan directive v-for, karena
datanya bisa jadi lebih dari 1 atau jamak.
//...
</tr>
INFORMASI : kita belum bisa melihat hasilnya sekarang, karena kita belum memiliki data di
dalam table invoices.
556
Konfigurasi Vuex Admin Customer
Pada materi kali ini kita akan belajar untuk membuat sebuah module Vuex untuk Customer,
dimana kita akan menambahkan state, mutations dan actions.
//state
export const state = () => ({
//customers
customers: [],
//page
page: 1,
})
//mutations
export const mutations = {
//mutation "SET_CUSTOMERS_DATA"
SET_CUSTOMERS_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
//actions
export const actions = {
557
//get customers data
getCustomersData({ commit, state }, payload) {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
Dari perubahan kode di atas, pertama kita menambahkan 2 state baru, yaitu :
//customers
customers: [],
//page
page: 1,
SET_CUSTOMERS_DATA
SET_PAGE
558
SET_CUSTOMERS_DATA
Mutation ini akan digunakan untuk mengupdate nilai dari state customers dengan data
yang dikirimkan dari action, yaitu berupa data response dari Rest API.
//mutation "SET_CUSTOMERS_DATA"
SET_CUSTOMERS_DATA(state, payload) {
SET_PAGE
Mutation ini akan digunakan untuk mengubah isi dari state page dengan data yang
dikirimkan oleh component / view, yaitu berupa nomor untuk pagination halaman.
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
Dan di dalam actions kita membuat 1 method baru dengan nama getCustomersData.
//...
}
Di dalam action tersebut, pertama kita membuat sebuah variable baru dengan nama
search dan isinya akan diambil dari parameter yang bernama payload dan isinya nanti
akan di dapatkan dari component / view.
559
//search
let search = payload ? payload : ''
Kemudian kita melakukan http request menggunakan Axios dengan method GET ke dalam
endpoint /api/admin/customers?q=""&page="". Untuk parameter q akan diisi dari
variable search dan untuk parameter page akan diambil dari state yang bernama page.
Jika proses http request berhasil dilakukan, maka kita akan melakukan commit ke dalam
mutation yang bernama SET_CUSTOMERS_DATA dengan memberikan parameter berupa
data response dari Rest API.
560
Menampilkan Data Customer
Di materi kali ini kita akan belajar bagaimana cara menampilkan data customer di dalam
component / view. Dan kita juga akan belajar membuat fitur pencarian data beserta fitur
pagination.
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
user"></i> CUSTOMERS</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control"
placeholder="cari berdasarkan nama customer">
<div class="input-group-append">
<button class="btn btn-warning"><i class="fa fa-
search"></i>
SEARCH
</button>
</div>
</div>
</div>
561
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Customers - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [{
label: 'Customer Name',
key: 'name'
},
{
label: 'Email Address',
key: 'email'
},
{
label: 'Joined',
key: 'created_at'
}
],
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/customer/getCustomersData')
},
562
//computed
computed: {
//customers
customers() {
return this.$store.state.admin.customer.customers
},
},
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.
//layout
layout: 'admin',
Setelah itu, kita juga atur untuk meta title dari view atau component ini.
//meta
head() {
return {
title: 'Customers - Administrator',
}
},
Kemudian di dalam data function kita membuat 1 state dengan nama fields, state
tersebut nantinya akan digunakan untuk membuat header di dalam table.
563
//table header
fields: [{
label: 'Customer Name',
key: 'name'
},
{
label: 'Email Address',
key: 'email'
},
{
label: 'Joined',
key: 'created_at'
}
],
Dan di dalam hook asyncData, kita memanggil atau melakukan dispatch ke dalam action
Vuex yang bernama getCustomersData, dimana action tersebut berada di dalam
store/admin/customer.js.
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/customer/getCustomersData')
},
Setelah di dalam action berhasil melakukan http request ke dalam server dan mendapatkan
datanya, maka sekarang kita tinggal akses data tersebut dari state customers yang ada di
dalam Vuex.
//computed
computed: {
//customers
customers() {
return this.$store.state.admin.customer.customers
},
},
Di dalam computed properti di atas, kita membuat method dengan nama customers yang
564
isinya memanggil sebuah state yang bernama customers di dalam
store/admin/customer.js.
Setelah itu, kita akan tampilkan datanya di dalam template menggunakan component dari
Bootstrap Vue, yaitu <b-table>. Kurang lebih seperti berikut ini :
Di atas, untuk properti :items akan digunakan untuk menampilkan isi dari table dan disini
kita berikan value dari computed properti, yaitu customers.
Dan untuk properti :fields akan digunakan untuk menampilkan header dari sebuah table
dan untuk isinya kita berikan dari state yang bernama fields yang ada di dalam data
function.
565
<li class="c-sidebar-nav-item">
<nuxt-link :to="{name: 'admin-customers'}" class="c-sidebar-nav-
link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
user"></use>
</svg> Customers
</nuxt-link>
</li>
Di atas yang semula masih menggunakan a href="#" kita ubah menjadi <nuxt-link dan
untuk route-nya kita arahkan ke dalam admin-customers.
Sekarang silahkan klik menu Customers yang ada di menu Sidebar atau bisa langsung ke
URL berikut ini http://localhost:3000/admin/customers, maka jika berhasil kita akan
mendapatkan tampilan seperti berikut ini :
566
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
user"></i> CUSTOMERS</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama customer">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
567
layout: 'admin',
//meta
head() {
return {
title: 'Customers - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [{
label: 'Customer Name',
key: 'name'
},
{
label: 'Email Address',
key: 'email'
},
{
label: 'Joined',
key: 'created_at'
}
],
//state search
search: ''
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/customer/getCustomersData')
},
//computed
computed: {
//customers
customers() {
return this.$store.state.admin.customer.customers
},
},
568
//method
methods: {
//method "searchData"
searchData() {
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita menambahkan state di dalam data function,
yang bertujuan untuk menampung input form pencarian.
//state search
search: ''
Setelah itu, di dalam template kita melakukan sedikit perubahan di dalam form pencarian,
kurang lebih seperti berikut ini :
569
<input type="text" class="form-control" v-model="search"
@keypress.enter="searchData" placeholder="cari berdasarkan nama
customer">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"><i class="fa fa-
search"></i>
SEARCH
</button>
</div>
Setelah itu, kita membuat method searchData, kurang lebih seperti berikut ini :
//method "searchData"
searchData() {
Di atas kita menjalankan 2 perintah, yang pertama melakukan commit langsung ke dalam
mutation SET_PAGE dengan paremeter / value 1.
Dan yang kedua melakukan dispatch ke dalam action Vuex yang bernama
getCustomersData dan kita parsing sebuah parameter yaitu this.search yang isinya
adalah kata kunci yang di dapatkan dari input form.
Sekarang silahkan refresh / reload halaman customers dan jika berhasil maka kita bisa
mencoba proses pencarian kurang lebih seperti berikut ini :
570
Langkah 4 - Membuat Fitur Pagination
Setelah berhasil membuat fitur pencarian, sekarang kita lanjutkan untuk menambahkan fitur
pagination, fitur ini digunakan untuk menampilkan button navigasi ke halaman-halaman
yang lain.
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
user"></i> CUSTOMERS</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
571
berdasarkan nama customer">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Customers - Administrator',
}
},
//data function
data() {
return {
//table header
572
fields: [{
label: 'Customer Name',
key: 'name'
},
{
label: 'Email Address',
key: 'email'
},
{
label: 'Joined',
key: 'created_at'
}
],
//state search
search: ''
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/customer/getCustomersData')
},
//computed
computed: {
//customers
customers() {
return this.$store.state.admin.customer.customers
},
},
//method
methods: {
//method "searchData"
searchData() {
573
//method "changePage"
changePage(page) {
}
</script>
<style>
</style>
Dari perubahan kode di atas, kita menambahkan component dari Bootstrap Vue yang
bernama <b-paginate> dan isinya akan diambil dari properti computed yang kita buat
sebelumnya, dimana datanya di ambil dari state customers yang ada di dalam Vuex.
Kemudian kita membuat 1 method baru dengan nama changePage, method tersebut akan
di jalankan ketika kita melakukan navigasi di button pagination. kurang lebih seperti berikut
ini :
574
//method "changePage"
changePage(page) {
Method di atas akan dijalankan ketika kita melakukan klik pada navigasi pagination. Dan di
dalam method tersebut kita menjalankan 2 perintah.
Yang pertama, kita melakukan commit ke dalam mutation yang bernama SET_PAGE yang
berada di dalam store/admin/customer.js dan untuk parameternya berupa nomor
pagination yang di dapatkan dari component <b-paginate>.
Yang kedua, kita melakukan dispatch ke dalam action yang bernama getCustomersData,
tujuannya agar data yang ditampilkan sesuai dengan nomor pagination-nya. Dan kita
tambahkan parameter this.search apabila kita melakukan navigasi di dalam hasil
pencarian.
Sekarang kita bisa mencoba dengan melakukan refresh di halaman customers atau bisa
melihatnya melalui link http://localhost:3000/admin/customers dan jika berhasil maka kita
akan mendapatkan tampilan kurang lebih seperti berikut ini :
575
576
Konfigurasi Vuex Admin Slider
Pada tahap kali ini kita akan belajar seperti sebelum-sebelumnya, yaitu menambahkan
sebuah state, mutation dan action yang nantinya akan digunakan untuk proses
menampilkan data sliders dari Rest API.
//state
export const state = () => ({
//sliders
sliders: [],
//page
page: 1,
})
//mutations
export const mutations = {
//mutation "SET_SLIDERS_DATA"
SET_SLIDERS_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
//actions
577
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
Dari penambahan kode di atas, pertama kita membuat 2 state baru yaitu sliders dan
page.
//sliders
sliders: [],
//page
page: 1,
State sliders akan digunakan untuk menyimpan data yang di dapatkan dari Rest API,
yang nantinya akan ditampilkan di dalam component / view.
Sedangkan untuk state page, digunakan untuk menyimpan nomor pagination halaman,
secara default kita berikan angka 1.
578
Kemudian, di dalam mutations kita membuat 2 method baru, yaitu :
SET_SLIDERS_DATA
SET_PAGE
SET_SLIDERS_DATA
Mutation ini akan kita gunakan untuk mengubah isi dari state sliders dengan data yang
dikirimkan oleh action, data tersebut merupakan response dari Rest API yang berupa data
sliders dari database.
//mutation "SET_SLIDERS_DATA"
SET_SLIDERS_DATA(state, payload) {
SET_PAGE
Mutation ini digunakan untuk mengubah isi dari state page dengan nomor pagination
halaman, dimana datanya nanti akan dikirimkan langsung melalui component / view.
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
Dan di dalam actions, kita membuat 1 method baru dengan nama getSlidersData,
method tersebut yang akan digunakan untuk melakukan http request ke dalam Rest API.
//...
}
Di dalam action tersebut, pertama kita membuat sebuah variable dengan nama search,
579
yang mana isinya akan diambil dari payload yang nantinya datanya akan dikirim dari
component / view.
//search
let search = payload ? payload : ''
Setelah itu, kita melakukan http request menggunakan Axios dengan method GET ke
dalam endpoint /api/admin/sliders?q=""&page="". Untuk parameter q akan diisi dari
variable search dan untuk parameter page akan diambil dari state yang bernama page.
Jika proses fetching http request berhasil dilakukan, maka kita akan melakukan commit ke
dalam mutation yang bernama SET_SLIDERS_DATA dengan memberikan parameter berupa
response data yang didapatkan dari Rest API.
580
Membuat Layout Admin
Setelah berhasil membuat proses otentikasi di Nuxt.js, sekarang kita lanjutkan belajar untuk
membuat layout untuk halaman admin, yaitu memecah beberapa bagian template menjadi
component yang berbeda, tujuannya agar terlihat lebih rapi dan bisa digunakan secara
berulang-ulang.
<template>
<header class="c-header c-header-light c-header-fixed c-header-with-
subheader">
<button class="c-header-toggler c-class-toggler d-lg-none mfe-auto"
type="button" data-target="#sidebar"
data-class="c-sidebar-show">
<svg class="c-icon c-icon-lg">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
menu"></use>
</svg>
</button>
<button class="c-header-toggler c-class-toggler mfs-3 d-md-down-
none" type="button" data-target="#sidebar"
data-class="c-sidebar-lg-show" responsive="true">
<svg class="c-icon c-icon-lg">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
menu"></use>
</svg>
</button>
<ul class="c-header-nav ml-auto mr-4">
581
avatars.com/api/?name=${user.name}&background=4e73df&color=fffff
f&size=100`">
</div>
</a>
<div class="dropdown-menu dropdown-menu-right pt-0">
<div class="dropdown-header bg-light py-2
rounded"><strong>QUICK MENU</strong></div><a class="dropdown-item"
href="#">
<svg class="c-icon mr-2">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
folder"></use>
</svg> Categories</a><a class="dropdown-item" href="#">
<svg class="c-icon mr-2">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
layers"></use>
</svg> Products</a><a class="dropdown-item" href="#">
<svg class="c-icon mr-2">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
user"></use>
</svg> Customers</a>
<div class="dropdown-divider"></div>
<div class="dropdown-header bg-light py-2
rounded"><strong>ORDERS</strong></div><a class="dropdown-item" href="#">
<svg class="c-icon mr-2">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
cart"></use>
</svg> Invoices</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" role="button" @click="logout">
<svg class="c-icon mr-2">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-account-
logout"></use>
</svg> Logout
</a>
</div>
</li>
</ul>
</header>
</template>
<script>
582
export default {
//computed
computed: {
user() {
return this.$auth.user
}
},
//method
methods: {
//method "logout"
async logout() {
//logout auth
await this.$auth.logout()
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita membuat sebuah properti computed, yang
mana di dalamnya kita mencoba untuk mendapatkan data user yang sedang login.
583
computed: {
user() {
return this.$auth.user
}
},
Di atas kita buat method user(), yang di dalamnya melakukan return ke dalam
this.$auth.user, yang mana isinya adalah data user yang sedang login. Dan di dalam
template kita bisa menampilkan data user seperti berikut ini :
{{ user.name }}
Kemudian kita juga menambahkan 1 method baru dengan nama logout, dimana method
tersebut akan di jalankan ketika link di template di klik.
Dan di dalam method logout ini kita melakukan destroy data user yang sedang login
menggunakan kode berikut ini :
//logout auth
await this.$auth.logout()
Jika proses logout berhasil, maka kita arahkan ke dalam route admin-login.
584
Langkah 2 - Membuat Component Sidebar
Setelah berhasil membuat component untuk Header, maka sekarang kita lanjutkan untuk
membuat component untuk Sidebar, component ini akan kita gunakan untuk menampilkan
list menu yang tersedia di dalam halalaman admin nantinya.
Silahkan buat file baru dengan nama sidebar.vue di dalam folder components/admin,
kemudian masukkan kode berikut ini :
<template>
<ul class="c-sidebar-nav">
<li class="c-sidebar-nav-title">ORDERS</li>
<li class="c-sidebar-nav-item"><a href="#" class="c-sidebar-nav-
link">
<svg class="c-sidebar-nav-icon">
585
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
cart"></use>
</svg> Invoices</a>
</li>
<li class="c-sidebar-nav-title">OTHERS</li>
<li class="c-sidebar-nav-divider"></li>
</ul>
</template>
<script>
export default {
}
</script>
<style scoped>
586
a.nuxt-link-active {
background: rgba(255,255,255,.05)!important;
}
</style>
Di atas kita hanya menambahkan sintaks HTML untuk menampilkan menu yang ada di
bagian sidebar. Kemudian ada fitu yang menarik di Nuxt.js, yaittu pada bagian style kita
menambahkan sebuah class CSS yang bernama a.nuxt-link-active, dimana CSS
tersebut akan di panggil secara otomatis ketika kita melakukan navigasi di dalam menu
atau route yang di pilih.
Silahkan buat file baru dengan nama admin.vue di dalam folder layouts, setelah itu
masukkan kode berikut ini :
<template>
<div class="c-app">
<div class="c-sidebar c-sidebar-dark c-sidebar-fixed c-sidebar-lg-
show" id="sidebar">
<div class="c-sidebar-brand d-lg-down-none">
<img src="/images/xiaomi.png" class="bg-light rounded shadow-sm
p-2" width="35"> <span class="ml-2 font-weight-bold">MI STORE</span>
</div>
</div>
<div class="c-wrapper c-fixed-components">
<!-- header -->
<Header />
<!-- end header -->
<div class="c-body">
587
<!-- end content -->
<footer class="c-footer">
<div><strong>MI STORE</strong> © 2021 -
SantriKoding.com.</div>
<div class="ml-auto">Template by <a
href="https://coreui.io/">CoreUI</a></div>
</footer>
</div>
</div>
</div>
</template>
<script>
export default {
//middleware
middleware: 'isAdmin',
//register components
components: {
Header,
Sidebar
}
}
</script>
<style>
</style>
Di atas, pertama kita melakukan import 2 component yang sudah kita buat sebelumnya,
yaitu Header dan Footer.
Setelah itu, kita register kedua component tersebut agar dapat digunakan di dalam
588
template.
//register components
components: {
Header,
Sidebar
}
Dan di dalam template, untuk menampilkan component tersebut, kita cukup menggunakan
sintaks seperti berikut ini :
Untuk <Nuxt /> akan digunakan untuk merender sebuah view atau component yang
menggunakan layout ini nantinya.
Dan kita atur agar layout ini menggunakan middleware isAdmin, artinya hanya bisa
diakses jika user tersebut memiliki role atau peran sebagai admin.
//middleware
middleware: 'isAdmin',
589
Menampilkan Data Sliders
Setelah berhasil menambahkan beberapa konfigurasi di dalam module slider Vuex, maka
sekarang kita akan belajar bagaimana cara menampilkan data di dalam component / view.
Kita juga akan belajar membuat fitur pagination halaman.
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
laptop"></i> SLIDERS</span>
</div>
<div class="card-body">
</div>
</div>
</div>
590
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Sliders - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
label: 'Link Slider',
key: 'link'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/slider/getSlidersData')
},
591
//computed
computed: {
//sliders
sliders() {
return this.$store.state.admin.slider.sliders
},
},
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.
//layout
layout: 'admin',
Setelah itu, kita juga atur untuk meta title dari view atau component ini.
//meta
head() {
return {
title: 'Sliders - Administrator',
}
},
Kemudian di dalam data function, kita membuat state baru dengan nama fields, state
tersebut akan kita gunakan untuk membuat header dari sebuah table.
592
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
label: 'Link Slider',
key: 'link'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
Dan di dalam hook asyncData, kita melakukan dispatch atau memanggil action yang ada di
dalam Vuex, yang bernama getSlidersData.
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/slider/getSlidersData')
},
Jika proses dari action di atas berhasil, maka kita tinggal mengambil datanya di dalam state
sliders yang ada di Vuex.
Disini kita menggunakan computed properti untuk mendapatkan datanya secara reactive.
Di dalam computed properti kita membuat method dengan nama sliders yang di
dalamnya memanggil sebuah state di dalam Vuex yang bernama sliders.
593
//computed
computed: {
//sliders
sliders() {
return this.$store.state.admin.slider.sliders
},
},
Dan untuk menampilkan di dalam template, kita menggunakan component dari Bootstrap
Vue yang bernama <b-table>.
Di atas, untuk properti :items merupakan data yang akan di tampilkan di dalam table, dan
disini kita berikan value sliders yang merupakan nama method di dalam computed
properti.
Sedangkan untuk properti :fields akan digunakan untuk generate sebuah table header,
dan disini kita berikan value-nya dari state yang bernama fields yang sudah kita buat
sebelumnya di dalam data function.
594
<li class="c-sidebar-nav-item"><a href="#" class="c-sidebar-nav-link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
laptop"></use>
</svg> Sliders</a>
</li>
<li class="c-sidebar-nav-item">
<nuxt-link :to="{name: 'admin-sliders'}" class="c-sidebar-nav-link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
laptop"></use>
</svg> Sliders
</nuxt-link>
</li>
Di atas yang semula masih menggunakan a href="#" kita ubah menjadi <nuxt-link dan
untuk route-nya kita arahkan ke dalam admin-sliders.
Sekarang silahkan klik menu Sliders yang ada di menu Sidebar atau bisa langsung ke
URL berikut ini http://localhost:3000/admin/sliders, maka jika berhasil kita akan
mendapatkan tampilan seperti berikut ini :
595
Langkah 3 - Membuat Fitur Pagination
Sekarang kita lanjutkan untuk menambahkan fitur pagination, fitur ini digunakan untuk
menampilkan button navigasi ke halaman-halaman yang lain. Silahkan buka file
pages/admin/sliders/index.vue, kemudian ubah semua kode-nya menjadi seperti
berikut ini :
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
laptop"></i> SLIDERS</span>
</div>
<div class="card-body">
596
<b-table striped bordered hover :items="sliders.data"
:fields="fields" show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="200"
:src="data.item.image" />
</template>
</b-table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Sliders - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
597
label: 'Link Slider',
key: 'link'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/slider/getSlidersData')
},
//computed
computed: {
//sliders
sliders() {
return this.$store.state.admin.slider.sliders
},
},
//method
methods: {
//method "changePage"
changePage(page) {
}
</script>
<style>
598
</style>
Dari perubahan kode di atas, kita menambahkan component dari Bootstrap Vue yang
bernama <b-paginate> dan isinya akan diambil dari properti computed yang kita buat
sebelumnya, dimana datanya di ambil dari state sliders yang ada di dalam Vuex.
Kemudian kita membuat 1 method baru dengan nama changePage, method tersebut akan
di jalankan ketika kita melakukan navigasi di button pagination. kurang lebih seperti berikut
ini :
//method "changePage"
changePage(page) {
Method di atas akan dijalankan ketika kita melakukan klik pada navigasi pagination. Dan di
dalam method tersebut kita menjalankan 2 perintah.
Yang pertama, kita melakukan commit ke dalam mutation yang bernama SET_PAGE yang
berada di dalam store/admin/slider.js dan untuk parameternya berupa nomor
pagination yang di dapatkan dari component <b-paginate>.
Yang kedua, kita melakukan dispatch ke dalam action yang bernama getSlidersData,
tujuannya agar data yang ditampilkan sesuai dengan nomor pagination-nya. Dan kita
tambahkan parameter this.search apabila kita melakukan navigasi di dalam hasil
pencarian.
599
//dispatch on action "getSlidersData"
this.$store.dispatch('admin/slider/getSlidersData', this.search)
Sekarang kita bisa mencoba dengan melakukan refresh di halaman sliders atau bisa
melihatnya melalui link http://localhost:3000/admin/sliders dan jika berhasil maka kita akan
mendapatkan tampilan kurang lebih seperti berikut ini :
600
Membuat Proses Insert Data Slider
Sebelum membuat component / view untuk menampilkan form upload data slider, maka kita
perlu menambahkan 1 action lagi di dalam Vuex, action tersebut yang nantinya akan
digunakan untuk melakukan sending data ke dalam server melalui Rest API.
//state
export const state = () => ({
//sliders
sliders: [],
//page
page: 1,
})
//mutations
export const mutations = {
//mutation "SET_SLIDERS_DATA"
SET_SLIDERS_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
//actions
601
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//store slider
storeSlider({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
//success
.then(() => {
//resolve promise
resolve()
})
602
//error
.catch(error => {
reject(error)
})
})
},
Dari penambahan kode di atas, kita tambahkan 1 action baru dengan nama storeSlider,
action tersebutlah yang nantinya akan digunakan untuk mengirim data ke dalam server
menggunakan Rest API.
//store slider
storeSlider({ dispatch, commit }, payload) {
//...
}
Di dalam action tersebut, kita melakukan sending data menggunakan Axios dengan
method POST ke dalam endpoint /api/admin/sliders dan datanya kita parsing dengan
nama payload. Data tersebut nantinya akan dikirim dari component / view.
Jika proses sending data berhasil dilakuka, maka kita akan melakukan dispatch atau
memanggil action yang bernama getSlidersData, tujuannya agar melakukan fetching
data ulang dan mendapatkan data baru untuk ditampilkan.
603
menampilkan halaman tambah data dan sekaligus untuk proses insert data melalui Rest API.
Silahkan buat sebuah folder baru dengan nama create di dalam folder
pages/admin/sliders dan di dalam folder create silahkan buat file baru dengan nama
index.vue, kemudian masukkan kode berikut ini :
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> ADD NEW SLIDER</span>
</div>
<div class="card-body">
<form @submit.prevent="storeSlider">
<div class="form-group">
<label>GAMBAR</label>
<input type="file" @change="handleFileChange"
class="form-control">
<div v-if="validation.image" class="mt-2">
<b-alert show variant="danger">{{
validation.image[0] }}</b-alert>
</div>
</div>
<div class="form-group">
<label>LINK SLIDER</label>
<input type="text" v-model="slider.link"
placeholder="Masukkan Link Slider" class="form-control">
</div>
604
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Add New Slider - Administrator',
}
},
data() {
return {
//state slider
slider: {
image: '',
'link': ''
},
//state validation
validation: []
}
},
methods: {
//get image
let image = this.slider.image = e.target.files[0]
//check fileType
if (!image.type.match('image.*')) {
605
//if fileType not allowed, then clear value and set null
e.target.value = ''
},
//method "storeSlider"
async storeSlider() {
//define formData
let formData = new FormData();
formData.append('image', this.slider.image)
formData.append('link', this.slider.link)
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
606
})
//error
.catch(error => {
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.
//layout
layout: 'admin',
Setelah itu, kita juga atur untuk meta title dari view atau component ini.
//meta
head() {
return {
title: 'Add New Slider - Administrator',
}
},
Di dalam data function, kita membuat 2 state baru, yaitu slider dan validation.
607
//state slider
slider: {
image: '',
'link': ''
},
//state validation
validation: []
State slider akan digunakan untuk menyimpan data yang di ketik di dalam input form dan
di dalam state ini kita tambahkan 2 key di dalamnya, yaitu image dan link.
Sedangkan untuk state validation akan digunakan untuk menyimpan response error
validasi yang di dapatkan dari Rest API.
handleFileChange
storeSlider
handleFileChange
Method ini akan dijalankan ketika input file di dalam template diubah, karena di dalam input
tersebut kita memberikan event @change yang mengarah ke dalam method ini.
Di damana di dalam method handleFileChange pertama kita akan melakukan assign file
yang dipilih dari komputer ke dalam state slider.image.
//get image
let image = this.slider.image = e.target.files[0]
Setelah itu, kita membuat kondisi untuk memeriksa apakah file yang diupload tersebut
menggunakan ekstensi image atau bukan. Jika file yang di pilih buka format image, maka
kita akan melakukan beberapa aksi, yang pertama mengosongkan input file di formnya
terlebih dahulu.
608
//if fileType not allowed, then clear value and set null
e.target.value = ''
Kemudian kita tampilkan sebuah alert atau pesan bahwa file yang akan di upload tidak
sesuai dengan yang di harapkan.
storeSlider
Method ini akan digunakan untuk menampung data dari form sebelum dikirimkan ke dalam
action Vuex. Disini pertama kita melakukan inisialisasi sebuah FormData.
//define formData
let formData = new FormData();
Setelah itu, kita append data yang diambil dari input form ke dalam formData. Tujuannya
agar lebih mudah dalam pengiriman data tersebut.
formData.append('image', this.slider.image)
formData.append('link', this.slider.link)
Dan sekarang kita akan kirimkan formData tersebut ke dalam action Vuex yang bernama
609
storeSlider.
Jika proses sending berhasil dilakukan di dalam Vuex, maka kita akan menampilkan alert
sukses menggunakan Sweet Alert2.
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
Setelah itu, kita arahkan / redirect ke dalam route yang bernama admin-sliders.
610
Silahkan masukkan data gambar dan link (opsional), dan jika berhasil kita akan
mendapatkan hasil seperti berikut ini :
611
Membuat Proses Delete Data Slider
Sama seperti proses insert data slider, sebelum kita menambahkan fitur delete data slider,
maka kita perlu menambahkan action terlebih dahulu di dalam Vuex. Action tersebutlah
yang nanti bertugas untuk melakukan http request ke dalam server menggunakan Rest API
untuk menjalankan proses delete data.
//state
export const state = () => ({
//sliders
sliders: [],
//page
page: 1,
})
//mutations
export const mutations = {
//mutation "SET_SLIDERS_DATA"
SET_SLIDERS_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
612
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//store slider
storeSlider({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
//success
.then(() => {
//resolve promise
resolve()
613
})
//error
.catch(error => {
reject(error)
})
})
},
//destroy slider
destroySlider({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
//delete to Rest API "/api/admin/sliders/:id" with method
"DELETE"
this.$axios.delete(`/api/admin/sliders/${payload}`)
//success
.then(() => {
//resolve promise
resolve()
})
})
Di atas, kita menambahkan 1 action baru dengan nama destroySlider, action tersebut
yang nanti akan digunakan untuk menangani proses delete data ke dalam server
menggunakan Rest API.
614
//destroy slider
destroySlider({ dispatch, commit }, payload) {
//...
}
Di dalam action tersebut, kita melakukan http request menggunakan Axios dengan method
DELETE ke dalam endpoint /api/admin/sliders/:id.
Jika proses delete data berhasil dilakukan, maka kita akan melakukan dispatch ke dalam
action yang bernama getSlidersData, tujuannya untuk melakukan fetching ulang agar
data yang ditampilkan bisa sesuai.
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
laptop"></i> SLIDERS</span>
</div>
615
<div class="card-body">
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Sliders - Administrator',
}
},
616
//data function
data() {
return {
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
label: 'Link Slider',
key: 'link'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/slider/getSlidersData')
},
//computed
computed: {
//sliders
sliders() {
return this.$store.state.admin.slider.sliders
},
},
//method
methods: {
//method "changePage"
changePage(page) {
617
this.$store.dispatch('admin/slider/getSlidersData',
this.search)
},
//method "destroySlider"
destroySlider(id) {
this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
}).then((result) => {
if (result.isConfirmed) {
//feresh data
this.$nuxt.refresh()
//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Dihapus!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
})
}
})
}
}
}
</script>
<style>
</style>
618
Di atas kita menambahkan 1 button baru, dimana button tersebut kita berikan event
@click yang mengarah ke dalam method yang bernama destroySlider dan di dalam
method tersebut kita berikan parameter berupa ID dari slider.
this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
})
Jika kita klik button konfirmasi, maka akan melakukan dispatch ke dalam action yang
bernama destroySlider dengan parameter ID dari slider.
Dan jika di dalam action Vuex proses delete data berhasil dilakukan, maka kita akan
melakukan refresh website-nya dengan kode berikut ini :
//feresh data
this.$nuxt.refresh()
Kode di atas digunakan untuk memperbarui data di dalam website tanpa perlu melakukan
reload halaman. Dan setelah itu kita menampilkan alert menggunakan Sweet Alert2.
619
//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Dihapus!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
Silahkan klik button DELETE di salah satu data yang kita miliki, jika berhasil maka akan
menampilkan halaman konfirmasi menggunakan Sweet Alert2.
620
Dan jika kita klik YA, HAPUS!, maka data akan terhapus dan kita mendapatkan alert sukses
menggunakan Sweet Alert2.
621
Konfigurasi Vuex Admin User
Pada materi kali ini kita akan membuat module Vuex untuk user dan disini kita akan
menambahkan state, mutations dan actions untuk kita gunakan mengambil data dari
server / Rest API ke dalam Nuxt.js.
//state
export const state = () => ({
//users
users: [],
//page
page: 1,
})
//mutations
export const mutations = {
//mutation "SET_USERS_DATA"
SET_USERS_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
//actions
622
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
Dari penambahan kode di atas, pertama kita membuat 2 state baru yaitu users dan page.
//users
users: [],
//page
page: 1,
State users akan digunakan untuk menyimpan data users yang di dapatkan dari Rest API
secara reactive. Sedangkan untuk state page akan digunakan untuk menyimpan nomor
pagination halaman.
623
SET_USERS_DATA
SET_PAGE
SET_USERS_DATA
Mutation ini akan digunakan untuk melakukan assign atau mengubah isi dari state users
dengan data berupa response dari Rest API yang dikirimkan melalui action.
//mutation "SET_USERS_DATA"
SET_USERS_DATA(state, payload) {
SET_PAGE
Mutation ini akan digunakan untuk mengubah isi dari state page dengan data berupa nomor
pagination halaman yang nanti dikirim langsung dari component / view.
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
Dan di dalam actions, kita membuat 1 method baru dengan nama getUsersData, method
tersebut yang akan digunakan untuk melakukan http request ke dalam Rest API.
//...
}
Di dalam action tersebut, pertama kita membuat sebuah variable dengan nama search,
yang mana isinya akan diambil dari payload yang nantinya datanya akan dikirim dari
component / view.
624
//search
let search = payload ? payload : ''
Setelah itu, kita melakukan http request menggunakan Axios dengan method GET ke
dalam endpoint /api/admin/users?q=""&page="". Untuk parameter q akan diisi dari
variable search dan untuk parameter page akan diambil dari state yang bernama page.
Jika proses fetching http request berhasil dilakukan, maka kita akan melakukan commit ke
dalam mutation yang bernama SET_USERS_DATA dengan memberikan parameter berupa
response data yang didapatkan dari Rest API.
625
Menampilkan Data Users
Setelah menambahkan state, mutations dan actions, sekarang kita lanjutkan untuk
proses menampilkan data di dalam component / view. Alurnya nanti component / view akan
memanggil action di dalam Vuex untuk melakukan http request ke dalam server
menggunakan Rest API. Setelah itu data yang di dapatkan akan di commit ke dalam
mutation dan di dalam mutation akan melakukan assign atau update state dengan data
response tersebut.
Setelah data ada di dalam state Vuex, selanjutnya component / view memanggil state
tersebut agar mendapatkan datanya dan bisa ditampilkam ke layar browser.
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
users"></i> USERS</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-users-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control"
placeholder="cari berdasarkan nama user">
<div class="input-group-append">
626
<button class="btn btn-warning"><i class="fa
fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Users - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [{
label: 'User Name',
key: 'name'
},
627
{
label: 'Email Address',
key: 'email'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/user/getUsersData')
},
//computed
computed: {
//users
users() {
return this.$store.state.admin.user.users
},
},
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.
//layout
layout: 'admin',
Setelah itu, kita juga atur untuk meta title dari view atau component ini.
628
//meta
head() {
return {
title: 'Users - Administrator',
}
},
Dan di dalam data function kita membuat state dengan nama fields, state tersebut
nantinya akan digenerate mernjadi header dari sebuah table.
//table header
fields: [{
label: 'User Name',
key: 'name'
},
{
label: 'Email Address',
key: 'email'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
kemudian kita melakukan dispatch ke dalam action Vuex yang bernama getUsersData di
dalam hook asyncData, tujuannya agar dijalankan secara SSR atau server side rendering.
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/user/getUsersData')
},
Setelah proses di dalam action tersebut berhasil, sekarang kita tinggal mengambil data
users yang ada di dalam state Vuex. Disini kita menggunakan computed properti untuk
memanggil data dari state Vuex. Kurang lebih seperti berikut ini :
629
//computed
computed: {
//users
users() {
return this.$store.state.admin.user.users
},
},
Di dalam computed propeti kita membuat method yang bernama users yang di dalamnya
akan memanggil sebuah state yang bernama users yang berada di dalam
store/admin/user.js.
Setelah data di ambil dan diletakkan di dalam computed properti, maka sekarang kita
tinggal menampilkannya di dalam component / view. Dan disini kita menggunakan
component dari Bootstrap Vue, yaitu <b-table>, kurang lebih seperti berikut ini :
Di atas, untuk properti :items akan berisi data yang ada di dalam table dan kita akan
berikan value dengan nama method yang ada di dalam computed properti, yaitu users.
Sedangkan untuk properti :fields akan digunakan untuk menampilkan header dari table
dan disini kita berikan value dari state yang bernama fields yang sebelumnya sudah kita
buat di dalam data function.
630
<li class="c-sidebar-nav-item"><a href="#" class="c-sidebar-nav-link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
group"></use>
</svg> Users</a>
</li>
<li class="c-sidebar-nav-item">
<nuxt-link :to="{name: 'admin-users'}" class="c-sidebar-nav-link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
group"></use>
</svg> Users
</nuxt-link>
</li>
Di atas yang semula masih menggunakan a href="#" kita ubah menjadi <nuxt-link dan
untuk route-nya kita arahkan ke dalam admin-users.
Sekarang silahkan klik menu Users yang ada di menu Sidebar atau bisa langsung ke URL
berikut ini http://localhost:3000/admin/users, maka jika berhasil kita akan mendapatkan
tampilan seperti berikut ini :
631
Langkah 3 - Membuat Fitur Pencarian
Sekarang kita lanjutkan untuk membuat fitur pencarian di dalam table, fitur ini akan
mempermudah kita jika ingin mencari data tertentu.
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
users"></i> USERS</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-users-
632
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama user">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Users - Administrator',
}
},
633
//data function
data() {
return {
//table header
fields: [{
label: 'User Name',
key: 'name'
},
{
label: 'Email Address',
key: 'email'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
//state search
search: ''
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/user/getUsersData')
},
//computed
computed: {
//users
users() {
return this.$store.state.admin.user.users
},
},
//method
methods: {
//method "searchData"
searchData() {
634
//dispatch on action "getUsersData"
this.$store.dispatch('admin/user/getUsersData', this.search)
},
}
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita menambahkan state di dalam data function,
yang bertujuan untuk menampung input form pencarian.
//state search
search: ''
Setelah itu, di dalam template kita melakukan sedikit perubahan di dalam form pencarian,
kurang lebih seperti berikut ini :
Setelah itu, kita membuat method searchData, kurang lebih seperti berikut ini :
635
//method "searchData"
searchData() {
Di atas kita menjalankan 2 perintah, yang pertama melakukan commit langsung ke dalam
mutation SET_PAGE dengan paremeter / value 1.
Dan yang kedua melakukan dispatch ke dalam action Vuex yang bernama getUsersData
dan kita parsing sebuah parameter yaitu this.search yang isinya adalah kata kunci yang
di dapatkan dari input form.
Sekarang silahkan refresh / reload halaman users dan jika berhasil maka kita bisa mencoba
proses pencarian kurang lebih seperti berikut ini :
636
Silahkan buka file pages/admin/users/index.vue, kemudian ubah semua kode-nya
menjadi seperti berikut ini :
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
users"></i> USERS</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-users-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama user">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>
637
<!-- pagination -->
<b-pagination align="right" :value="users.current_page"
:total-rows="users.total"
:per-page="users.per_page" @change="changePage" aria-
controls="my-table"></b-pagination>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Users - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [{
label: 'User Name',
key: 'name'
},
{
label: 'Email Address',
key: 'email'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
638
//state search
search: ''
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/user/getUsersData')
},
//computed
computed: {
//users
users() {
return this.$store.state.admin.user.users
},
},
//method
methods: {
//method "searchData"
searchData() {
}
</script>
<style>
639
</style>
Dari perubahan kode di atas, kita menambahkan component dari Bootstrap Vue yang
bernama <b-paginate> dan isinya akan diambil dari properti computed yang kita buat
sebelumnya, dimana datanya di ambil dari state users yang ada di dalam Vuex.
Kemudian kita membuat 1 method baru dengan nama changePage, method tersebut akan
di jalankan ketika kita melakukan navigasi di button pagination. kurang lebih seperti berikut
ini :
//method "changePage"
changePage(page) {
Method di atas akan dijalankan ketika kita melakukan klik pada navigasi pagination. Dan di
dalam method tersebut kita menjalankan 2 perintah.
Yang pertama, kita melakukan commit ke dalam mutation yang bernama SET_PAGE yang
berada di dalam store/admin/user.js dan untuk parameternya berupa nomor
pagination yang di dapatkan dari component <b-paginate>.
Yang kedua, kita melakukan dispatch ke dalam action yang bernama getUsersData,
tujuannya agar data yang ditampilkan sesuai dengan nomor pagination-nya. Dan kita
tambahkan parameter this.search apabila kita melakukan navigasi di dalam hasil
pencarian.
640
//dispatch on action "getUsersData"
this.$store.dispatch('admin/user/getUsersData', this.search)
Sekarang kita bisa mencoba dengan melakukan refresh di halaman users atau bisa
melihatnya melalui link http://localhost:3000/admin/users dan jika berhasil maka kita akan
mendapatkan tampilan kurang lebih seperti berikut ini :
641
Membuat Proses Insert Data User
Sekarang kita akan lanjutkan belajar bagaimana cara membuat proses insert data user baru
ke dalam database di dalam Nuxt.js. Sebelum itu, kita akan menambahkan sebuah action
baru terlebih dahulu di dalam Vuex, action tersebutlah yang akan digunakan untuk
mengirim data ke dalam server untuk disimpan ke dalam database.
//state
export const state = () => ({
//users
users: [],
//page
page: 1,
})
//mutations
export const mutations = {
//mutation "SET_USERS_DATA"
SET_USERS_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
642
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//store user
storeUser({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
//success
.then(() => {
//resolve promise
resolve()
643
})
//error
.catch(error => {
reject(error)
})
})
},
Dari perubahan kode di atas, pertama kita menambahkan action baru dengan nama
storeUser.
//store user
storeUser({ dispatch, commit }, payload) {
//...
}
Di dalam action tersebut, kita melakukan sending data menggunakan Axios dengan
method POST ke dalam endpoint /api/admin/users dan untuk parameter-nya adalah
payload yang nanti akan berisi data user yang dikirim dari component / view.
Jika proses sending data berhasil dilakukan, maka kita akan melakukan dispatch ke dalam
action yang bernama getUsersData, dengan tujuan agar melakukan fetching ulang
dengan data yang terbaru.
644
Langkah 2 - Membuat View Tambah User
Disini kita akan membuat sebuah file view atau component baru yang akan digunakan untuk
menampilkan halaman tambah data dan sekaligus untuk proses insert data melalui Rest API.
Silahkan buat sebuah folder baru dengan nama create di dalam folder
pages/admin/users dan di dalam folder create silahkan buat file baru dengan nama
index.vue, kemudian masukkan kode berikut ini :
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
users"></i> ADD NEW USER</span>
</div>
<div class="card-body">
<form @submit.prevent="storeUser">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>FULL NAME</label>
<input type="text" v-model="user.name"
placeholder="Masukkan Nama User" class="form-control">
<div v-if="validation.name" class="mt-2">
<b-alert show variant="danger">{{
validation.name[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>EMAIL ADDRESS</label>
<input type="email" v-model="user.email"
placeholder="Masukkan Email Address"
class="form-control">
<div v-if="validation.email" class="mt-2">
<b-alert show variant="danger">{{
645
validation.email[0] }}</b-alert>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>PASSWORD</label>
<input type="password" v-model="user.password"
placeholder="Masukkan Password"
class="form-control">
<div v-if="validation.password" class="mt-2">
<b-alert show variant="danger">{{
validation.password[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>PASSWORD CONFIRMATION</label>
<input type="password" v-
model="user.password_confirmation"
placeholder="Masukkan Konfirmasi Password"
class="form-control">
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
646
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Add New User - Administrator',
}
},
data() {
return {
//state user
user: {
name: '',
email: '',
password: '',
password_confirmation: '',
},
//state validation
validation: []
}
},
methods: {
//method "storeUser"
async storeUser() {
//define formData
let formData = new FormData();
formData.append('name', this.user.name)
formData.append('email', this.user.email)
formData.append('password', this.user.password)
formData.append('password_confirmation',
this.user.password_confirmation)
647
//success
.then(() => {
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
})
//error
.catch(error => {
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.
//layout
layout: 'admin',
Setelah itu, kita juga atur untuk meta title dari view atau component ini.
648
//meta
head() {
return {
title: 'Add New User - Administrator',
}
},
Kemudian di dalam data function kita menambahkan 2 state baru, yaitu user dan
validation. Untuk state user akan digunakan untuk menyimpan data yang dikirim dari
input form. Sedangkan state validation akan digunakan untuk menyimpan error response
validasi yang di dapatkan dari Rest API.
//state user
user: {
name: '',
email: '',
password: '',
password_confirmation: '',
},
//state validation
validation: []
Dan kita membuat 1 method baru dengan nama storeUser, method tersebut akan
dijalankan ketika form di dalam template di submit.
<form @submit.prevent="storeUser">
//...
</form>
//method "storeUser"
async storeUser() {
//...
}
649
Di dalam method tersebut, pertama kita melakukan inisialisasi FormData, tujuannya untuk
memudahkan dalam mengelompokan data yang akan dikirim ke dalam action Vuex.
//define formData
let formData = new FormData();
Setelah itu, kita melakukan append formData yang berisi data yang didapatkan dari input
form.
formData.append('name', this.user.name)
formData.append('email', this.user.email)
formData.append('password', this.user.password)
formData.append('password_confirmation',
this.user.password_confirmation)
Kemudian kita kirim formData tersebut ke dalam action Vuex dengan nama storeUser.
Jika proses sending data di dalam action Vuex sukses dilakukan, maka kita akan
menampilkam alert sukses menggunakan Sweet Alert2.
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
Setelah itu, kita redirect / arahkan ke dalam route yang bernama admin-users.
650
//redirect route "admin-users"
this.$router.push({
name: 'admin-users'
})
Tapi jika data yang dikirim gagal disimpan, maka akan melakukan assign ke dalam state
validation dengan isi response error validasi.
Silahkan masukkan data user baru dan klik SAVE, jika berhasil maka akan mendapatkan
alert dan akan diarahkan ke halaman index users.
651
652
Membuat Proses Edit dan Update Data User
Setelah berhasil membuat proses insert data user baru, sekarang kita akan lanjutkan belajar
bagaimana cara melakukan proses edit dan update data ke dalam database di dalam
Nuxt.js. Sebelum itu, tentu kita akan menambahkan state, mutation dan action terlebih
dahulu di dalam Vuex.
//state
export const state = () => ({
//users
users: [],
//page
page: 1,
//user
user: {}
})
//mutations
export const mutations = {
//mutation "SET_USERS_DATA"
SET_USERS_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
653
},
//mutation "SET_USER_DATA"
SET_USER_DATA(state, payload) {
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//store user
storeUser({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
654
this.$axios.post('/api/admin/users', payload)
//success
.then(() => {
//resolve promise
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
//set promise
return new Promise((resolve, reject) => {
//success
.then(response => {
//resolve promise
resolve()
})
})
},
//update user
655
updateUser({ dispatch, commit }, { userId, payload }) {
//set promise
return new Promise((resolve, reject) => {
//success
.then(() => {
//resolve promise
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
Dari penambahan kode di atas, pertama kita menambahkan 1 state baru dengan nama
user. State tersebut akan digunakan untuk menyimpan detail data user yang didapatkan
dari Rest API.
//user
user: {}
656
//mutation "SET_USER_DATA"
SET_USER_DATA(state, payload) {
getDetailUser
updateUser
getDetailUser
Action ini akan digunakan untuk mendapatkan detail data user yang akan di edit, dimana di
dalam action ini kita melakukan fetching menggunakan Axios dengan method GET ke
dalam endpoint /api/admin/users/:id.
Jika proses fetching data tersebut berhasil dilakukan, maka kita akan melakukan commit ke
dalam mutation yang bernama SET_USER_DATA dengan parameter berupa response data
dari Rest API.
updateUser
Action ini akan digunakan untuk melakukan proses update data user ke dalam server
menggunakan Rest API. Disini kita melakukan sending data menggunakan Axios dengan
method POST ke dalam endpoint /api/admin/users/:id. Dan kita tambahkan parameter
berupa payload, dimana isinya adalah data user yang dikirimkan dari component / view.
657
//store to Rest API "/api/admin/users/:id" with method "POST"
this.$axios.post(`/api/admin/users/${userId}`, payload)
Jika proses sending data di atas berhasil dilakukan, maka kita akan menjalankan /
melakukan dispatch ke dalam action yang bernama getUsersData, dengan tujuan agar
melakukan fetching ulang dengan data yang terbaru.
658
<b-table striped bordered hover :items="users.data" :fields="fields"
show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="50" :src="data.item.image" />
</template>
<template v-slot:cell(actions)="row">
<b-button :to="{name: 'admin-users-edit-id', params: {id:
row.item.id}}" variant="info" size="sm">
EDIT
</b-button>
</template>
</b-table>
Di atas kita menambahkan 1 button baru untuk kita gunakan dalam proses edit data,
dimana di dalam button tersebut kita arahkan ke dalam route yang bernama admin-
users-edit-id dan kita tambahkan juga parameter berupa data ID params: {id:
row.item.id}.
Sekarang jika kita coba refresh halaman index users, maka kita akan mendapatkan kurang
lebih tampilannya seperti berikut ini :
659
Silahkan buat folder baru dengan nama edit di dalam folder pages/admin/users dan di
dalam folder edit silahkan buat file baru dengan nama _id.vue dan masukkan kode
berikut ini :
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
users"></i> EDIT USER</span>
</div>
<div class="card-body">
<form @submit.prevent="updateUser">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>FULL NAME</label>
<input type="text" v-model="user.name"
placeholder="Masukkan Nama User" class="form-control">
<div v-if="validation.name" class="mt-2">
<b-alert show variant="danger">{{
validation.name[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>EMAIL ADDRESS</label>
<input type="email" v-model="user.email"
placeholder="Masukkan Email Address"
class="form-control">
<div v-if="validation.email" class="mt-2">
<b-alert show variant="danger">{{
validation.email[0] }}</b-alert>
</div>
</div>
</div>
</div>
660
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>PASSWORD</label>
<input type="password" v-model="user.password"
placeholder="Masukkan Password"
class="form-control">
<div v-if="validation.password" class="mt-2">
<b-alert show variant="danger">{{
validation.password[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>PASSWORD CONFIRMATION</label>
<input type="password" v-
model="user.password_confirmation"
placeholder="Masukkan Konfirmasi Password"
class="form-control">
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
661
layout: 'admin',
//meta
head() {
return {
title: 'Edit User - Administrator',
}
},
data() {
return {
//state user
user: {
name: '',
email: '',
password: '',
password_confirmation: '',
},
//state validation
validation: []
}
},
//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('admin/user/getDetailUser',
route.params.id)
},
//mounted
mounted() {
this.user.name = this.$store.state.admin.user.user.name
this.user.email = this.$store.state.admin.user.user.email
},
methods: {
//method "updateUser"
async updateUser() {
//define formData
let formData = new FormData();
formData.append('name', this.user.name)
formData.append('email', this.user.email)
662
formData.append('password', this.user.password)
formData.append('password_confirmation',
this.user.password_confirmation)
formData.append("_method", "PATCH")
//success
.then(() => {
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Diupdate!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
})
//error
.catch(error => {
}
</script>
<style>
</style>
663
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.
//layout
layout: 'admin',
Setelah itu, kita juga atur untuk meta title dari view atau component ini.
//meta
head() {
return {
title: 'Edit User - Administrator',
}
},
Kemudian di dalam data function kita menambahkan 2 state baru, yaitu user dan
validation. Untuk state user akan digunakan untuk menyimpan data yang dikirim dari
input form. Sedangkan state validation akan digunakan untuk menyimpan error response
validasi yang di dapatkan dari Rest API.
//state user
user: {
name: '',
email: '',
password: '',
password_confirmation: '',
},
//state validation
validation: []
Dan di dalam hook asyncData kita melakukan dispatch atau memanggil sebuah action
Vuex yang bernama getDetailUser dengan parameter ID user yang di dapatkan dari
URL.
664
//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('admin/user/getDetailUser', route.params.id)
},
Jika proses di dalam Vuex di atas berhasil dilakukan, sekarang kita tinggal mengambil
datanya dari state yang ada di Vuex dan kita assign ke dalam state yang ada di dalam daata
function menggunakan mounted properti.
//mounted
mounted() {
this.user.name = this.$store.state.admin.user.user.name
this.user.email = this.$store.state.admin.user.user.email
},
kemudian kita membuat 1 method dengan nama updateUser, method tersebut akan
dijalankan ketika form di dalam template di submit.
<form @submit.prevent="updateUser">
//..
</form>
//method "updateUser"
async updateUser() {
//...
}
Di dalam method di atas, pertama kita melakukan inisialisasi sebuah FormData, fungsinya
untuk mempermudah dalam pengiriman sebuah data ke dalam Vuex.
//define formData
let formData = new FormData();
665
Setelah itu, kita melakukan append dari data yang di dapatkan dari input form ke dalam
formData.
formData.append('name', this.user.name)
formData.append('email', this.user.email)
formData.append('password', this.user.password)
formData.append('password_confirmation',
this.user.password_confirmation)
formData.append("_method", "PATCH")
Untuk proses update, jangan lupa menambahkan key _method dengan value PATCH.
Sekarang kita kirimkan formData tersebut ke dalam action Vuex yang bernama
updateUser.
Di atas, kita tidak hanya mengirimkan formData saja, melainkan ada parameter lain, yaitu
userID yang isinya adalah ID user yang didapatkan dari URL.
Jika proses update di dalam action Vuex berhasil dilakukan, maka kita akan menampilkan
alert sukses update menggunakan Sweet Alert2.
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Diupdate!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
666
//redirect route "admin-users"
this.$router.push({
name: 'admin-users'
})
Tapi, jika proses update gagal dilakukan, maka akan melakukan assign error response
validasi ke dalam state validation.
Setelah itu, silahkan diubah isi sesuai dengan kebutuhan, setelah itu silahkan klik UPDATE
dan jika berhasil maka kita akan mendapatkan pesan berhasil update seperti berikut ini :
667
668
Membuat Proses Delete Data User
Setelah berhasil membuat proses edit dan update data user, sekarang kita akan lanjutkan
untuk membuat proses delete data user dari datanase. Sebelum itu, kita akan
menambahkan sebuah action terlebih dahulu di dalam Vuex untuk proses http request
delete data menggunakan Rest API.
//state
export const state = () => ({
//users
users: [],
//page
page: 1,
//user
user: {}
})
//mutations
export const mutations = {
//mutation "SET_USERS_DATA"
SET_USERS_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
669
},
//mutation "SET_USER_DATA"
SET_USER_DATA(state, payload) {
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//store user
storeUser({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
670
this.$axios.post('/api/admin/users', payload)
//success
.then(() => {
//resolve promise
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
//set promise
return new Promise((resolve, reject) => {
//success
.then(response => {
//resolve promise
resolve()
})
})
},
//update user
671
updateUser({ dispatch, commit }, { userId, payload }) {
//set promise
return new Promise((resolve, reject) => {
//success
.then(() => {
//resolve promise
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
//destroy user
destroyUser({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
//delete to Rest API "/api/admin/users/:id" with method
"DELETE"
this.$axios.delete(`/api/admin/users/${payload}`)
//success
.then(() => {
//resolve promise
resolve()
672
})
})
Dari perubahan kode di atas, kita menambahkan 1 action baru dengan nama destroyUser.
//destroy user
destroyUser({ dispatch, commit }, payload) {
//...
}
Di dalam action tersebut, kita melakukan http request menggunakanAxios dengan method
DELETE ke dalam endpoint /api/admin/users/:id.
Jika dari proses http request delete data di atas berjalan dengan sukses, maka kita akan
melakukan dispatch ke dalam sebuah action yang bernama getUsersData, dengan tujuan
agar melakukan fetching ulang dengan data yang telah diperbarui.
673
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
users"></i> USERS</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-users-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama user">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>
674
<b-button variant="danger" size="sm"
@click="destroyUser(row.item.id)">DELETE</b-button>
</template>
</b-table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Users - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [{
label: 'User Name',
key: 'name'
},
{
label: 'Email Address',
key: 'email'
},
{
675
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
//state search
search: ''
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/user/getUsersData')
},
//computed
computed: {
//users
users() {
return this.$store.state.admin.user.users
},
},
//method
methods: {
//method "searchData"
searchData() {
676
//method "destroyUser"
destroyUser(id) {
this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
}).then((result) => {
if (result.isConfirmed) {
//feresh data
this.$nuxt.refresh()
//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Dihapus!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
})
}
})
}
}
}
</script>
<style>
</style>
Di atas kita menambahkan 1 button baru, dimana button tersebut kita berikan event
@click yang mengarah ke dalam method yang bernama destroyUser dan di dalam
677
method tersebut kita berikan parameter berupa ID dari user.
this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
})
Jika kita klik button konfirmasi, maka akan melakukan dispatch ke dalam action yang
bernama destroyUser dengan parameter ID dari user.
Dan jika di dalam action Vuex proses delete data berhasil dilakukan, maka kita akan
melakukan refresh website-nya dengan kode berikut ini :
//feresh data
this.$nuxt.refresh()
Kode di atas digunakan untuk memperbarui data di dalam website tanpa perlu melakukan
reload halaman. Dan setelah itu kita menampilkan alert menggunakan Sweet Alert2.
678
//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Dihapus!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
Silahkan klik button DELETE di salah satu data yang kita miliki, jika berhasil maka akan
menampilkan halaman konfirmasi menggunakan Sweet Alert2.
679
Dan jika kita klik YA, HAPUS!, maka data akan terhapus dan kita mendapatkan alert sukses
menggunakan Sweet Alert2.
680
Membuat Halaman Dashboard
Sekarang kita akan lanjutkan untuk membuat halaman dashboard untuk admin, halaman ini
yang akan pertama kali di tampilkan setelah berhasil melakukan proses otentikasi. Dan
nantinya di dalam halaman ini akan kita gunakan untuk menampilkan beberapa data,
seperti statistik dan pendapatan berupa chart.
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
681
</div>
<div>
<div class="text-value text-success">{{
statistic.success }}</div>
<div class="text-muted text-uppercase font-weight-bold
small">SUCCESS</div>
</div>
</div>
</div>
</div>
</div>
682
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-chart-
bar"></i> GRAFIK PENDAPATAN {{new Date().getFullYear()}}</span>
</div>
<div class="card-body">
<client-only>
<line-chart :data="chart.chartData"></line-chart>
</client-only>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Dashboard - Administrator',
}
},
//fetching dashboard
const dashboard = await $axios.$get('/api/admin/dashboard')
//statistic
const statistic = {
'pending': dashboard.data.count.pending,
'success': dashboard.data.count.success,
'expired': dashboard.data.count.expired,
'failed': dashboard.data.count.failed,
683
}
//cart
const chart = {
chartData: {
labels: dashboard.data.chart.month_name,
datasets: [
{
label: `STATISTIK PENDAPATAN : ${new
Date().getFullYear()}`,
backgroundColor: '#bccad8',
data: dashboard.data.chart.grand_total
},
]
}
}
return {
statistic,
chart
}
},
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.
//layout
layout: 'admin',
Setelah itu, kita juga atur untuk title dari websitenya, kurang lebih seperti berikut ini :
684
//meta
head() {
return {
title: 'Dashboard - Administrator',
}
},
Kemudian kita menggunakan hook asyncData untuk melakukan fething dengan methode
SSR atau Server Side Rendering, jadi halaman atau template tidak akan di tampilkan,
sebelum proses di dalam hook ini selesai.
Dimana di dalam hook asyncData kita melakukan fething ke dalam sebuah Rest API
dengan endpoint /api/admin/dashboard menggunakan Axios dengan method GET.
//fetching dashboard
const dashboard = await $axios.$get('/api/admin/dashboard')
Setelah itu, kita membuat 2 variable baru yang akan digunakan untuk menampung hasil
response dari Rest API, yaitu statistic dan chart.
//statistic
const statistic = {
'pending': dashboard.data.count.pending,
'success': dashboard.data.count.success,
'expired': dashboard.data.count.expired,
'failed': dashboard.data.count.failed,
}
Di atas kita membuat beberapa key yang isinya mengambil response dari Rest API berupa
statistik data invoice, yaitu jumlah pending, success, expired dan failed.
685
//cart
const chart = {
chartData: {
labels: dashboard.data.chart.month_name,
datasets: [{
label: `STATISTIK PENDAPATAN : ${new Date().getFullYear()}`,
backgroundColor: '#bccad8',
data: dashboard.data.chart.grand_total
}, ]
}
}
Di atas, untuk labels akan berisi data response dari nama bulan yang di dapatkan dari
Rest API. Dan untuk data akan berupa data grand_total yang di dapatkan juga dari Rest
API.
Setelah itu, agar kedua variable di atas dapat digunakan dan di panggil di dalam template,
maka kita perlu melakukan return terlebih dahulu.
return {
statistic,
chart
}
686
687
Konfigurasi Vuex Admin Category
Sekarang, kita akan belajar membuat module baru untuk mengelola data category di dalam
project Nuxt.js. Ini bertujuan agar kode-kode store kita, tidak tercampur menjadi satu
dengan yang lain, dan dengan menerapkan module-module di dalam Vuex, kita bisa lebih
mudah dalam melakukan maintenance dan pengembangan.
Sebelum membuat konfigurasi Vuex, maka kita perlu tau dulu bagaimana cara Vuex ini
bekerja. Dan untuk gambaran alur kerjanya, kurang lebih seperti berikut ini :
Dari gambar di atas, pertama component / view melakukan dispatch ke dalam sebuah
action dan di dalam action akan melakukan http request ke dalam sebuah endpoint dan
hasil dari request tersebut akan di commit ke dalam mutation dan di dalam mutation
akan melakukan update sebuah state. Dan component / view tersebut baru bisa
menampilkan data dari state yang di dapatkan dari Rest API.
688
menambahkan sebuah actions, mutation dan state.
Silahkan buat folder baru dengan nama admin di dalam folder store. Dan di dalam folder
admin silahkan buat file baru dengan nama category.js, kemudian masukkan kode
berikut ini :
//state
export const state = () => ({
//categories
categories: [],
//page
page: 1,
})
//mutations
export const mutations = {
//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
//actions
export const actions = {
//search
let search = payload ? payload : ''
689
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
1. state.
2. mutations.
3. actions.
state
State digunakan untuk menyimpan sebuah data yang bersifat reactive/realtime dan
umumnya akan digunakan untuk menyimpan data dari hasil yang di dapatkan dari sebuah
response Rest API. Disini kita membuat 2 state baru, yaitu
Untuk state categories akan digunakan untuk menampung data response yang di
dapatkaan dari Rest API berupa list data categories. Dan nantinya di dalam component /
view kita tinggal memanggil state ini untuk menampilkan data-nya.
690
//categories
categories: [],
Dan untuk state page akan digunakan untuk menampung nilai dari pagination, dan untuk
default-nya kita berikan angka 1.
//page
page: 1,
mutations
Mutation digunakan untuk mengubah isi dari sebuah state, disini kita membuat 2 mutation,
yaitu
Untuk mutation SET_CATEGORIES_DATA akan digunakan untuk mengubah isi dari state
categories dan datanya akan di dapatkan dari response Rest API yang di kirim dari
actions.
//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {
Dan untuk mutation SET_PAGE akan digunakan untuk mengubah isi dari state page, untuk
datanya nanti akan dinamis sesuai yang dikirimkan melalui component / view.
691
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
actions
Action digunakan untuk melakukan http request ke dalam sebuah endpoint tertentu, dan
disini kita membuat sebuah action yang bernama getCategoriesData.
//...
}
Dan di dalamnya pertama kita membuat sebuah variable untuk menyimpan keyword
pencarian data.
//search
let search = payload ? payload : ''
Setelah itu, kita melakukan http request ke server menggunakan Axios dengan method
GET ke dalam endpoint /api/admin/categories?q=""&page="" dan di dalam endpoint
tersebut kita tambahkan 2 parameter yang bersifat opsional, yaitu q untuk pencarian data
dan page untuk pagination.
Untuk parameter q akan diisi dari variable search dan untuk parameter page akan diambil
dari state yang bernama page.
Jika proses request ke dalam endpoint tersebut berhasil, maka kita akan melakukan commit
ke dalam mutation yang bernama SET_CATEGORIES_DATA dan parameternya adalah
response data yang di dapatkan dari Rest API.
692
//commit ti mutation "SET_CATEGORIES_DATA"
commit('SET_CATEGORIES_DATA', response.data.data)
693
Menampilkan Data Categories
Pada materi kali ini kita semua akan belajar bagaimana cara menampilkan data categories
dari database di dalam Nuxt.js, Disini kita akan memanfaatkan Vuex untuk mendapatkan
data dari Rest API.
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> CATEGORIES</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-categories-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control"
placeholder="cari berdasarkan nama category">
<div class="input-group-append">
<button class="btn btn-warning"><i
class="fa fa-search"></i>SEARCH</button>
</div>
</div>
</div>
694
<b-table striped bordered hover :items="categories.data"
:fields="fields" show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="50"
:src="data.item.image" />
</template>
</b-table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Categories - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
label: 'Category Name',
key: 'name'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
695
}
],
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/category/getCategoriesData')
},
//computed
computed: {
//categories
categories() {
return this.$store.state.admin.category.categories
},
},
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.
//layout
layout: 'admin',
Setelah itu, kita juga atur untuk meta title dari view atau component ini.
696
//meta
head() {
return {
title: 'Categories - Administrator',
}
},
Di dalam data function, kita menambahkan sebuah state yang bernama fields dan di
dalamnya berisi beberapa key array, state ini akan kita gunakan untuk membuat sebuah
header untuk table.
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
label: 'Category Name',
key: 'name'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
Setelah itu, kita menggunakan hook asyncData untuk melakukan dispatch ke dalam action
yang sudah kita buat sebelumnya di dalam Vuex.
await store.dispatch('admin/category/getCategoriesData')
Di atas kita melakukan dispatch ke dalam action yang bernama getCategoriesData yang
berada di dalam file /store/admin/category.js.
Jika proses dispatch berhasil, kita tinggal memanggil datanya yang ada di dalam state, disini
kita menggunakan properti computed dan di dalamnya akan memanggil state yang
bernama categories yang berada di dalam file /store/admin/category.js.
697
return this.$store.state.admin.category.categories
Setelah itu pada bagian template, coba perhatikan kode berikut ini :
//...
</b-table>
Kode di atas merupakan component dari Bootstrap Vue yang bernama <b-table>, yang
akan kita gunakan untuk menampilkan sebuah table dengan lebih mudah. Dan di dalam
component tersebut kita memiliki properti :items yang mana isinya kita berikan nilai dari
properti computed yang sudah kita buat di atas sebelumnya, yaitu categories.
Kemudian untuk memberikan table header kita menggunakan properti :fields yang mana
isinya akan kita ambil dari data function yang bernama fields.
698
<li class="c-sidebar-nav-item"><nuxt-link :to="{name: 'admin-
categories'}" class="c-sidebar-nav-link">
<svg class="c-sidebar-nav-icon">
<use xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
folder"></use>
</svg> Categories</nuxt-link>
</li>
Di atas yang semula kita menggunakan <a href="#">, kita ubah menggunakan <nuxt-
link> dan route-nya kita arahkan ke dalam name yang bernama admin-categories.
Sekarang silahkan klik menu Categories yang ada di menu Sidebar atau bisa langsung ke
URL berikut ini http://localhost:3000/admin/categories, maka jika berhasil kita akan
mendapatkan tampilan seperti berikut ini :
699
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> CATEGORIES</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-categories-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama category">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>SEARCH</button>
</div>
</div>
</div>
700
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Categories - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
label: 'Category Name',
key: 'name'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
//state search
search: ''
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/category/getCategoriesData')
},
701
//computed
computed: {
//categories
categories() {
return this.$store.state.admin.category.categories
},
},
//method
methods: {
//method "searchData"
searchData() {
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita menambahkan state di dalam data function,
yang bertujuan untuk menampung input form pencarian.
//state search
search: ''
Setelah itu, di dalam template kita melakukan sedikit perubahan di dalam form pencarian,
kurang lebih seperti berikut ini :
702
<input type="text" class="form-control" v-model="search"
@keypress.enter="searchData" placeholder="cari berdasarkan nama
category">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"><i class="fa fa-
search"></i>SEARCH</button>
div>
Setelah itu, kita membuat method searchData, kurang lebih seperti berikut ini :
//method "searchData"
searchData() {
Di atas kita menjalankan 2 perintah, yang pertama melakukan commit langsung ke dalam
mutation SET_PAGE dengan paremeter / value 1.
Dan yang kedua melakukan dispatch ke dalam action Vuex yang bernama
getCategoriesData dan kita parsing sebuah parameter yaitu this.search yang isinya
adalah kata kunci yang di dapatkan dari input form.
Sekarang silahkan refresh / reload halaman categories dan jika berhasil maka kita bisa
mencoba proses pencarian kurang lebih seperti berikut ini :
703
Langkah 4 - Membuat Fitur Pagination
Sekarang kita lanjutkan untuk membuat fitur pagination, itur ini akan memberikan kita
navigasi di bagian bawah untuk berpindah dari halaman 1 ke halaman yang lain.
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> CATEGORIES</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-categories-
704
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama category">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>SEARCH</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
705
title: 'Categories - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
label: 'Category Name',
key: 'name'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
//state search
search: ''
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/category/getCategoriesData')
},
//computed
computed: {
//categories
categories() {
return this.$store.state.admin.category.categories
},
},
//method
methods: {
//method "searchData"
706
searchData() {
//method "changePage"
changePage(page) {
}
</script>
<style>
</style>
Dari perubahan kode di atas, kita menambahkan component dari Bootstrap Vue yang
bernama <b-paginate> dan isinya akan diambil dari properti computed yang kita buat
sebelumnya, dimana datanya di ambil dari state categories yang ada di dalam Vuex.
Dan ketika navigasi pagination di klik, maka akan menjalankan event @change, yang
mengarah ke dalam method changePage.
707
commit ke dalam mutation yang bernama SET_PAGE dengan parameter yang diambil dari
component pagination di atas.
Sekarang kita bisa mencoba dengan melakukan refresh di halaman categories atau bisa
melihatnya melalui link http://localhost:3000/admin/categories dan jika berhasil maka kita
akan mendapatkan tampilan kurang lebih seperti berikut ini :
708
Membuat Proses Insert Data Category
Sebelumnya kita sudah belajar bagaimana cara menampilkan data dari database melalui
Rest API dan Vuex dan juga membuat fitur pencarian beserta pagination. Sekarang kita akan
lanjutkan untuk membuat proses insert data category ke dalam database menggunakan
Rest API dan Vuex di dalam Nuxt.js
//state
export const state = () => ({
//categories
categories: [],
//page
page: 1,
})
//mutations
export const mutations = {
//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
709
}
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//store category
storeCategory({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
//success
.then(() => {
710
//resolve promise
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
Dari penambahan kode di atas, kita menambahkan 1 action baru dengan nama
storeCategory, di dalamnya kita akan melakukan sending data ke dalam server.
//store category
storeCategory({ dispatch, commit }, payload) {
//...
}
Di dalam action di atas, kita melakukan http request menggunakan Axios dengan method
POST ke dalam endpoint /api/admin/categories dan kita tambahkan parameter
payload yang mana isinya akan di kirimkan melalui component / view.
Jika proses sending data berhasil dilakukan, maka kita akan menjalankan dispatch ke
dalam action getCategoriesData. Tujuannya agar kita melakukan fetching ulang dengan
data yang baru.
711
Langkah 2 - Membuat View Tambah Category
Disini kita akan membuat sebuah file view atau component baru yang akan digunakan untuk
menampilkan halaman tambah data dan sekaligus untuk proses insert data melalui Rest API.
Silahkan buat sebuah folder baru dengan nama create di dalam folder
pages/admin/categories dan di dalam folder create silahkan buat file baru dengan
nama index.vue, kemudian masukkan kode berikut ini :
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> ADD NEW CATEGORY</span>
</div>
<div class="card-body">
<form @submit.prevent="storeCategory">
<div class="form-group">
<label>GAMBAR</label>
<input type="file" @change="handleFileChange"
class="form-control">
<div v-if="validation.image" class="mt-2">
<b-alert show variant="danger">{{
validation.image[0] }}</b-alert>
</div>
</div>
<div class="form-group">
<label>NAMA CATEGORY</label>
<input type="text" v-model="category.name"
placeholder="Masukkan Nama Category" class="form-control">
<div v-if="validation.name" class="mt-2">
<b-alert show variant="danger">{{
validation.name[0] }}</b-alert>
</div>
</div>
712
<button class="btn btn-info mr-1 btn-submit"
type="submit"><i class="fa fa-paper-plane"></i>
SAVE</button>
<button class="btn btn-warning btn-reset"
type="reset"><i class="fa fa-redo"></i>
RESET</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Add New Category - Administrator',
}
},
data() {
return {
//state category
category: {
image: '',
name: ''
},
//state validation
validation: []
}
},
methods: {
713
//get image
let image = this.category.image = e.target.files[0]
//check fileType
if (!image.type.match('image.*')) {
//if fileType not allowed, then clear value and set null
e.target.value = ''
},
//method "storeCategory"
async storeCategory() {
//define formData
let formData = new FormData();
formData.append('image', this.category.image)
formData.append('name', this.category.name)
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
714
})
})
//error
.catch(error => {
}
</script>
<style>
</style>
Dari penambahan kode di atas pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.
//layout
layout: 'admin',
Setelah itu, kita juga mengatur untuk meta title yang ada di dalam view atau component ini.
//meta
head() {
return {
title: 'Add New Category - Administrator',
}
},
715
Dan di dalam data function kita membuat 2 state, yaitu category dan validation. Untuk
state category akan memiliki key lagi di dalamnya, state ini akan digunakan untuk
menampung data yang diinputkan dari form. Dan untuk state validation akan digunakan
untuk menampung response error validasi yang di dapatkan dari Rest API.
return {
//state category
category: {
image: '',
name: ''
},
//state validation
validation: []
}
handleFileChange
storeCategory
handleFileChange
Method ini akan dijalankan ketika input file di dalam template diubah, karena di dalam input
tersebut kita memberikan event @change yang mengarah ke dalam method ini.
Di damana di dalam method handleFileChange pertama kita akan melakukan assign file
yang dipilih dari komputer ke dalam state category.image.
//get image
let image = this.category.image = e.target.files[0]
Setelah itu, kita membuat kondisi untuk memeriksa apakah file yang diupload tersebut
menggunakan ekstensi image atau bukan. Jika file yang di pilih buka format image, maka
kita akan melakukan beberapa aksi, yang pertama mengosongkan input file di formnya
terlebih dahulu.
716
//if fileType not allowed, then clear value and set null
e.target.value = ''
Kemudian kita tampilkan sebuah alert atau pesan bahwa file yang akan di upload tidak
sesuai dengan yang di harapkan.
storeCategory
Method ini akan digunakan untuk menampung data yang di dapatkan dari form sebelum
dikirimkan ke action Vuex. Disini pertama kita melakukan inisialisasi FormData.
//define formData
let formData = new FormData();
Setelah itu, kita melakukan append FormData dari data yang di dapatkan dari input yang
disimpan di dalam state category.image dan category.name.
formData.append('image', this.category.image)
formData.append('name', this.category.name)
Kemudian kita melakukan dispatch atau memanggil action Vuex yang bernama
717
storeCategory yang berada di dalam file store/admin/category.js dan kita berikan
parameter formData.
Jika proses insert data menggunakan action Vuex berhasil, maka akan menampilkan sebuah
alert menggunakan Sweet Alert2.
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
Setelah itu, kita akan di arahkan ke dalam route yang bernama admin-categories.
Tapi, jika proses insert data gagal dilakukan, maka akan melakukan assign error response
validasi ke dalam state validation.
Di dalam template, untuk form action kita arahkan ke dalam method yang sudah kita buat di
atas, yaitu storeCategory.
<form @submit.prevent="storeCategory">
//...
</form>
718
Langkah 3 - Uji Coba Proses Insert Data Category
Sekarang kita akan lakukan uji coba untuk proses insert data category. Silahkaan klik button
TAMBAH yang berada di halaman index cateegory atau bisa menggunakan link berikut ini
http://localhost:3000/admin/categories/create jika berhasil maka kita akan mendapatkan
hasil seperti berikut ini :
719
Membuat Proses Edit dan Update Data Category
Setelah berhasil membuat proses insert data ke dalam database menggunakan Rest API dan
Vuex, maka sekarang kita lanjutkan untuk membuat proses edit dan update data.
Konsepnya kita akan menampilkan datanya terlebih dahulu di dalam form edit dan setelah
data berhasil di tampilkan, kita bisa sesuaikan datanya, setelah itu kita membuat proses
update agar data yang telah disesuaikan tersebut dapat berubah juga di dalam database.
//state
export const state = () => ({
//categories
categories: [],
//page
page: 1,
//category
category: {}
})
//mutations
export const mutations = {
//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
720
//set value state "page"
state.page = payload
},
//mutation "SET_CATEGORY_DATA"
SET_CATEGORY_DATA(state, payload) {
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//store category
storeCategory({ dispatch, commit }, payload) {
//set promise
721
return new Promise((resolve, reject) => {
//success
.then(() => {
//resolve promise
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
//set promise
return new Promise((resolve, reject) => {
//success
.then(response => {
//resolve promise
resolve()
})
722
})
},
//update category
updateCategory({ dispatch, commit }, { categoryId, payload }) {
//set promise
return new Promise((resolve, reject) => {
//success
.then(() => {
//resolve promise
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
Dari perubahan kode Vuex di atas, pertama kita menambahkan 1 state baru dengan nama
category.
//category
category: {}
723
State tersebut akan digunakan untuk menyimpan detail data category nanti-nya.
Setelah itu, kita menambahkan 1 mutation yang nanti akan digunakan untuk mengupdate
nilai dari state category di atas dari hasil response Rest API yang dikirim oleh action.
//mutation "SET_CATEGORY_DATA"
SET_CATEGORY_DATA(state, payload) {
getDetailCategory
Action ini akan digunakan untuk mendapatkan detail data category dari database
menggunakan Rest API. Disini kita memanggil sebuah endpoint
/api/admin/categories/:id menggunakan Axios dengan method GET.
Jika data berhasil di dapatkan, maka kita akan melakukan commit ke dalam mutation yang
bernama SET_CATEGORY_DATA dan kita berikan parameter data response dari Rest API.
updateCategory
Action ini akan digunakan untuk melakukan proses update data ke dalam database. Disini
kita mengirim data ke dalam sebuah endpoint /api/admin/categories/:id dengan
sebuah data yang di kirimkan dari component / view.
724
//store to Rest API "/api/admin/categories/:id" with method "POST"
this.$axios.post(`/api/admin/categories/${categoryId}`, payload)
Jika proses http request berhasil, maka kita akan menjalankan dispatch ke dalam action
getCategoriesData dengan tujuan agar melakukan proses fetching ulang.
725
<b-table striped bordered hover :items="categories.data"
:fields="fields" show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="50" :src="data.item.image" />
</template>
<template v-slot:cell(actions)="row">
<b-button :to="{name: 'admin-categories-edit-id', params: {id:
row.item.id}}" variant="info" size="sm">
EDIT
</b-button>
</template>
</b-table>
Di atas kita menambahkan 1 button baru untuk kita gunakan dalam proses edit data,
dimana di dalam button tersebut kita arahkan ke dalam route yang bernama admin-
categories-edit-id dan kita tambahkan juga parameter berupa data ID params: {id:
row.item.id}.
Sekarang jika kita coba refresh halaman index categories, maka kita akan mendapatkan
kurang lebih tampilannya seperti berikut ini :
726
Silahkan buat folder baru dengan nama edit di dalam folder pages/admin/categories
dan di dalam folder edit silahkan buat file baru dengan nama _id.vue dan masukkan
kode berikut ini :
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> EDIT CATEGORY</span>
</div>
<div class="card-body">
<form @submit.prevent="updateCategory">
<div class="form-group">
<label>GAMBAR</label>
<input type="file" @change="handleFileChange"
class="form-control">
</div>
<div class="form-group">
<label>NAMA CATEGORY</label>
<input type="text" v-model="category.name"
placeholder="Masukkan Nama Category" class="form-control">
<div v-if="validation.name" class="mt-2">
<b-alert show variant="danger">{{
validation.name[0] }}</b-alert>
</div>
</div>
</form>
</div>
727
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Edit Category - Administrator',
}
},
data() {
return {
//state category
category: {
image: '',
name: ''
},
//state validation
validation: []
}
},
//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('admin/category/getDetailCategory',
route.params.id)
},
//mounted
mounted() {
this.category.name =
this.$store.state.admin.category.category.name
},
728
//method
methods: {
//get image
let image = this.category.image = e.target.files[0]
//check fileType
if (!image.type.match('image.*')) {
//if fileType not allowed, then clear value and set null
e.target.value = ''
},
//method "updateCategory"
async updateCategory() {
//define formData
let formData = new FormData();
formData.append('image', this.category.image)
formData.append('name', this.category.name)
formData.append("_method", "PATCH")
729
.then(() => {
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Diupdate!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
})
//error
.catch(error => {
}
</script>
<style>
</style>
Dari penambahan kode di atas pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.
//layout
layout: 'admin',
Setelah itu, kita juga mengatur untuk meta title yang ada di dalam view atau component ini.
730
//meta
head() {
return {
title: 'Edit Category - Administrator',
}
},
Dan di dalam data function kita membuat 2 state baru, yairu category dan validation.
Untuk state category akan di isi data dari Vuex untuk di tampilkan di halaman input form.
//state category
category: {
image: '',
name: ''
},
Dan untuk state validation akan digunakan untuk menyimpan data response error
validasi yang di dapatkan dari Rest API.
//state validation
validation: []
Setelah itu, di dalam hook asyncData kita memanggil atau menjalankan dispatch ke dalam
action yang ada di dalam Vuex dengan bernama getDetailCategory dan memberikan
parameter ID yang di dapatkan dari URL.
//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('admin/category/getDetailCategory',
route.params.id)
},
Selanjutnya, saat proses mounted, kita melakukan assign state category.name dengan
data yang di dapatkan dari state yang ada di dalam Vuex, yaitu nama category.
731
//mounted
mounted() {
this.category.name = this.$store.state.admin.category.category.name
},
handleFileChange
updateCategory
handleFileChange
Method ini akan dijalankan ketika input file di dalam template diubah, karena di dalam input
tersebut kita memberikan event @change yang mengarah ke dalam method ini.
Di damana di dalam method handleFileChange pertama kita akan melakukan assign file
yang dipilih dari komputer ke dalam state category.image.
//get image
let image = this.category.image = e.target.files[0]
Setelah itu, kita membuat kondisi untuk memeriksa apakah file yang diupload tersebut
menggunakan ekstensi image atau bukan. Jika file yang di pilih buka format image, maka
kita akan melakukan beberapa aksi, yang pertama mengosongkan input file di formnya
terlebih dahulu.
//if fileType not allowed, then clear value and set null
e.target.value = ''
732
//set state "category.image" to null
this.category.image = null
Kemudian kita tampilkan sebuah alert atau pesan bahwa file yang akan di upload tidak
sesuai dengan yang di harapkan.
updateCategory
Method ini digunakan untuk menampung data yang dikirim dari form sebelum di kirimkan ke
dalam Vuex untuk proses update data dengan Rest API.
Disini pertama-tama kita melakukan inisialisasi sebuah FormData, tujuannya adalah untuk
menampung data menjadi satu agar mudah di kirim ke dalam server.
//define formData
let formData = new FormData();
Setelah itu, kita buat beberapa append dari formData yang berisi data category dan
termasuk method untuk update, yaitu PATCH.
formData.append('image', this.category.image)
formData.append('name', this.category.name)
formData.append("_method", "PATCH")
Kemudian kita kirimkan formData tersebut ke dalam sebuah action Vuex dengan nama
updateCategory.
733
//sending data to action "updateCategory" vuex
await this.$store.dispatch('admin/category/updateCategory', {
categoryId: this.$route.params.id,
payload: formData
})
Di atas kita mengirim 2 data ke dalam Vuex, yaitu category ID yang di dapatkan dari URL
dan payload yang berisi formData.
Jika proses update data berhasil, maka akan menampilkan sebuah alert menggunakan
Sweet Alert2. Kurang lebih seperti berikut ini :
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Diupdate!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
Setelah itu, kita redirect / arahkan ke dalam route yang bernama admin-categories.
Tapi jika proses update data gagal, maka akan melakukan assign sebuah response error
validasi ke dalam state yang bernama validation.
734
Setelah itu, silahkan diubah isi sesuai dengan kebutuhan dan untuk gambar bisa kita isi atau
kosongkan, setelah itu silahkan klik UPDATE dan jika berhasil maka kita akan mendapatkan
pesan berhasil update seperti berikut ini :
735
Membuat Proses Delete Data Category
Di materi kali ini kita akan belajar bagaimana cara membuat fitur delete data category dari
database. Dan tentu saja kita tetap menggunakan Vuex untuk melakukan proses delete
data menggunakan Rest API. Nanti kita juga akan kombinasikan menggunakan Sweet
Alert2 untuk menampilkan jendela konfirmasi sebelum data benar-benar di delete.
//state
export const state = () => ({
//categories
categories: [],
//page
page: 1,
//category
category: {}
})
//mutations
export const mutations = {
//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
736
},
//mutation "SET_CATEGORY_DATA"
SET_CATEGORY_DATA(state, payload) {
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//store category
storeCategory({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
737
//store to Rest API "/api/admin/categories" with method
"POST"
this.$axios.post('/api/admin/categories', payload)
//success
.then(() => {
//resolve promise
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
//set promise
return new Promise((resolve, reject) => {
//success
.then(response => {
//resolve promise
resolve()
})
})
738
},
//update category
updateCategory({ dispatch, commit }, { categoryId, payload }) {
//set promise
return new Promise((resolve, reject) => {
//success
.then(() => {
//resolve promise
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
//destroy category
destroyCategory({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
//delete to Rest API "/api/admin/categories/:id" with method
"DELETE"
this.$axios.delete(`/api/admin/categories/${payload}`)
//success
.then(() => {
739
//resolve promise
resolve()
})
})
},
Dari penambahan kode di atas, kita menambahkan 1 action baru dengan nama
destroyCategory, dimana di dalamnya kita melakukan http request menggunakan Axios
ke dalam endpoint /api/admin/categories/:id dengan method DELETE.
Jika proses delete berhasil, maka kita akan menjalankan dispatch ke dalam action yang
bernama getCategoriesData, dengan tujuan melakukan fetching ulang.
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
740
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> CATEGORIES</span>
</div>
<div class="card-body">
<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-categories-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama category">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>SEARCH</button>
</div>
</div>
</div>
741
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
//layout
layout: 'admin',
//meta
head() {
return {
title: 'Categories - Administrator',
}
},
//data function
data() {
return {
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
label: 'Category Name',
key: 'name'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
//state search
search: ''
}
},
742
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/category/getCategoriesData')
},
//computed
computed: {
//categories
categories() {
return this.$store.state.admin.category.categories
},
},
//method
methods: {
//method "searchData"
searchData() {
//method "changePage"
changePage(page) {
//method "destroyCategory"
destroyCategory(id) {
this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
743
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
}).then((result) => {
if (result.isConfirmed) {
//feresh data
this.$nuxt.refresh()
//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Dihapus!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
})
}
})
}
}
</script>
<style>
</style>
Dari perubahan kode di atas, pertama kita menambahkan 1 button batu untuk proses delete
data.
744
Di atas kita menambahkan event @click yang di arahkan ke dalam method yang bernama
destroyCategory dan di dalamnya kita berikan parameter dari ID category.
Setelah itu, kita menambahkan 1 method baru dengan nama destroyCategory, dimana
jika dijalankan maka akan menampilkan sebuah jendela konfirmasi menggunakan Sweet
Alert2.
this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
})
Jika button konfirmasi di klik, maka akan menjalankan / memanggil dispatch ke dalam action
Vuex yang bernama destroyCategory dan kita tambahkan parameter ID yang di
dapatkan dari button delete.
Jika proses delete data berhasil, maka pertama kita melakukan refresh project Nuxt.js,
dengan tujuan agar data yang ditampilkan bisa terupdate.
//feresh data
this.$nuxt.refresh()
Setelah itu, kita akan menampilkan alert lagi menggunakan Sweet Alert2 dari status berhasil
melakukan proses delete data.
//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Dihapus!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
745
Langkah 3 - Uji Coba Proses Delete Data Category
Sekarang kita bisa mencobanya dengan membuka halaman categories atau bisa melalui link
berikut ini http://localhost:3000/admin/categories, jika berhasil maka kita akan
mendapatkan tampilan kurang lebih seperti berikut ini :
Jika kita klik button DELETE di salah satu data yang ada, maka akan menampilkan sebuah
konfirmasi kurang lebih seperti berikut ini :
Dan jika kita klik YA, HAPUS!, maka data akan berhasil dihapus dan kita mendapatkan
746
hasil seperti berikut ini :
747
Konfigurasi Vuex Admin Product
Pada materi kali ini kita akan belajar membuat konfigurasi Vuex Admin Product. Disini kita
akan menambahkan beberapa store, yaitu state, mutations dan actions.
//state
export const state = () => ({
//products
products: [],
//page
page: 1,
})
//mutations
export const mutations = {
//mutation "SET_PRODUCTS_DATA"
SET_PRODUCTS_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
//actions
export const actions = {
748
//get products data
getProductsData({ commit, state }, payload) {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
Dari penambahan kode store Vuex di atas, pertama kita menambahkan 2 state baru, yaitu
products dan page. Untuk state products akan digunakan untuk menyimpan response
data yang di dapatkan dari Rest API. Sedangkan untuk state page akan digunakan untuk
menyimpan nomor halaman untuk pagination.
//products
products: [],
//page
page: 1,
749
//mutation "SET_PRODUCTS_DATA"
SET_PRODUCTS_DATA(state, payload) {
SET_PAGE - digunakan untuk mengubah nilai dari state page dengan data yang
dikirmkan dari component / view yaitu berupa nomor halaman untuk pagination.
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
//...
}
Dimana di dalamnya pertama kita membuat sebuah variable dengan nama search, dengan
isi yang di dapatkan dari parameter.
//search
let search = payload ? payload : ''
Variable tersebut akan kita sertakan saat proses http request ke dalam endpoint Rest API
sebagai parameter untuk pencarian.
Selanjutnya untuk proses http request kita menggunakan Axios dengan method GET dan
750
untuk endpoint-nya adalah /api/admin/products?q=""&page="". Untuk parameter q
akan diisi dari variable search dan untuk parameter page akan diambil dari state yang
bernama page.
Jika proses fetching http request berhasil, maka akan melakukan commit ke dalam mutation
yang bernama SET_PRODUCTS_DATA dengan memberikan parameter berupa response data
dari Rest API.
751
Membuat Component Hader dan Footer
Sekarang kita akan belajar membuat component Header dan Footer yang nanti akan
digunakan untuk halaman depan website. Kedua component ini akan sering kita gunakan di
semua view yang ada.
Silahkan buat folder baru dengan nama web di dalam folder components dan di dalam
folder web tersebut, silahkan buat file baru dengan nama header.vue, kemudian
masukkan kode berikut ini di dalamnya :
<template>
<header class="section-header fixed-top">
<section class="header-main border-bottom">
<div class="container-fluid">
<div class="row align-items-center">
<div class="col-lg-3 col-sm-4 col-md-4 col-5">
<nuxt-link to="/" class="brand-wrap" data-abc="true">
<img src="/images/xiaomi.png" width="35" class="bg-light
p-2 rounded">
<span class="logo">MI STORE</span>
</nuxt-link>
</div>
<div class="col-lg-4 col-xl-5 col-sm-8 col-md-4 d-none d-md-
block">
<div class="search-wrap">
<div class="input-group w-100">
<input type="text" class="form-control search-form"
style="width:55%;" placeholder="mau belanja apa hari ini ?">
<div class="input-group-append">
<button class="btn btn-primary search-button"> <i
class="fa fa-search"></i> </button>
</div>
</div>
</div>
</div>
<div class="col-lg-5 col-xl-4 col-sm-8 col-md-4 col-7">
<div class="d-flex justify-content-end">
<a href="#" class="btn search-button btn-md d-md-block
ml-4"><i class="fa fa-shopping-cart"></i> <span class="ml-2">0</span> |
Rp. 0</a>
752
</div>
</div>
</div>
</div>
</section>
<nav class="navbar navbar-expand-md navbar-main border-bottom p-2">
<div class="container-fluid">
<div class="d-md-none my-2">
<div class="input-group">
<input type="search" name="search" class="form-control"
placeholder="mau belanja apa hari ini ?">
<div class="input-group-append">
<button class="btn btn-warning"> <i class="fa fa-
search"></i></button>
</div>
</div>
</div>
<button class="navbar-toggler collapsed" type="button" data-
toggle="collapse" data-target="#dropdown6"
aria-expanded="false"> <span class="navbar-toggler-
icon"></span> </button>
<div class="navbar-collapse collapse" id="dropdown6">
<ul class="navbar-nav mr-auto">
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-list-ul"></i> KATEGORI</a> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-shopping-bag"></i> SEMUA PRODUK</a> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-info-circle"></i> TENTANG</a> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-comments"></i> KONTAK</a> </li>
</ul>
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown" v-if="!$auth.loggedIn">
<nuxt-link :to="{name: 'customer-login'}" class="nav-link"
href="#" role="button" aria-expanded="false"> <i class="fa fa-user-
circle"></i>
ACCOUNT</nuxt-link>
</li>
<li class="nav-item dropdown" v-if="$auth.loggedIn">
<nuxt-link :to="{name: 'customer-dashboard'}" class="nav-
link" href="#" role="button" aria-expanded="false"> <i class="fa fa-
tachometer-alt"></i>
DASHBOARD</nuxt-link>
</li>
</ul>
753
</div>
</div>
</nav>
</header>
</template>
<script>
export default {
}
</script>
<style scoped>
.btn {
font-size: initial;
}
</style>
Di atas, kita tambahkan component Header untuk menampilkan navigasi atau navbar dari
project Nuxt.js kita. Template di atas masih bersifat statis semua dan belum ada data
apapun yang akan di tampilkan disini.
Selanjutnya, kita akan membuat component lagi untuk Footer. Silahkan buat file baru
dengan nama footer.vue di dalam folder components/web, kemudian masukkan kode
berikut ini di dalamnya :
<template>
<footer class="pt-5" style="background: rgb(255, 255, 255); border-
top: 5px solid rgb(230 74 26);">
<div class="container-fluid">
<div class="row">
<div class="col-md-5 mb-4">
<h4 class="font-weight-bold">TENTANG</h4>
<hr style="border-top: 3px solid rgb(226, 232, 240); border-
radius: 0.5rem;">
<p> Mi Store Official Terpercaya di Indonesia. Jual Beli Aman
& Harga Termurah! Belanja
sekarang. </p>
</div>
<div class="col-md-3 mb-4">
754
<h4 class="font-weight-bold">METODE PEMBAYARAN</h4>
<hr style="border-top: 3px solid rgb(226, 232, 240); border-
radius: 0.5rem;">
<div class="row">
<div class="col-md-4 col-4 mb-3">
<div class="card rounded">
<div class="card-body p-2 text-center"><img
src="/images/payment/BCA.png" style="width:
50px;"></div>
</div>
</div>
<div class="col-md-4 col-4 mb-3">
<div class="card rounded">
<div class="card-body p-2 text-center"><img
src="/images/payment/BNI.png" style="width:
45px;"></div>
</div>
</div>
<div class="col-md-4 col-4 mb-3">
<div class="card rounded">
<div class="card-body p-2 text-center"><img
src="/images/payment/BRI.png" style="width:
60px;"></div>
</div>
</div>
<div class="col-md-4 col-4 mb-3">
<div class="card rounded">
<div class="card-body p-2 text-center"><img
src="/images/payment/GOPAY.png" style="width:
60px;"></div>
</div>
</div>
<div class="col-md-4 col-4 mb-3">
<div class="card rounded">
<div class="card-body p-2 text-center"><img
src="/images/payment/indomaret-logo.png"
style="width: 60px;">
</div>
</div>
</div>
<div class="col-md-4 col-4 mb-3">
<div class="card rounded">
<div class="card-body p-2 text-center"><img
src="/images/payment/atm-bersama.jpg" style="width:
40px;"></div>
</div>
755
</div>
</div>
</div>
<div class="col-md-4 mb-4">
<h4 class="font-weight-bold">JAM OPERASIONAL</h4>
<hr style="border-top: 3px solid rgb(226, 232, 240); border-
radius: 0.5rem;">
<p><i class="fa fa-clock"></i> Toko Buka Setiap Hari :
<br><br> <strong>Senin - Jum'at</strong> ( 07.00 s/d
19.00 ) <br> <strong>Sabtu - Minggu</strong> ( 07.00 s/d
16.00 ) </p>
</div>
</div>
<div class="row text-center mt-3 pb-3">
<div class="col-md-12">
<hr> © <strong>MI STORE</strong> 2021 • Hak Cipta Dilindungi
</div>
</div>
</div>
</footer>
</template>
<script>
export default {
}
</script>
<style>
</style>
Di atas kita memanggil beberapa gambar yang ada di dalam component Footer dari folder
/static/images/. Dimana gambar-gambar tersebut sudah kita tambahkan sebelumnya.
756
Membuat Fitur Rating dan Review
Fitur ini bisa digunakan jika status invoice bernilai success, karena masih di localhost,
maka untuk status invoice nanti belum bisa otomatis diupdate menjadi success saat
pembayaran berhasil dilakukan. Jadi nanti kita akan manual mengganti status invoice di
dalam database untuk menguji fitur ini.
Sebelum itu, kita akan membuat module Vuex terlebih dahulu yang nanti digunakan untuk
menangani proses insert data rating dan review di dalam fitur ini.
757
//actions
export const actions = {
//store review
storeReview({ commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
//success
.then(response => {
//resolve promise
resolve(response.data)
})
//error
.catch(error => {
reject(error)
})
})
},
Di atas, kita hanya menambahkan 1 action baru dengan nama storeReview. Di dalam
action tersebut kita melakukan sending data menggunakan Axios dengan method POST ke
dalam endpoint /api/customer/reviews dan untuk parameter payload akan berisi data
rating dan review yang nanti akan dikirmkan oleh component / view.
758
Langkah 2 - Menambahkan Button dan Modal untuk Review
Sekarang kita akan menambahkan button dan modal yang nanti digunakan untuk
mengirimkan rating dan review dari product yang di beli.
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3">
<!-- sidebar -->
<Sidebar />
<!-- end sidebar -->
</div>
<div class="col-md-9">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-body">
<h5><i class="fa fa-shopping-cart"></i> DETAIL ORDER</h5>
<hr>
<table class="table table-bordered">
<client-only>
<tr>
<td style="width: 25%">
NO. INVOICE
</td>
<td style="width: 1%">:</td>
<td>
{{ invoice.invoice }}
</td>
</tr>
<tr>
<td>
FULL NAME
</td>
<td>:</td>
<td>
{{ invoice.name }}
</td>
</tr>
<tr>
759
<td>
PHONE
</td>
<td>:</td>
<td>
{{ invoice.phone }}
</td>
</tr>
<tr>
<td>
COURIER / SERVICE / COST
</td>
<td>:</td>
<td>
{{ invoice.courier }} / {{ invoice.courier_service
}} / Rp.
{{ formatPrice(invoice.courier_cost) }}
</td>
</tr>
<tr>
<td>
CITY
</td>
<td>:</td>
<td>
{{ invoice.city.name }}
</td>
</tr>
<tr>
<td>
PROVINCE
</td>
<td>:</td>
<td>
{{ invoice.province.name }}
</td>
</tr>
<tr>
<td>
ADDRESS
</td>
<td>:</td>
<td>
{{ invoice.address }}
</td>
</tr>
760
<tr>
<td>
GRAND TOTAL
</td>
<td>:</td>
<td>
Rp. {{ formatPrice(invoice.grand_total) }}
</td>
</tr>
<tr>
<td>
STATUS
</td>
<td>:</td>
<td>
<button @click="payment(invoice.snap_token)" v-
if="invoice.status == 'pending'"
class="btn btn-info">BAYAR SEKARANG</button>
<button v-else-if="invoice.status == 'success'"
class="btn btn-success"><i class="fa fa-check-
circle"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'expired'"
class="btn btn-warning-2"><i class="fa fa-
exclamation-triangle"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'failed'"
class="btn btn-danger"><i class="fa fa-times-
circle"></i> {{ invoice.status }}</button>
</td>
</tr>
</client-only>
</table>
</div>
</div>
761
<tr v-for="order in invoice.orders" :key="order.id"
style="background: #edf2f7;">
<td class="b-none" width="25%">
<div class="wrapper-image-cart">
<img :src="order.product.image" style="width:
100%;border-radius: .5rem">
</div>
</td>
<td class="b-none" width="50%">
<h5><b>{{ order.product.title }}</b></h5>
<table class="table-borderless" style="font-
size: 14px">
<tr>
<td style="padding: .20rem">QTY</td>
<td style="padding: .20rem">:</td>
<td style="padding: .20rem"><b>{{ order.qty
}}</b></td>
</tr>
</table>
<!-- modal button -->
<button v-if="invoice.status == 'success'"
type="button" class="btn btn-warning-2 mt-4" data-toggle="modal"
:data-target="'#modal-'+order.id">
BERIKAN ULASAN
</button>
762
</vue-star-rating>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<div class="form-group">
<label class="font-weight-
bold">ULASAN</label>
<textarea class="form-control"
id="alamat" rows="3" placeholder="Masukkan Ulasan Produk"
v-
model="rating.review"></textarea>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-
secondary" data-dismiss="modal">TUTUP</button>
<button v-if="rating.star &&
rating.review" @click.prevent="storeReview(order.id, order.product.id)"
type="button"
class="btn btn-warning" data-
dismiss="modal">KIRIM</button>
</div>
</div>
</div>
</div>
</td>
<td class="b-none text-right">
<p class="m-0 font-weight-bold">Rp. {{
formatPrice(order.price) }}</p>
</td>
</tr>
</client-only>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
763
<script>
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'
export default {
//middleware
middleware: 'isCustomer',
//layout
layout: 'default',
//register components
components: {
Sidebar
},
//meta
head() {
return {
title: 'Detail Order - Customer',
}
},
//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('customer/invoice/getDetailInvoice',
route.params.snap_token)
},
//computed
computed: {
invoice() {
return this.$store.state.customer.invoice.invoice
}
},
//data function
data() {
return {
//state rating
rating: {
star: 0,
review: ''
},
}
764
},
//method
methods: {
//method "payment"
payment(snap_token) {
window.snap.pay(snap_token, {
onSuccess: function () {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
})
},
onPending: function () {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
})
},
onError: function () {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
})
}
})
},
//method "storeReview"
async storeReview(orderId, productId) {
//define formData
let formData = new FormData();
formData.append('rating', this.rating.star)
formData.append('review', this.rating.review)
formData.append('order_id', orderId)
formData.append('product_id', productId)
765
//sending data to action "storeReview" vuex
await this.$store.dispatch('customer/review/storeReview',
formData)
//success
.then(() => {
//feresh data
this.$nuxt.refresh()
//clear state
this.rating.star = 0
this.rating.review = ''
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Ulasan Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 3000
})
})
.catch(() => {
//sweet alert
this.$swal.fire({
title: 'GAGAL!',
text: "Anda sudah membuat ulasan untuk produk ini!",
icon: 'error',
showConfirmButton: false,
timer: 3000
})
})
}
}
}
</script>
<style>
766
</style>
Dari perubahan kode diatas, pertama kita menambahkan button BERIKAN ULASAN di dalam
data item order. Dan button tersebut akan muncul jika status dari invoice success.
Kemudian, kita juga menambahkan modal untuk menampilkan input rating dan review di
dalamnya.
//...
</div>
Dan kita juga menambahkan state di dalam data function, state tersebut yang nanti akan
digunakan untuk menampung nilai dari data yang diinputkan.
//state rating
rating: {
star: 0,
review: ''
},
Setelah itu, kita juga membuat method yang bernama storeReview, method tersebut akan
dijalankan ketika button KIRIM di dalam modal di klik.
767
<button v-if="rating.star && rating.review"
@click.prevent="storeReview(order.id, order.product.id)" type="button"
class="btn btn-warning" data-dismiss="modal">KIRIM</button>
//method "storeReview"
async storeReview(orderId, productId) {
//...
}
Di dalam method di atas, pertama kita melakukan inisialisasi sebuah FormData, tujuannya
agar mempermudah dalam mengelompokan data yang akan dikirim ke dalam action Vuex.
//define formData
let formData = new FormData();
Setelah itu, kita buat append formData dengan key dan value yang di dapatkan dari state,
yang mana isinya akan diambilkan dari input form.
formData.append('rating', this.rating.star)
formData.append('review', this.rating.review)
formData.append('order_id', orderId)
formData.append('product_id', productId)
Dan kita akan kirimkan formData tersebut ke dalam action yang bernama storeReview.
Jika data rating dan review berhasil dikirim, maka kita akan melakukan refresh Nuxt.js
menggunakan kode berikut ini :
768
//feresh data
this.$nuxt.refresh()
Dan kita set state rating ke dalam bentuk semula atau kosong.
//clear state
this.rating.star = 0
this.rating.review = ''
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Ulasan Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 3000
})
Tapi, jika sudah ada data rating dan ulasan di dalam database, maka kita akan menculkan
error menggunakan Sweet Alert2.
769
//sweet alert
this.$swal.fire({
title: 'GAGAL!',
text: "Anda sudah membuat ulasan untuk produk ini!",
icon: 'error',
showConfirmButton: false,
timer: 3000
})
ketika status di invoice sudah success, maka akan menampilkan button di dalam item
product yang nantinya bisa digunakan untuk mengirim sebuah rating dan review. Kurang
lebih seperti berikut ini :
770
Jika kita klik button BERIKAN ULASAN, maka akan menampilkan popup seperti berikut ini :
771
Kita bisa memberitan rating dan ulasan / review sesuai dengan product yang kita beli dan
jika kita klik KIRIM, maka akan mendapatkaan hasil seperti berikut ini :
772
Membuat Layout Default
Setelah berhasil membuat component Header dan juga Footer, maka kita akan lanjutkan
untuk membuat layout induk dari website yang kita buat.
773
<template>
<div>
<!-- header -->
<Header />
<!-- end header -->
<script>
export default {
//register components
components: {
Header,
Footer
}
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita melakukan import kedua component yang
sudah kita buat sebeleumnya, yaitu Header dan Footer.
774
Setelah itu, agar kedua component tersebut dapat digunakan di dalam template, maka kita
perlu melakukan register terlebih dahulu.
//register components
components: {
Header,
Footer
}
Dan untuk memanggil component tersebut di dalam template, kita bisa menggunakan kode
seperti berikut ini :
775
<template>
<div class="mt-custom mb-5">
<div class="fade-in">
<h1 class="text-center">SANTRI KODING</h1>
</div>
</div>
</template>
<script>
export default {}
</script>
Sekarang, silahkan reload / refresh halaman depan website atau bisa buka di URL berikut ini
http://localhost:3000, jika berhasil maka kita akan mendapatkan hasil kurang lebih seperti
berikut ini :
776
Membuat Proses Register Customer
Setelah berhasil menampilkan component Header dan juga Footer, sekarang kita akan
lanjutkan belajar membuat proses register untuk customer di website toko online yang
sedang kita kembangkan. Sebelum itu, kita akan membuat action di Vuex untuk menangani
proses register menggunakan Rest API.
777
//actions
export const actions = {
//store register
storeRegister({ commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
//success
.then(() => {
//resolve promise
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
Di atas, kita hanya menambahkan sebuah action baru dengan nama storeRegister,
action tersebut yang nanti akan digunakan untuk menangai proses register menggunakan
Rest API.
//store register
storeRegister({ commit }, payload) {
//...
}
Di dalam action tersebut, kita melakukan http request untuk mengirim data menggunakan
778
Axios dengan method POST ke dalam endpoint /api/customer/register. Dan kita
berikan parameter payload yang nantinya akan berisi data customer yang dikirimkan dari
component / view.
Agar di dalam component / view dapat menerima callback promise, maka kita perlu
melakukan resolve.
//resolve promise
resolve()
Silahkan buat folder baru dengan nama customer di dalam folder pages. Dan di dalam
folder customer, silahkan buat file baru dengan nama register.vue, kemudian
masukkan kode berikut ini di dalamnya.
<template>
<div class="container mt-custom mb-3">
<div class="fade-in">
<div class="row justify-content-center">
<div class="col-md-7">
<div class="card-group">
<div class="card border-top-orange border-0 shadow-sm
rounded">
<div class="card-body">
<h3>REGISTER</h3>
<hr>
<div v-if="validation.message" class="mt-2">
<b-alert show variant="danger">{{ validation.message
}}</b-alert>
</div>
<form @submit.prevent="register">
779
<div class="row">
<div class="col-md-6">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-user"></i>
</span>
</div>
<input class="form-control" v-model="user.name"
type="text" placeholder="Nama Lengkap">
</div>
<div v-if="validation.name" class="mt-2">
<b-alert show variant="danger">{{
validation.name[0] }}</b-alert>
</div>
</div>
<div class="col-md-6">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-envelope"></i>
</span>
</div>
<input class="form-control" v-model="user.email"
type="email" placeholder="Email Address">
</div>
<div v-if="validation.email" class="mt-2">
<b-alert show variant="danger">{{
validation.email[0] }}</b-alert>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="input-group mb-4">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-lock"></i>
</span>
</div>
<input class="form-control" v-
model="user.password" type="password" placeholder="Password">
</div>
<div v-if="validation.password" class="mt-2">
<b-alert show variant="danger">{{
780
validation.password[0] }}</b-alert>
</div>
</div>
<div class="col-md-6">
<div class="input-group mb-4">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-lock"></i>
</span>
</div>
<input class="form-control" v-
model="user.password_confirmation" type="password"
placeholder="Password Confirmation">
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<button class="btn btn-info shadow-sm rounded-sm
px-4" type="submit">REGISTER</button>
<button class="btn btn-warning shadow-sm rounded-sm
px-4" type="reset">RESET</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="text-center mt-3">
Sudah punya akun? <nuxt-link :to="{name: 'customer-login'}"
class="font-weight-bold"> Login Disini
</nuxt-link>
</div>
</div>
</div>
</template>
<script>
export default {
//middleware
middleware: 'authenticated',
781
//layout
layout: 'default',
//meta
head() {
return {
title: 'Register - Customer',
}
},
//data function
data() {
return {
//state user
user: {
name: '',
email: '',
password: '',
password_confirmation: ''
},
//validation
validation: []
}
},
//method
methods: {
//method "register"
async register() {
.then(() => {
//sweet alert
this.$swal.fire({
title: 'REGISTER BERHASIL!',
text: "Proses Register Berhasil!",
icon: 'success',
782
showConfirmButton: false,
timer: 2000
})
//redirect
this.$router.push({
name: 'customer-login'
})
})
.catch(error => {
//assign validation
this.validation = error.response.data
})
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan middleware authenticated.
//middleware
middleware: 'authenticated',
Setelah itu, kita atur layout induk yang digunakan, disini kita akan atur dengan layout yang
sebelumnya sudah kita buat, yaitu default.
//layout
layout: 'default',
Setelah itu, kita juga menambahkan konfigurasi untuk meta tag title. Fungsinya untuk
783
mengubah title yang ada di website kita.
//meta
head() {
return {
title: 'Register - Customer',
}
},
Dan di dalam data function, kita membuat 2 state baru, yaitu user dan validation. Untuk
state user akan digunakan untuk menampung data yang diinputkan di dalam form.
Sedangkan untuk state validation akan digunakan untuk menampung error response
validasi yang di dapatkan dari Rest API.
//state user
user: {
name: '',
email: '',
password: '',
password_confirmation: ''
},
//validation
validation: []
Dan di dalam method, kita menambahkan 1 function baru dengan nama register.
//method "register"
async register() {
//...
}
Di dalam method tersebut, kita melakukan dispatch atau memanggil sebuah action yang
bernama storeRegister yang berada di dalam file store/customer/customer.js dan
kita tambahkan beberapa parameter di dalamnya.
784
//dispatch to action "storeRegister"
await this.$store.dispatch('customer/customer/storeRegister', {
name: this.user.name,
email: this.user.email,
password: this.user.password,
password_confirmation: this.user.password_confirmation
})
Di atas kita menambahkan 4 parameter data, yaitu name, email, password dan
confirmation_password. Ke empat parameter tersebut yang nanti akan dikirim action ke
dalam endpoint Rest API untuk proses register.
Jika proses register berhasil dilakukan, maka kita akan menampilkan alert sukses
menggunakan Sweet Alert2.
//sweet alert
this.$swal.fire({
title: 'REGISTER BERHASIL!',
text: "Proses Register Berhasil!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
//redirect
this.$router.push({
name: 'customer-login'
})
Tapi, jika proses register di atas gagal dilakukan, maka akan melakukan assign error
response validasi ke dalam state yaang bernama validation.
//assign validation
this.validation = error.response.data
785
Dan di dalam template, untuk form action akan kita arahkan ke dalam method yang sudah
kita buat di atas, yaitu register.
<form @submit.prevent="register">
//...
</form>
Silahkan klik REGISTER tanpa mengisi data apapun dan jika berhasil, maka kita akan
mendapatkan error validasi yang dikirimkan oleh Laravel, kurang lebih seperti berikut ini :
786
Dan sekarang, silahkan coba masukkan data yang dibutuhkan dan klik REGISTER, jika
berhasil maka kita akan mendapatkan hasil seperti berikut ini :
787
Membuat Proses Login Customer
Setelah berhasil membuat proses register, maka sekarang kita lanjutkan untuk membuat
proses login untuk customer. Dimana kita akan melakukan proses login menggunakan data
yang kita masukkan saat register tadi. Karena menggunakan module Nuxt Auth, maka
untuk proses login kita tidak perlu membuat sebuah Vuex.
<template>
<div class="container mt-custom mb-3">
<div class="fade-in">
<div class="row justify-content-center">
<div class="col-md-4">
<div class="card-group">
<div class="card border-top-orange border-0 shadow-sm
rounded">
<div class="card-body">
<h3>LOGIN</h3>
<hr>
<div v-if="validation.message" class="mt-2">
<b-alert show variant="danger">{{ validation.message
}}</b-alert>
</div>
<form @submit.prevent="login">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-envelope"></i>
</span>
</div>
<input class="form-control" v-model="user.email"
type="email" placeholder="Email Address">
</div>
<div v-if="validation.email" class="mt-2">
<b-alert show variant="danger">{{ validation.email[0]
}}</b-alert>
</div>
<div class="input-group mb-4">
788
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-lock"></i>
</span>
</div>
<input class="form-control" v-model="user.password"
type="password" placeholder="Password">
</div>
<div v-if="validation.password" class="mt-2">
<b-alert show variant="danger">{{
validation.password[0] }}</b-alert>
</div>
<div class="row">
<div class="col-12">
<button class="btn btn-warning shadow-sm rounded-sm
px-4 w-100" type="submit">LOGIN</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="text-center mt-3">
Belum punya akun? <nuxt-link :to="{name: 'customer-register'}"
class="font-weight-bold">Daftar Sekarang</nuxt-link>
</div>
</div>
</div>
</template>
<script>
export default {
//middleware
middleware: 'authenticated',
//layout
layout: 'default',
//meta
head() {
return {
title: 'Login - Customer',
}
789
},
data() {
return {
//state user
user: {
email: '',
password: '',
},
//validation
validation: []
}
},
methods: {
async login() {
await this.$auth.loginWith('customer', {
data: {
email: this.user.email,
password: this.user.password
}
})
.then(() => {
//redirect
this.$router.push({
name: 'customer-dashboard'
})
})
.catch(error => {
//assign validation
this.validation = error.response.data
})
}
}
</script>
<style>
790
</style>
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan middleware authenticated.
//middleware
middleware: 'authenticated',
Setelah itu, kita atur layout induk yang digunakan, disini kita akan atur dengan layout yang
sebelumnya sudah kita buat, yaitu default.
//layout
layout: 'default',
Setelah itu, kita juga menambahkan konfigurasi untuk meta tag title. Fungsinya untuk
mengubah title yang ada di website kita.
//meta
head() {
return {
title: 'Login - Customer',
}
},
Di dalam data function, kita menambahkan 2 state baru, yaitu user dan validation.
Untuk state user akan digunakan untuk menampung data yang dikirimkan dari input form.
Sedangkan untuk state validation akan digunakan untuk menampung error response
validasi yang di dapatkan dari Rest API.
791
//state user
user: {
email: '',
password: '',
},
//validation
validation: []
}
Dan di dalam method, kita menambahkan 1 action baru yang bernama login, method
tersebut akan dijalankan ketika form di submit.
<form @submit.prevent="login">
//...
</form>
async login() {
//...
}
Di dalam method tersebut, kita melakukan proses login dengan memanggil konfigurasi auth
yang sudah kita buat di dalam file nuxt.config.js. Kurang lebih seperti berikut ini :
await this.$auth.loginWith('customer', {
data: {
email: this.user.email,
password: this.user.password
}
})
Di atas, kita melakukan proses otentikasi dengan memanggil nama strategy customer
dengan mengirimkan 2 data parameter, yaitu email dan password.
Jika proses otentikasi di atas berhasil, maka kita akan diarahkan / redirect ke dalam route
792
yang bernama customer-dashboard.
//redirect
this.$router.push({
name: 'customer-dashboard'
})
Tapi, jika proses otentikasi gagal dilakukan, maka akan melakukan assign data response
error validasi ke dalam state yang bernama validation.
Dan untuk menampilkan validasi di dalam template, kurang lebih seperti berikut ini :
793
Silahkan klik LOGIN tanpa mengisi data apapun di dalam form, maka kita akan
mendapatkan response kurang lebih seperti berikut ini :
Sekarang, kita coba memasukkan data email dan password customer yang belum ada di
dalam database, maka kita akan mendapatkan error login failed seperti berikut ini :
794
Dan terakhir, silahkan coba masukkan email dan password customer yang sebelumnya
kita masukkan lewat proses register, dan jika berhasil maka akan mendapatkan hasil seperti
berikut ini :
Di atas, sebenernya kita sudah berhasil melakukan proses otentikasi, halaman error
tersebut muncul karena kita belum memiliki route yang bernama customer-dashboard.
795
Membuat Halaman Dashboard Customer
Setelah berhasil membuat proses otentikasi / login, maka sekarang kita tinggal membuat
halaman dashboard untuk customer. Dimana halaman ini yang pertama kali ditampilkan
ketika customer berhasil melakukan proses otentikasi dan di halaman ini kita juga akan
menampilkan statistik data invoice yang pernah dibuat oleh si customer tersebut.
Silahkan buat file baru dengan nama sidebar.vue di dalam folder component/web,
kemudian masukkan kode berikut ini di dalamnya.
<template>
<div class="card border-0 rounded shadow-sm border-top-orange">
<div class="card-body">
<h5>MAIN MENU</h5>
<hr>
<ul class="list-group">
<nuxt-link :to="{name: 'customer-dashboard'}"
class="list-group-item text-decoration-none text-dark text-
uppercase"><i
class="fa fa-tachometer-alt"></i> Dashboard
</nuxt-link>
796
<script>
export default {
//method
methods: {
//method "logout"
async logout() {
//logout auth
await this.$auth.logout()
}
</script>
<style scoped>
a.nuxt-link-active {
background: rgba(255, 222, 212, .05) !important;
}
</style>
Di atas, kita juga menambahkan fungsi untuk proses logout, di dalam button kita
menambahkan event @click yang kita arahkan ke dalam method yang bernama logout.
Dan untuk method logout, di dalamnya kita melakukan proses logout dengan kode seperti
berikut ini :
797
//logout auth
await this.$auth.logout()
Jika proses logout berhasil, maka akan di arahkan / redirect ke dalam route yang bernama
customer-login.
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3">
<!-- sidebar -->
<Sidebar />
<!-- end sidebar -->
</div>
<div class="col-md-9">
<div class="card border-0 rounded shadow-sm border-top-orange">
<div class="card-body">
<h5><i class="fa fa-tachometer-alt"></i> DASHBOARD</h5>
<hr>
<div class="row">
<div class="col-md-12">
<div class="alert alert-success" role="alert">
Selamat Datang <strong>{{ $auth.user.name }}</strong>
</div>
</div>
</div>
798
<div class="row">
799
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'
export default {
//middleware
middleware: 'isCustomer',
//layout
layout: 'default',
//register components
components: {
Sidebar
},
800
//meta
head() {
return {
title: 'Dashboard - Customer',
}
},
//fetching dashboard
const dashboard = await $axios.$get('/api/customer/dashboard')
return {
//count statistik
'pending': dashboard.data.count.pending,
'success': dashboard.data.count.success,
'expired': dashboard.data.count.expired,
'failed': dashboard.data.count.failed,
}
},
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita melakukam import component sidebar
terlebih dahulu.
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'
Dan agar component tersebut dapat digunakan di dalam template, maka kita perlu
melakukan register.
801
//register components
components: {
Sidebar
},
Dan untuk memanggil component tersebut di dalam template, kita bisa menggunakan kode
seperti berikut ini :
Kemudian, kita atur agar view ini menggunakan middleware isCustomer, artinya hanya
bisa diakses jika si user memiliki level customer dan sudah melakukan proses otentikasi.
//middleware
middleware: 'isCustomer',
//layout
layout: 'default',
Setelah itu, kita juga menambahkan konfigurasi untuk meta tag title. Fungsinya untuk
mengubah title yang ada di website kita.
//meta
head() {
return {
title: 'Dashboard - Customer',
}
},
Dan kita juga melakukan fetching ke dalam endpoint Rest API menggunakan hook
asyncData, tujuannya agar proses fetching dilakukan secara SSR atau server side
802
rendering.
//...
}
//fetching dashboard
const dashboard = await $axios.$get('/api/customer/dashboard')
kemudian, agar response dari hook asyncData dapat dipanggil di dalam template, maka
kita perlu melakukan return terlebih dahulu.
return {
//count statistik
'pending': dashboard.data.count.pending,
'success': dashboard.data.count.success,
'expired': dashboard.data.count.expired,
'failed': dashboard.data.count.failed,
}
Dan untuk menampilkan statistik di dalam template, kurang lebih seperti berikut ini :
{{ pending }}
{{ success }}
{{ expired }}
{{ failed }}
803
Langkah 3 - Uji Coba Halaman Dashboard Customer
Silahkan reload / refresh halaman-nya atau bisa buka di URL berikut ini
http://localhost:3000/customer/dashboard, jika berhasil maka kita akan mendapatkan hasil
seperti berikut ini :
804
Konfigurasi Vuex Customer Invoice
Sekarang kita akan belajar membuat state, mutations dan actions yang nantinya akan
kita gunakan untuk proses menampilkan data invoice berdasarkan customer yang sedang
login.
Silahkan buat file baru dengan nama invoice.js di dalam folder store/customer,
kemudian masukkan kode berikut ini di dalamnya.
//state
export const state = () => ({
//invoices
invoices: [],
//page
page: 1,
})
//mutations
export const mutations = {
//mutation "SET_INVOICES_DATA"
SET_INVOICES_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
805
}
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
Dari penambahan kode di atas, pertama kita membuat 2 state baru, yaitu invoices dan
page.
//invoices
invoices: [],
//page
page: 1,
806
Untuk state invoices akan digunakan untuk menampung data invoice yang di dapatkan
dari Rest API dan untuk ditampilkan di component / view. Sedangkan untuk state page akan
digunakan untuk menyimpan nomor pagination halaman.
SET_INVOICES_DATA
SET_PAGE
SET_INVOICES_DATA
Mutation ini akan digunakan untuk mengubah nilai state invoices dengan data yang
dikirimkan dari action, data tersebut berupa response dari Rest API.
//mutation "SET_INVOICES_DATA"
SET_INVOICES_DATA(state, payload) {
SET_PAGE
Mutation ini akan digunakan untuk mengubah isi dari state page dan isinya nanti akan
dikirimkan langsung melalui component / view, yaitu berupa data nomor pagination
halaman.
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
807
//get invoices data
getInvoicesData({ commit, state }, payload) {
//...
}
Di dalam action tersebut, pertama kita membuat sebuah variable baru dengan nama
search dan isinya akan diambil dari parameter yang bernama payload dan isinya nanti
akan di dapatkan dari component / view.
//search
let search = payload ? payload : ''
Setelah itu, kita melakukan http request menggunakan Axios dengan method GET ke
dalam endpoint /api/customer/invoices?q=""&page="". Untuk parameter q akan
diisi dari variable search dan untuk parameter page akan diambil dari state yang bernama
page.
Jika proses fetching http request berhasil dilakukan, maka kita akan lakukan commit ke
dalam mutation yang bernama SET_INVOICES_DATA dengan menambahkan parameter
berupa data response yang di dapatkan dari Rest API.
808
Menampilkan Data Invoice Customer
Setelah berhasil membuat konfigurasi di dalam Vuex, maka sekarang kita akan lanjutkan
untuk menampilkan data di dalam component / view. Kita juga akan belajar membuat fitur
pencarian dan pagination.
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3">
<!-- sidebar -->
<Sidebar />
<!-- end sidebar -->
</div>
<div class="col-md-9">
<div class="card border-0 rounded shadow-sm border-top-orange">
<div class="card-body">
<h5><i class="fa fa-shopping-cart"></i> MY ORDERS</h5>
<hr>
<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control"
placeholder="cari berdasarkan no. invoice">
<div class="input-group-append">
<button class="btn btn-warning"><i class="fa fa-
search"></i>
SEARCH
</button>
</div>
</div>
</div>
809
<template v-slot:cell(grand_total)="row">
Rp. {{ formatPrice(row.item.grand_total) }}
</template>
<template v-slot:cell(status)="row">
<button v-if="row.item.status == 'pending'" class="btn
btn-sm btn-primary"><i class="fa fa-circle-notch fa-spin"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'success'" class="btn
btn-sm btn-success"><i class="fa fa-check-circle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'expired'" class="btn
btn-sm btn-warning-2"><i class="fa fa-exclamation-triangle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'failed'" class="btn
btn-sm btn-danger"><i class="fa fa-times-circle"></i> {{ row.item.status
}}</button>
</template>
</b-table>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'
export default {
//middleware
middleware: 'isCustomer',
//layout
layout: 'default',
//register components
components: {
Sidebar
},
//meta
head() {
return {
810
title: 'Invoices - Customer',
}
},
//data function
data() {
return {
//table header
fields: [{
label: 'No. Invoice',
key: 'invoice'
},
{
label: 'Grand Total',
key: 'grand_total'
},
{
label: 'Status Payment',
key: 'status',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('customer/invoice/getInvoicesData')
},
//computed
computed: {
//invoices
invoices() {
return this.$store.state.customer.invoice.invoices
},
},
}
</script>
811
<style>
</style>
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'
Setelah itu, agar component tersebut dapat digunakan di dalam template, maka kita perlu
melakukan register.
//register components
components: {
Sidebar
},
Dan untuk memanggil component tersebut di dalam template, kita bisa seperti berikut ini :
Kemudian, kita atur agar view ini menggunakan middleware isCustomer, artinya hanya
bisa diakses jika si user memiliki level customer dan sudah melakukan proses otentikasi.
//middleware
middleware: 'isCustomer',
//layout
layout: 'default',
812
Setelah itu, kita juga menambahkan konfigurasi untuk meta tag title. Fungsinya untuk
mengubah title yang ada di website kita.
//meta
head() {
return {
title: 'Invoices - Customer',
}
},
Di dalam data function, kita membuat state baru dengan nama field, state tersebut nanti
akan digenerate menjadi header dari table.
//table header
fields: [{
label: 'No. Invoice',
key: 'invoice'
},
{
label: 'Grand Total',
key: 'grand_total'
},
{
label: 'Status Payment',
key: 'status',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
Dan kita menggunakan hook asyncData untuk melakukan dispatch atau memanggil action
di dalam Vuex yang bernama getInvoicesData.
813
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('customer/invoice/getInvoicesData')
},
Jika dari proses di dalam Vuex berhasil dilakukan, maka kita akan mengambil datanya dari
state yang ada di Vuex. Disini kita menggunakan computed properti untuk mengambil data
tersebut.
//computed
computed: {
//invoices
invoices() {
return this.$store.state.customer.invoice.invoices
},
},
Dan untuk menampilkan data tersebut di dalam template, kita akan menggunakan
component dari Bootstrap Vue, yaitu <b-table>. Kurang lebih seperti berikut ini :
//...
</b-table>
Di atas, untuk properti :items akan diisi dengan data yang didapatkan dari state, dan disini
kita berikan value invoices yang kita buat di computed properti.
Sedangkan untuk properti :fields akan digunakan untuk mengenerate sebuah table
header dan disini kita berikan value state yang bernama fields, yang kita buat di dalam
data function.
814
membuatkan kita route dari file yang sudah kita buat di dalam halaman pages. Sekarang
kita akan mengaktifkan menu tersebut di dalam file component Sidebar.
Di atas yang semula masih menggunakan a href="#" kita ubah menjadi <nuxt-link dan
untuk route-nya kita arahkan ke dalam customer-invoices.
Sekarang silahkan klik menu My Orders yang ada di menu Sidebar atau bisa langsung ke
URL berikut ini http://localhost:3000/customer/invoices, maka jika berhasil kita akan
mendapatkan tampilan seperti berikut ini :
815
Langkah 3 - Membuat Fitur Pencarian
Sekarang kita lanjutkan untuk membuat fitur pencarian di dalam table, fitur ini akan
mempermudah kita jika ingin mencari data tertentu.
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3">
<!-- sidebar -->
<Sidebar />
<!-- end sidebar -->
</div>
<div class="col-md-9">
<div class="card border-0 rounded shadow-sm border-top-orange">
<div class="card-body">
<h5><i class="fa fa-shopping-cart"></i> MY ORDERS</h5>
<hr>
<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control" v-model="search"
@keypress.enter="searchData" placeholder="cari berdasarkan no. invoice">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"><i
class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>
816
row.item.status }}</button>
<button v-if="row.item.status == 'success'" class="btn
btn-sm btn-success"><i class="fa fa-check-circle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'expired'" class="btn
btn-sm btn-warning-2"><i class="fa fa-exclamation-triangle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'failed'" class="btn
btn-sm btn-danger"><i class="fa fa-times-circle"></i> {{ row.item.status
}}</button>
</template>
</b-table>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'
export default {
//middleware
middleware: 'isCustomer',
//layout
layout: 'default',
//register components
components: {
Sidebar
},
//meta
head() {
return {
title: 'Invoices - Customer',
}
},
//data function
data() {
817
return {
//table header
fields: [{
label: 'No. Invoice',
key: 'invoice'
},
{
label: 'Grand Total',
key: 'grand_total'
},
{
label: 'Status Payment',
key: 'status',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
//state search
search: ''
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('customer/invoice/getInvoicesData')
},
//computed
computed: {
//invoices
invoices() {
return this.$store.state.customer.invoice.invoices
},
},
//method
methods: {
//method "searchData"
searchData() {
818
//commit to mutation "SET_PAGE"
this.$store.commit('customer/invoice/SET_PAGE', 1)
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita menambahkan state di dalam data function,
yang bertujuan untuk menampung input form pencarian.
//state search
search: ''
Setelah itu, di dalam template kita melakukan sedikit perubahan di dalam form pencarian,
kurang lebih seperti berikut ini :
Setelah itu, kita membuat method searchData, kurang lebih seperti berikut ini :
819
//method "searchData"
searchData() {
Di atas kita menjalankan 2 perintah, yang pertama melakukan commit langsung ke dalam
mutation SET_PAGE dengan paremeter / value 1.
Dan yang kedua melakukan dispatch ke dalam action Vuex yang bernama
getInvoicesData dan kita parsing sebuah parameter yaitu this.search yang isinya
adalah kata kunci yang di dapatkan dari input form.
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3">
<!-- sidebar -->
<Sidebar />
<!-- end sidebar -->
</div>
<div class="col-md-9">
<div class="card border-0 rounded shadow-sm border-top-orange">
<div class="card-body">
<h5><i class="fa fa-shopping-cart"></i> MY ORDERS</h5>
<hr>
820
<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control" v-model="search"
@keypress.enter="searchData" placeholder="cari berdasarkan no. invoice">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"><i
class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
821
</template>
<script>
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'
export default {
//middleware
middleware: 'isCustomer',
//layout
layout: 'default',
//register components
components: {
Sidebar
},
//meta
head() {
return {
title: 'Invoices - Customer',
}
},
//data function
data() {
return {
//table header
fields: [{
label: 'No. Invoice',
key: 'invoice'
},
{
label: 'Grand Total',
key: 'grand_total'
},
{
label: 'Status Payment',
key: 'status',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
822
tdClass: 'text-center'
}
],
//state search
search: ''
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('customer/invoice/getInvoicesData')
},
//computed
computed: {
//invoices
invoices() {
return this.$store.state.customer.invoice.invoices
},
},
//method
methods: {
//method "searchData"
searchData() {
//method "changePage"
changePage(page) {
823
},
}
}
</script>
<style>
</style>
Dari perubahan kode di atas, kita menambahkan component dari Bootstrap Vue yang
bernama <b-paginate> dan isinya akan diambil dari properti computed yang kita buat
sebelumnya, dimana datanya di ambil dari state users yang ada di dalam Vuex.
Kemudian kita membuat 1 method baru dengan nama changePage, method tersebut akan
di jalankan ketika kita melakukan navigasi di button pagination. kurang lebih seperti berikut
ini :
//method "changePage"
changePage(page) {
Method di atas akan dijalankan ketika kita melakukan klik pada navigasi pagination. Dan di
dalam method tersebut kita menjalankan 2 perintah.
Yang pertama, kita melakukan commit ke dalam mutation yang bernama SET_PAGE yang
berada di dalam store/customer/invoice.js dan untuk parameternya berupa nomor
pagination yang di dapatkan dari component <b-paginate>.
824
//commit to mutation "SET_PAGE"
this.$store.commit('customer/invoice/SET_PAGE', page)
Yang kedua, kita melakukan dispatch ke dalam action yang bernama getInvoicesData,
tujuannya agar data yang ditampilkan sesuai dengan nomor pagination-nya. Dan kita
tambahkan parameter this.search apabila kita melakukan navigasi di dalam hasil
pencarian.
Sekarang kita bisa mencoba dengan melakukan refresh di halaman invoices atau bisa
melihatnya melalui link http://localhost:3000/customer/invoices dan jika berhasil maka kita
akan mendapatkan tampilan kurang lebih seperti berikut ini :
825
Menampilkan Detail Data Invoice Customer
Pada materi kali ini kita akan belajar bagaimana cara menampilkan detail data invoice dan
data item order berdasarkan invoice tersebut. Sebelum itu, kita akan menambahkan state,
mutation dan action terlebih dahulu di dalam Vuex. Action yang nantinya akan
digunakan untuk melakukan http request ke dalam server untuk mendapatan detail data
invoice-nya dan data tersebut akan di parsing ke dalam mutation untuk di update ke dalam
state.
//state
export const state = () => ({
//invoices
invoices: [],
//page
page: 1,
//invoice
invoice: {}
})
//mutations
export const mutations = {
//mutation "SET_INVOICES_DATA"
SET_INVOICES_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
826
//set value state "page"
state.page = payload
},
//mutation "SET_INVOICE_DATA"
SET_INVOICE_DATA(state, payload) {
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//set promise
827
return new Promise((resolve, reject) => {
//success
.then(response => {
//resolve promise
resolve()
})
})
},
//invoice
invoice: {}
State tersebut akan digunakan untuk menyimpan detail data invoice yang di dapatkan dari
Rest API.
Kemudian di dalam mutation, kita menambahkan juga 1 method baru dengan nama
SET_INVOICE_DATA, mutation tersebut nantinya akan digunakan untuk mengubah isi dari
state invoice dengan data response yang dikirimkan oleh action.
828
//mutation "SET_INVOICE_DATA"
SET_INVOICE_DATA(state, payload) {
Dan di dalam action, kita menambahkan 1 method baru dengan nama getDetailInvoice.
//...
}
Di dalam action tersebut kita melakukan http request menggunakan Axios dengan method
GET ke dalam endpoint /api/customer/invoices/:snap_token. Untuk parameter
snap_token nanti akan dikirimkan oleh component / view.
Jika proses fetching http request berhasil dilakukan, maka kita akan melakukan commit ke
dalam mutation yang bernama SET_INVOICE_DATA dengan parameter berupa response
data yang di dapatkan dari Rest API.
829
<b-table striped bordered hover :items="invoices.data" :fields="fields"
show-empty>
<template v-slot:cell(grand_total)="row">
Rp. {{ formatPrice(row.item.grand_total) }}
</template>
<template v-slot:cell(status)="row">
<button v-if="row.item.status == 'pending'" class="btn btn-sm
btn-primary"><i class="fa fa-circle-notch fa-spin"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'success'" class="btn btn-sm
btn-success"><i class="fa fa-check-circle"></i> {{ row.item.status
}}</button>
<button v-if="row.item.status == 'expired'" class="btn btn-sm
btn-warning-2"><i class="fa fa-exclamation-triangle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'failed'" class="btn btn-sm
btn-danger"><i class="fa fa-times-circle"></i> {{ row.item.status
}}</button>
</template>
</b-table>
830
<b-table striped bordered hover :items="invoices.data" :fields="fields"
show-empty>
<template v-slot:cell(grand_total)="row">
Rp. {{ formatPrice(row.item.grand_total) }}
</template>
<template v-slot:cell(status)="row">
<button v-if="row.item.status == 'pending'" class="btn btn-sm
btn-primary"><i class="fa fa-circle-notch fa-spin"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'success'" class="btn btn-sm
btn-success"><i class="fa fa-check-circle"></i> {{ row.item.status
}}</button>
<button v-if="row.item.status == 'expired'" class="btn btn-sm
btn-warning-2"><i class="fa fa-exclamation-triangle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'failed'" class="btn btn-sm
btn-danger"><i class="fa fa-times-circle"></i> {{ row.item.status
}}</button>
</template>
<template v-slot:cell(actions)="row">
<b-button :to="{name: 'customer-invoices-show-snap_token',
params: {snap_token: row.item.snap_token}}" variant="info" size="sm">
DETAIL
</b-button>
</template>
</b-table>
Di atas, kita menambahkan 1 button yaitu DETAIL, dan di dalamnya kita arahkan ke dalam
route yang bernama customer-invoice-show-snap_token dan kita tambahkan
paremeter juga dengan data snap_token.
INFORMASI : Untuk sekarang kamu belum bisa melihat hasilnya, karena belum memiliki
data invoice apapun di dalam database.
831
Langkah 2 - Membuat View Detail Invoice
Sekarang kita lanjutkan untuk membuat view untuk menampilkan halaman detail data
invoice beserta item order-nya. Silahkan buat folder baru dengan nama show di dalam
folder pages/customer/invoices dan di dalam folder show silahkan buat file baru
dengan nama _snap_token.vue dan masukkan kode berikut ini di dalamnya :
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3">
<!-- sidebar -->
<Sidebar />
<!-- end sidebar -->
</div>
<div class="col-md-9">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-body">
<h5><i class="fa fa-shopping-cart"></i> DETAIL ORDER</h5>
<hr>
<table class="table table-bordered">
<client-only>
832
<tr>
<td style="width: 25%">
NO. INVOICE
</td>
<td style="width: 1%">:</td>
<td>
{{ invoice.invoice }}
</td>
</tr>
<tr>
<td>
FULL NAME
</td>
<td>:</td>
<td>
{{ invoice.name }}
</td>
</tr>
<tr>
<td>
PHONE
</td>
<td>:</td>
<td>
{{ invoice.phone }}
</td>
</tr>
<tr>
<td>
COURIER / SERVICE / COST
</td>
<td>:</td>
<td>
{{ invoice.courier }} / {{ invoice.courier_service
}} / Rp.
{{ formatPrice(invoice.courier_cost) }}
</td>
</tr>
<tr>
<td>
CITY
</td>
<td>:</td>
<td>
{{ invoice.city.name }}
</td>
833
</tr>
<tr>
<td>
PROVINCE
</td>
<td>:</td>
<td>
{{ invoice.province.name }}
</td>
</tr>
<tr>
<td>
ADDRESS
</td>
<td>:</td>
<td>
{{ invoice.address }}
</td>
</tr>
<tr>
<td>
GRAND TOTAL
</td>
<td>:</td>
<td>
Rp. {{ formatPrice(invoice.grand_total) }}
</td>
</tr>
<tr>
<td>
STATUS
</td>
<td>:</td>
<td>
<button v-if="invoice.status == 'pending'"
class="btn btn-info">BAYAR SEKARANG</button>
<button v-else-if="invoice.status == 'success'"
class="btn btn-success"><i class="fa fa-check-
circle"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'expired'"
class="btn btn-warning-2"><i class="fa fa-
exclamation-triangle"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'failed'"
class="btn btn-danger"><i class="fa fa-times-
circle"></i> {{ invoice.status }}</button>
</td>
834
</tr>
</client-only>
</table>
</div>
</div>
835
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'
export default {
//middleware
middleware: 'isCustomer',
//layout
layout: 'default',
//register components
components: {
Sidebar
},
//meta
head() {
return {
title: 'Detail Order - Customer',
}
},
//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('customer/invoice/getDetailInvoice',
route.params.snap_token)
},
//computed
computed: {
invoice() {
return this.$store.state.customer.invoice.invoice
}
},
836
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita melakukan import component sidebar,
karena kita akan menampilkan component tersebut di halaman ini.
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'
Agar component dapat digunakan di dalam template, maka kita perlu melakukan register
terlebih dahulu.
//register components
components: {
Sidebar
},
Dan untuk memanggil component sidebar di dalam template, kurang lebih seperti berikut
ini :
Kemudian, kita atur agar view ini menggunakan middleware isCustomer, artinya hanya
bisa diakses jika si user memiliki level customer dan sudah melakukan proses otentikasi.
//middleware
middleware: 'isCustomer',
837
//layout
layout: 'default',
Setelah itu, kita juga menambahkan konfigurasi untuk meta tag title. Fungsinya untuk
mengubah title yang ada di website kita.
//meta
head() {
return {
title: 'Detail Order - Customer',
}
},
Di dalam hook asyncData kita melakukan dispatch atau memanggil ke dalam sebuah
action di Vuex dengan nama getDetailInvoice dan kita berikan parameter snap_token
yang diambil dari URL browser.
//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('customer/invoice/getDetailInvoice',
route.params.snap_token)
},
Jika proses di dalam Vuex sukses dilakukan, maka kita tinggal mengambil data response-nya
di dalam state yang ada di Vuex. Dan disini kita akan menggunakan computed properti
untuk mengambilnya.
//computed
computed: {
invoice() {
return this.$store.state.customer.invoice.invoice
}
},
Di dalam computed properti di atas kita membuat method dengan nama invoice yang
mana di dalamnya memanggil sebuah state yang bernama invoice yang berada di dalam
file store/customer/invoice.js.
838
return this.$store.state.customer.invoice.invoice
Dan sekarang kita bisa memanggil detail data invoice di dalam template, kurang lebih
seperti ini contohnya.
<tr>
<td>
FULL NAME
</td>
<td>:</td>
<td>
{{ invoice.name }}
</td>
</tr>
Dan untuk data item orders dari invoice, akan ditampilkan menggunakan perulangan v-
for, karena datanya bisa lebih dari 1.
Di atas, melakukan perulangan data yang diambil dari invoice.orders, dimana data
orders tersebut berada.
INFORMASI : Untuk sekarang kamu belum bisa melihat hasilnya, karena belum memiliki
data invoice apapun di dalam database.
839
840
Menampilkan Snap Payment Midtrans
Setelah berhasil menampilkan detail data invoice dan item order, maka sekarang kita akan
belajar untuk menampilkan popup pembayaran atau yang biasa disebut dengan SNAP
PAYMENT dari Midtrans.
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3">
<!-- sidebar -->
<Sidebar />
<!-- end sidebar -->
</div>
<div class="col-md-9">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-body">
<h5><i class="fa fa-shopping-cart"></i> DETAIL ORDER</h5>
<hr>
<table class="table table-bordered">
<client-only>
<tr>
<td style="width: 25%">
NO. INVOICE
</td>
<td style="width: 1%">:</td>
<td>
{{ invoice.invoice }}
</td>
</tr>
<tr>
<td>
FULL NAME
</td>
<td>:</td>
841
<td>
{{ invoice.name }}
</td>
</tr>
<tr>
<td>
PHONE
</td>
<td>:</td>
<td>
{{ invoice.phone }}
</td>
</tr>
<tr>
<td>
COURIER / SERVICE / COST
</td>
<td>:</td>
<td>
{{ invoice.courier }} / {{ invoice.courier_service
}} / Rp.
{{ formatPrice(invoice.courier_cost) }}
</td>
</tr>
<tr>
<td>
CITY
</td>
<td>:</td>
<td>
{{ invoice.city.name }}
</td>
</tr>
<tr>
<td>
PROVINCE
</td>
<td>:</td>
<td>
{{ invoice.province.name }}
</td>
</tr>
<tr>
<td>
ADDRESS
</td>
842
<td>:</td>
<td>
{{ invoice.address }}
</td>
</tr>
<tr>
<td>
GRAND TOTAL
</td>
<td>:</td>
<td>
Rp. {{ formatPrice(invoice.grand_total) }}
</td>
</tr>
<tr>
<td>
STATUS
</td>
<td>:</td>
<td>
<button @click="payment(invoice.snap_token)" v-
if="invoice.status == 'pending'"
class="btn btn-info">BAYAR SEKARANG</button>
<button v-else-if="invoice.status == 'success'"
class="btn btn-success"><i class="fa fa-check-
circle"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'expired'"
class="btn btn-warning-2"><i class="fa fa-
exclamation-triangle"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'failed'"
class="btn btn-danger"><i class="fa fa-times-
circle"></i> {{ invoice.status }}</button>
</td>
</tr>
</client-only>
</table>
</div>
</div>
843
<div class="card-body">
<table class="table" style="border-style: solid
!important;border-color: rgb(198, 206, 214) !important;">
<tbody>
<client-only>
<tr v-for="order in invoice.orders" :key="order.id"
style="background: #edf2f7;">
<td class="b-none" width="25%">
<div class="wrapper-image-cart">
<img :src="order.product.image" style="width:
100%;border-radius: .5rem">
</div>
</td>
<td class="b-none" width="50%">
<h5><b>{{ order.product.title }}</b></h5>
<table class="table-borderless" style="font-
size: 14px">
<tr>
<td style="padding: .20rem">QTY</td>
<td style="padding: .20rem">:</td>
<td style="padding: .20rem"><b>{{ order.qty
}}</b></td>
</tr>
</table>
</td>
<td class="b-none text-right">
<p class="m-0 font-weight-bold">Rp. {{
formatPrice(order.price) }}</p>
</td>
</tr>
</client-only>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'
844
export default {
//middleware
middleware: 'isCustomer',
//layout
layout: 'default',
//register components
components: {
Sidebar
},
//meta
head() {
return {
title: 'Detail Order - Customer',
}
},
//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('customer/invoice/getDetailInvoice',
route.params.snap_token)
},
//computed
computed: {
invoice() {
return this.$store.state.customer.invoice.invoice
}
},
//method
methods: {
//method "payment"
payment(snap_token) {
window.snap.pay(snap_token, {
onSuccess: function () {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
845
})
},
onPending: function () {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
})
},
onError: function () {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
})
}
})
},
}
}
</script>
<style>
</style>
Dari perubahan yang dilakukan, pertama kita menambahkan event @clik di dalam button
BAYAR SEKARANG, yang kita arahkan ke dalam method yang bernama payment dan di
dalamnya kita berikan parameter berupa snap_token.
Setelah itu, kita membuat method baru dengan nama payment dan di dalamnya kurang
lebih seperti berikut ini :
846
//method "payment"
payment(snap_token) {
window.snap.pay(snap_token, {
onSuccess: function() {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
})
},
onPending: function() {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
})
},
onError: function() {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
})
}
})
},
Di atas, di dalam function payment, akan memanggil function snap.pay dari Midtrans,
dan di dalamnya kita berikan value snap_token.
Jika pembayaran succes, maka yang akan di eksekusi adalah onSuccess, jika pending
adalah onPending dan jika pembayaran gagal maka kode ini yang di eksekusi onError.
Di mana di dalam kode status di atas, kita arahkan ke dalam route yang bernama
invoices-show-snap_token.
847
Langkah 2 - Uji Coba Snap Payment
INFORMASI : Untuk sekarang kamu belum bisa melihat hasilnya, karena belum memiliki
data invoice apapun di dalam database.
Jika button BAYAR SEKARANG di klik, maka akan menampilkan popup pembayaran atau
SNAP PAYMENT dari Midtrans, kurang lebih seperti berikut ini :
848
Installasi dan Konfigurasi PWA
PWA adalah singkatan dari Progressive Web App, sebuah aplikasi yang dibangun dengan
melakukan optimasi pada sebuah website. Optimasi yang dilakukan tidak hanya akan
membuat website menjadi lebih cepat namun juga mampu memberikan pengalaman
layaknya menggunakan aplikasi mobile [1].
Di dalam Nuxt.js untuk membuat PWA akan sangat mudah sekali, karena sudah disediakan
library atau package secara official dengan tag line Zero Config. Artinya tanpa perlu
melakukan konfigurasi yang banyak kita sudah bisa menggunakan manfaat PWA di dalam
Nuxt.js, dibandingkan jika kita membuat PWA dari 0.
Silahkan tunggu proses installasi library atau package tersebut sampai selesai dan pastikan
terhubung dengan internet.
buildModules: [
],
849
buildModules: [
'@nuxtjs/pwa', // <-- register PWA
],
pwa: {
meta: {
title: 'MI STORE - Distributor Xiaomi Indonesia Resmi',
author: 'Xiaomi Indonesia'
},
manifest: {
name: 'Xiaomi',
short_name: 'xiaomi',
description: 'Official Toko Online Penjualan Produk
Xiaomi',
lang: 'en'
},
icon: {
fileName: 'images/logo.png',
sizes: [64, 120, 144, 152, 192, 384, 512]
}
},
Dari perubahan di atas, pertama kita lakukan register library PWA di dalam config
buildModules. Dan kita juga menambahkan beberapa konfigurasi PWA untuk meta dan
kustom icon.
Jika file nuxt.config.js ditulis dengan lengkap, kurang lebih seperti berikut ini :
export default {
// Target Deployment
target: 'server',
loading: {
color: 'white', // <-- color
height: '5px' // <-- height
},
850
title: 'nuxt-ecommerce',
htmlAttrs: {
lang: 'en'
},
meta: [{
charset: 'utf-8'
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1'
},
{
hid: 'description',
name: 'description',
content: ''
}
],
link: [{
rel: 'icon',
type: 'image/x-icon',
href: 'images/logo.png'
},
{
rel: 'stylesheet',
href:
'https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600
;700&display=swap'
},
{
rel: 'stylesheet',
href:
'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.
css'
},
],
script: [
{ src: '/js/coreui.bundle.min.js' },
{ src: 'https://app.sandbox.midtrans.com/snap/snap.js', 'data-
client-key': 'SB-Mid-client-bWcHM3-QSyGV2hhw' },
]
},
851
],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
//https://dev.auth.nuxtjs.org/
'@nuxtjs/auth-next',
//https://github.com/avil13/vue-sweetalert2
'vue-sweetalert2/nuxt',
],
852
auth: {
strategies: {
//strategy "admin"
admin: {
scheme: 'local',
token: {
property: 'token',
required: true,
type: 'Bearer'
},
user: {
property: 'user',
// autoFetch: true
},
endpoints: {
login: {
url: '/api/admin/login',
method: 'post',
propertyName: 'token'
},
logout: {
url: '/api/admin/logout',
method: 'post'
},
user: {
url: '/api/admin/user',
method: 'get',
propertyName: 'user'
}
},
},
//strategy "customer"
customer: {
scheme: 'local',
token: {
property: 'token',
required: true,
type: 'Bearer'
},
user: {
property: 'user',
// autoFetch: true
},
endpoints: {
login: {
url: '/api/customer/login',
853
method: 'post',
propertyName: 'token'
},
logout: {
url: '/api/customer/logout',
method: 'post'
},
user: {
url: '/api/customer/user',
method: 'get',
propertyName: 'user'
}
},
},
},
},
CATATAN : fitur ini akan aktif ketika kita melakukan deployment di production.
[1] :
https://psti.unisayogya.ac.id/2020/02/05/berkenalan-dengan-progressive-web-apps-pwa/
854
Membuat Vuex Web Category
Sebelum menampilkan data di dalam component / view tentu saja kita akan membuat sebua
module Vuex terlebih dahulu, dimana isinya adalah state, mutation dan action.
Tujuannya agar data yang digunakan bisa global dan lebih mudah dipanggil di semua
component yang membutuhkan.
//state
export const state = () => ({
//categories
categories: [],
})
//mutations
export const mutations = {
//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {
//actions
export const actions = {
//set promise
return new Promise((resolve, reject) => {
855
//fetching Rest API "/api/web/categories" with method "GET"
this.$axios.get('/api/web/categories')
//success
.then((response) => {
//resolve promise
resolve()
})
})
},
Dari perubahan kode diatas, kita menambahkan 1 state baru dengan nama categories,
state tersebut akan digunakan untuk menampung data response yang di dapatkan dari Rest
API.
//categories
categories: [],
//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {
856
//get categories data
getCategoriesData({ commit }) {
//...
}
Di dalam action tersebut kita melakukan http request menggunakan Axios dengan method
GET ke dalam endpoint /api/web/categories.
Jika proses fetching di atas berhasil, maka kita akan melakukan commit ke dalam mutation
yang bernama SET_CATEGORIES_DATA dengan parameter data response yang di dapatkan
dari Rest API.
857
Menampilkan Detail Data Product
Pada materi kali ini kita akan belajar bagaimana cara membuat halaman detail data product,
dimana di dalam halaman ini kita nanti akan menampilkan beberapa data, seperti gambar
product, harga, deskripsi, rating, dan lain-lain. Sebelum itu, kita akan menambahkan sebuah
state, mutation dan action terlebih dahulu di dalam module Vuex.
Silahkan buka file store/web/product.js, kemudian ubah semua kode yang ada di
dalamnya dengan ini :
//state
export const state = () => ({
//products
products: [],
//page
page: 1,
//product
product: {}
})
//mutations
export const mutations = {
//mutation "SET_PRODUCTS_DATA"
SET_PRODUCTS_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
858
//mutation "SET_PRODUCT_DATA"
SET_PRODUCT_DATA(state, payload) {
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//set promise
return new Promise((resolve, reject) => {
859
//success
.then(response => {
//resolve promise
resolve()
})
})
},
Dari penambahan kode di atas, pertama kita membuat 1 state baru dengan nama product,
state tersebut yang nanti akan digunakan untuk menyimpan detail data product.
//product
product: {}
Dan di dalam mutation, kita juga menambahkan 1 method baru dengan nama
SET_PRODUCT_DATA, mutation tersebut akan digunakan untuk mengubah isi dari state
product dengan data response yang dikirim oleh action.
//mutation "SET_PRODUCT_DATA"
SET_PRODUCT_DATA(state, payload) {
Kemudian di dalam action kita membuat 1 method baru dengan nama getDetailProduct.
860
//get detail product
getDetailProduct({ commit }, payload) {
//...
}
Di dalam method tersebut, kita melakukan http request menggunakan Axios dengan
method GET ke dalam endpoint /api/web/products/:slug.
Jika proses http request di atas berhasil dijalankan, maka kita akan melakukan commit ke
dalam mutation yang bernama SET_PRODUCT_DATA dengan parameter data response yang
di dapatkan dari Rest API.
<template>
<div class="container mt-custom mb-5">
<div class="fade-in">
<div class="row">
861
<div class="col-md-8">
<div class="card border-0 rounded shadow-sm">
<div class="card-body">
<h4>{{ product.title }}</h4>
<hr>
<h6 class="mb-0 font-weight-semibold"><s class="text-
red">Rp. {{ formatPrice(product.price) }}</s> /
<strong>{{ product.discount }} %</strong></h6>
<h5 class="mb-0 font-weight-semibold mt-3 text-
success">Rp. {{ formatPrice(calculateDiscount(product)) }}
</h5>
<div class="mt-3">
<div v-html="product.description"></div>
</div>
<div class="table-responsive">
<table class="table table-sm table-borderless mb-0">
<tbody>
<client-only>
<tr>
<th class="pl-0 w-25"
scope="row"><strong>BERAT</strong></th>
<td><strong>{{ product.weight }}</strong>
gram</td>
</tr>
<tr>
<th class="pl-0 w-25"
scope="row"><strong>STOK</strong></th>
<td><strong>{{ product.stock }}</strong></td>
</tr>
</client-only>
</tbody>
</table>
</div>
<hr>
<button class="btn btn-lg btn-warning border-0 shadow-
sm"><i class="fa fa-shopping-cart"></i> TAMBAH KE
KERANJANG</button>
</div>
</div>
</div>
</div>
862
<div class="card-body">
<h5><i class="fa fa-comments"></i> ULASAN PRODUK (
<strong>{{ product.reviews_count }}</strong> ulasan )</h5>
<hr>
<div class="card bg-light shadow-sm rounded" v-for="review
in product.reviews" :key="review.id">
<div class="card-body">
<div class="row">
<div class="col-md-1">
<div class="review-avatar avatar-sm">
<img
:src="`https://ui-
avatars.com/api/?name=${review.customer.name}&background=4e73df&
color=ffffff&size=100`">
</div>
</div>
<div class="col-md-11">
<client-only>
<vue-star-rating class="mb-2"
:rating="review.rating" :star-size="20" :read-only="true" :show-
rating="false">
</vue-star-rating>
</client-only>
<strong>
<span class="text-dark">{{ review.customer.name
}}</span>
</strong>
<div class="description mt-2">
<span style="color: rgb(119, 118, 118);font-
size:15px;font-style:italic">
{{ review.review }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
863
export default {
//meta
head() {
return {
title: `${this.product.title} - MI STORE - Distributor Xiaomi
Indonesia Resmi`,
meta: [{
hid: 'og:title',
name: 'og:title',
content: `${this.product.title} - MI STORE - Distributor
Xiaomi Indonesia Resmi`,
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: `${this.product.title} - MI STORE - Distributor
Xiaomi Indonesia Resmi`,
},
{
hid: 'og:image',
name: 'og:image',
content: this.product.image
},
{
hid: 'description',
name: 'description',
content: `${this.product.title.substr(0, 30)}...`,
},
]
}
},
//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('web/product/getDetailProduct',
route.params.slug)
},
//computed
computed: {
//product
product() {
return this.$store.state.web.product.product
},
864
},
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama-tama kita atur untuk meta tag agar dinamis diambil
dari database. Dan ini sangat bagus untuk kebutuhan SEO di dalam website.
//meta
head() {
return {
title: `${this.product.title} - MI STORE - Distributor Xiaomi
Indonesia Resmi`,
meta: [{
hid: 'og:title',
name: 'og:title',
content: `${this.product.title} - MI STORE - Distributor
Xiaomi Indonesia Resmi`,
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: `${this.product.title} - MI STORE - Distributor
Xiaomi Indonesia Resmi`,
},
{
hid: 'og:image',
name: 'og:image',
content: this.product.image
},
{
hid: 'description',
name: 'description',
content: `${this.product.title.substr(0, 30)}...`,
},
]
}
},
865
Setelah itu, kita melakukan dispatch ke dalam action yang bernama getDetailProduct
dan kita tambahkan parameter berupa slug yang diambil dari URL browser. Dan proses
dispatch tersebut dilakukan di dalam hook asyncData, agar proses tersebut dijalankan
secara SSR atau server side rendering.
//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('web/product/getDetailProduct',
route.params.slug)
},
Jika dari proses dispatch di atas berhasil, sekarang kita akan ambil data yang ada di dalam
state product di Vuex menggunakan computed properti.
//computed
computed: {
//product
product() {
return this.$store.state.web.product.product
},
},
Di dalam computed properti di atas, kita membuat method dengan nama product yang
mana di dalamnya akan melakukan pengambilan data dari state product yang ada di
dalam Vuex.
Dan untuk menampilkan data product di template, kurang lebih contohnya seperti berikut ini
:
Kode di atas, digunakan untuk menampilkan gambar product. Dan untuk menampilkan data
rating dan ulasan, maka kita perlu menggunakan directive v-for, karena data yang
ditampilkan akan lebih dari 1.
866
<div class="card bg-light shadow-sm rounded" v-for="review in
product.reviews" :key="review.id">
//...
</div>
867
Membuat Fitur Pencarian Product
Setelah berhasil menampilkan data products dan detail datanya, maka sekarang kita akan
belajar bagaimana cara membuat proses pencarian di dalam website ini. Disini kita tidak
perlu membuat sebuah Vuex lagi, karena kita akan manfaatkan dari module Vuex product.
Silahkan buka file components/web/header.vue, kemudian ubah semua kode yang ada
di dalamnya menjadi seperti berikut ini :
<template>
<header class="section-header fixed-top">
<section class="header-main border-bottom">
<div class="container-fluid">
<div class="row align-items-center">
<div class="col-lg-3 col-sm-4 col-md-4 col-5">
<nuxt-link to="/" class="brand-wrap" data-abc="true">
<img src="/images/xiaomi.png" width="35" class="bg-light
p-2 rounded">
<span class="logo">MI STORE</span>
</nuxt-link>
</div>
<div class="col-lg-4 col-xl-5 col-sm-8 col-md-4 d-none d-md-
block">
<div class="search-wrap">
<div class="input-group w-100">
<input type="text" class="form-control search-form" v-
model="search" @keypress.enter="searchData" style="width:55%;"
placeholder="mau belanja apa hari ini ?">
<div class="input-group-append">
<button @click="searchData" class="btn btn-primary
search-button"> <i class="fa fa-search"></i> </button>
</div>
</div>
</div>
</div>
<div class="col-lg-5 col-xl-4 col-sm-8 col-md-4 col-7">
868
<div class="d-flex justify-content-end">
<a href="#" class="btn search-button btn-md d-md-block
ml-4"><i class="fa fa-shopping-cart"></i> <span class="ml-2">0</span> |
Rp. 0</a>
</div>
</div>
</div>
</div>
</section>
<nav class="navbar navbar-expand-md navbar-main border-bottom p-2">
<div class="container-fluid">
<div class="d-md-none my-2">
<div class="input-group">
<input type="search" name="search" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="mau belanja apa
hari ini ?">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"> <i
class="fa fa-search"></i></button>
</div>
</div>
</div>
<button class="navbar-toggler collapsed" type="button" data-
toggle="collapse" data-target="#dropdown6"
aria-expanded="false"> <span class="navbar-toggler-
icon"></span> </button>
<div class="navbar-collapse collapse" id="dropdown6">
<ul class="navbar-nav mr-auto">
<li class="nav-item dropdown"> <a class="nav-link dropdown-
toggle" href="#" data-toggle="dropdown"
data-abc="true" aria-expanded="false"><i class="fa fa-
list-ul"></i> KATEGORI</a>
<div class="dropdown-menu">
<nuxt-link :to="{name: 'categories-slug', params: {slug:
category.slug}}" class="dropdown-item" v-for="category in categories"
:key="category.id">
<img :src="category.image" width="50"> {{
category.name }}
</nuxt-link>
<div class="dropdown-divider"></div>
<nuxt-link :to="{name: 'categories'}" class="dropdown-
item active text-center" href="" data-abc="true">
LIHAT SEMUA KATEGORI <i class="fa fa-long-arrow-alt-
right"></i>
</nuxt-link>
</div>
869
</li>
<li class="nav-item"> <nuxt-link :to="{name: 'products'}"
class="nav-link" data-abc="true"><i class="fa fa-shopping-bag"></i>
SEMUA PRODUK</nuxt-link> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-info-circle"></i> TENTANG</a> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-comments"></i> KONTAK</a> </li>
</ul>
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown" v-if="!$auth.loggedIn">
<nuxt-link :to="{name: 'customer-login'}" class="nav-link"
href="#" role="button" aria-expanded="false"> <i class="fa fa-user-
circle"></i>
ACCOUNT</nuxt-link>
</li>
<li class="nav-item dropdown" v-if="$auth.loggedIn">
<nuxt-link :to="{name: 'customer-dashboard'}" class="nav-
link" href="#" role="button" aria-expanded="false"> <i class="fa fa-
tachometer-alt"></i>
DASHBOARD</nuxt-link>
</li>
</ul>
</div>
</div>
</nav>
</header>
</template>
<script>
export default {
//hook "fetch"
async fetch() {
//computed
computed: {
//categories
categories() {
return this.$store.state.web.category.categories
},
},
870
//data function
data() {
return {
//state search
search: ''
}
},
//method
methods: {
searchData() {
this.$router.push({
name: 'search',
query: {
q: this.search
}
});
}
}
}
</script>
<style scoped>
.btn {
font-size: initial;
}
</style>
Dari penambahan kode di atas, pertama kita menambahkan state di dalam data function,
yang bertujuan untuk menampung input form pencarian.
//state search
search: ''
Setelah itu, di dalam template kita melakukan sedikit perubahan di dalam form pencarian,
kurang lebih seperti berikut ini :
871
<input type="search" name="search" class="form-control" v-model="search"
@keypress.enter="searchData" placeholder="mau belanja apa hari ini ?">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"> <i class="fa
fa-search"></i></button>
</div>
Setelah itu, kita membuat method searchData, kurang lebih seperti berikut ini :
searchData() {
this.$router.push({
name: 'search',
query: {
q: this.search
}
});
}
Di dalam method searchData di atas, kita akan arahkan ke dalam sebuah route yang
bernama search dan kita tambahkan query / parameter di URL dengan nama q dan isinya
adalah data yang di ketik di dalam form pencarian.
Silahkan buat folder baru dengan nama search di dalam folder pages. Dan di dalam folder
search tersebut silahkan buat file baru dengan nama index.vue, kemudian masukkan
kode berikut ini di dalamnya.
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
872
<div class="row">
<div class="col-md-12">
<h3>PENCARIAN DENGAN KATA KUNCI : <strong>{{
$route.query.q }}</strong></h3>
<!-- Solid divider -->
<hr class="solid">
</div>
</div>
873
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
//meta
head() {
return {
title: `Pencarian untuk : ${this.$route.query.q} - MI STORE -
Distributor Xiaomi Indonesia Resmi`,
}
},
//hook "asyncData"
async asyncData({ store, query }) {
await store.dispatch('web/product/getProductsData', query.q)
},
//computed
computed: {
//products
products() {
return this.$store.state.web.product.products
},
},
}
</script>
<style>
874
</style>
Di atas, pertama kita atur meta title untuk component / view ini. Disini kita menampilkan
keyword pencarian di dalam title website.
//meta
head() {
return {
title: `Pencarian untuk : ${this.$route.query.q} - MI STORE -
Distributor Xiaomi Indonesia Resmi`,
}
},
Dan di dalam hook asyncData, kita melakukan dispatch ke dalam action yang bernama
getProductsData dengan mengirimkan parameter data yang diambil dari query yang
bernama q di URL.
//hook "asyncData"
async asyncData({ store, query }) {
await store.dispatch('web/product/getProductsData', query.q)
},
Dan setelah itu, kita akan ambil datanya menggunakan computed properti.
//computed
computed: {
//products
products() {
return this.$store.state.web.product.products
},
},
Di dalam template, kita membuat kondisi untuk menampilkan hasil pencarian data, jika data
di dalam computed properti lebih dari 0, artinya datanya ada, maka kita akan menampilkan
data products menggunakan directive v-for. Tapi, jika data-nya sama dengan atau kurang
dari 0, maka akan menampilkan pesan DATA PRODUK TIDAK DITEMUKAN!.
875
<div class="row" v-if="products.data.length > 0">
<div v-else>
//DATA PRODUK TIDAK DITEMUKAN!
</div>
876
Langkah 4 - Menambahkan Fitur Pagination
Bisa jadi di dalam proses pencarian mendapatkan result data yang banyak dan tentu tidak
boleh ditampilkan semuanya sekaligus, karena akan meberatkan website-nya. Dengan
menggunakan pagination, maka data yang ditampilkan akan di batasi perhalaman.
Silahkan buka file pages/search/index.vue, kemudian ubah semua kode yang ada
menjadi seperti berikut ini :
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<h3>PENCARIAN DENGAN KATA KUNCI : <strong>{{
$route.query.q }}</strong></h3>
<!-- Solid divider -->
<hr class="solid">
</div>
</div>
877
<h6 class="mb-0 font-weight-semibold"><s class="text-
red">Rp. {{ formatPrice(product.price) }}</s> /
<strong>{{ product.discount }} %</strong></h6>
<h5 class="mb-0 font-weight-semibold mt-3 text-
success">Rp. {{ formatPrice(calculateDiscount(product)) }}
</h5>
<hr>
<client-only>
<vue-star-rating
:rating="parseFloat(product.reviews_avg_rating)" :increment="0.5" :star-
size="20" :read-only="true" :show-rating="false" :inline="true"></vue-
star-rating>
(<strong>{{ product.reviews_count }}</strong> ulasan)
</client-only>
</div>
</div>
</div>
</div>
<!--pagination -->
<div class="row justify-content-center mt-4 mb-4">
<div class="text-center">
<b-pagination align="center" :value="products.current_page"
:total-rows="products.total"
:per-page="products.per_page" @change="changePage" aria-
controls="my-table"></b-pagination>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
//meta
head() {
return {
878
title: `Pencarian untuk : ${this.$route.query.q} - MI STORE -
Distributor Xiaomi Indonesia Resmi`,
}
},
//hook "asyncData"
async asyncData({ store, query }) {
await store.dispatch('web/product/getProductsData', query.q)
},
//computed
computed: {
//products
products() {
return this.$store.state.web.product.products
},
},
//method
methods: {
//method "changePage"
changePage(page) {
<style>
</style>
Dari penambahan kode di atas, pertama kita menambahkan component baru dari Boostrap
Vue, yaitu <b-paginate>. Dan di dalamnya kita berikan data yang diambil dari computed
879
properti yang berisi data pagination.
Ketika navigasi pagination di klik, maka akan mencalankan event @change yang mengarah
ke dalam method yang bernama changePage.
//method "changePage"
changePage(page) {
Di dalam method di atas, pertama kita melakukan commit ke dalam mutation yang bernama
SET_PAGE yang berada di dalam store/web/product.js dan untuk parameter-nya akan
berupa data angka pagination yang dikirim dari component <b-paginate>.
Dan yang kedua akan menjalankan dispatch ke dalam action yang bernama
getProductsData, dengan tujuan agar melakukan fetching ulang dengan data yang
sesuai dengan pagination dan query URL.
Sekarang, silahkan reload halaman pencarian products, jika berhasil maka kita akan
mendapatkan hasil kurang lebih seperti berikut ini :
880
881
Membuat Vuex Web Cart
Di materi kali ini kita semua akan belajar membuat konfigurasi Vuex untuk cart. Dimana
disini kita akan menambahkan beberapa state, mutations dan action untuk kebutuhan
cart. Dengan menggunakan Vuex, maka data cart yang kita buat akan bersifat global dan
mempermudah kita dalam pemanggilan data tersebut di semua component / view yang
membutuhkan.
//state
export const state = () => ({
//carts
carts: [],
//cartPrice
cartPrice: 0,
//cartWeight
cartWeight: 0
})
//mutations
export const mutations = {
//mutation "SET_CARTS_DATA"
SET_CARTS_DATA(state, payload) {
//mutation "SET_CART_PRICE"
SET_CART_PRICE(state, payload) {
882
},
//mutation "SET_CART_WEIGHT"
SET_CART_WEIGHT(state, payload) {
//actions
export const actions = {
//set promise
return new Promise((resolve, reject) => {
//dispatch "getCartPrice"
dispatch('getCartPrice')
//dispatch "getCartWeight"
dispatch('getCartWeight')
//resolve promise
resolve()
})
})
},
//set promise
883
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//store cart
storeCart({ dispatch }, payload) {
//set promise
884
return new Promise((resolve, reject) => {
//success
.then(() => {
//resolve promise
resolve()
})
//error
.catch(error => {
reject(error)
})
})
},
//remove cart
removeCart({ dispatch }, payload) {
//set promise
return new Promise((resolve, reject) => {
//dispatch "getCartsData"
dispatch('getCartsData')
//resolve promise
resolve()
})
})
},
885
}
Dari penambahan kode di atas, pertama kita membuat 3 state baru, yaitu carts,
cartPrice dan cartWeight.
//carts
carts: [],
//cartPrice
cartPrice: 0,
//cartWeight
cartWeight: 0
State carts akan digunakan untuk menyimpan data carts berdasarkan customer yang
sedang login. Dan untuk state cartPrice digunakan untuk menyimpan total harga yang
ada di dalam cart tersebut. Sedangkan state cartWeight digunakan untuk menyimpan
total berat dari semua product yang ada di dalam cart tersebut.
SET_CARTS_DATA
Mutation ini akan digunakan untuk mengubah isi dari state carts dengan data yang
dikirimkan oleh action, dimana data tersebut nantinya akan berupa data carts yang di
dapatkan dari Rest API berupa response JSON.
//mutation "SET_CARTS_DATA"
SET_CARTS_DATA(state, payload) {
SET_CART_PRICE
Mutation ini akan digunakan untuk mengubah isi dari state cartPrice dan datanya akan
didapatkaan dari action berupa response JSON dari Rest API.
886
//mutation "SET_CART_PRICE"
SET_CART_PRICE(state, payload) {
SET_CART_WEIGHT
Sama seperti di atas, mutation ini akan digunakan untuk mengubah isi dari state
cartWeight dengan data yang dikirimkan oleh action.
//mutation "SET_CART_WEIGHT"
SET_CART_WEIGHT(state, payload) {
getCartsData
getCartPrice
getCartWeight
storeCart
removeCart
getCartsData
Action ini digunakan untuk mendapatkan data carts dari database sesuai dengan customer
yang sedang login. Di dalam action ini kita melakukan http request menggunakan Axios
dengan method GET ke dalam endpoint /api/web/carts.
Jika dari proses fetching di atas berhasil, maka kita akan menjalankan beberapa kode lagi,
pertama kita akan melakukan commit ke dalam mutation yang bernama SET_CARTS_DATA
dengan mengirimkan parameter response data.
887
//commit ti mutation "SET_CARTS_DATA"
commit('SET_CARTS_DATA', response.data.data)
Setelah itu, kita akan menjalankan dispatch ke dalam 2 action yang nanti akan kita bahas,
yaitu getCartPrice dan getCartWeight.
//dispatch "getCartPrice"
dispatch('getCartPrice')
//dispatch "getCartWeight"
dispatch('getCartWeight')
Tujuannya menjalankan 2 action tersebut agar kita bisa mengambil data total price dan
weight secara bersamaan.
getCartPrice
Action ini akan digunakan untuk mendapatkan data total price yang ada di dalam cart
sesuai dengan customer yang sedang login. Disini kita melakukan http request
menggunakan Axios dengan method GET ke dalam endpoint
/api/web/cart/total_price.
Jika proses fetching di atas berhasil, maka kita akan melakukan commit ke dalam mutation
yang bernama SET_CART_PRICE dan kita kirimkan parameter berupa data response yang
didapatkan dari Rest API.
getCartWeight
Sama seperti dia tas, bedanya action ini digunakan untuk mengambil total berat dari semua
product yang ada di dalam carts. Disini kita melakukan http request menggunakan Axios
dengan method GET ke dalam endpoint /api/web/cart/total_weight.
888
//fetching Rest API "/api/web/carts/total_weight" with method "GET"
this.$axios.get('/api/web/carts/total_weight')
Jika proses fetching di atas berhasil dilakukan, maka kita akan melakukan commit ke dalam
mutation yang bernama SET_CART_WEIGHT dengan menambahkan parameter data
repsonse dari Rest API.
storeCart
Action ini digunakan untuk proses insert data cart ke dalam database atau biasa disebut
dengan add to cart. Dan disini kita melakukan sending data menggunakan Axios dengan
method POST ke dalam endpoint /api/web/cart dengan mengirimkan parameter
payload. Parameter tersebut nanti akan berisi beberapa data yang dikirimkan oleh
component / view, seperti product_id, price, qty dan lain-lain.
Jika proses sending data di atas berhasil dilakukan, maka kita akan melakukan proses
dispatch ke dalam action yang bernama getCartsData, dengan tujuan agar dilakukan
fetching ulang untuk mendapatkan data carts yang terbaru.
removeCart
Terakhir adalah action yang digunakan untuk menghapus data cart. Disini kita melakukan
http request menggunakan Axios dengan method POST ke dalam endpoint
/api/web/cart/remove dan kita juga mengirimkan parameter payload, dimana
parameter tersebut akan berisi cart_id yang dikirimkan oleh component / view.
889
//fetching Rest API "/api/web/carts/remove" with method "GET"
this.$axios.post('/api/web/carts/remove', payload)
Jika proses hapus data cart berhasil dilakukan, maka kita akan melakukan dispatch ke dalam
action yang bernama getCartsData, agar data carts diperbarui setelah adanya proses
hapus data.
//dispatch "getCartsData"
dispatch('getCartsData')
890
Membuat Proses Add To Cart.md
Setelah berhasil menambahkan Vuex untuk cart, sekarang kita akan lanjutkan belajat
membuat fitur add to cart atau melakukan proses insert data cart ke dalam database sesuai
dengan customer yang sedang login.
<template>
<div class="container mt-custom mb-5">
<div class="fade-in">
<div class="row">
<div class="col-md-8">
<div class="card border-0 rounded shadow-sm">
<div class="card-body">
<h4>{{ product.title }}</h4>
<hr>
<h6 class="mb-0 font-weight-semibold"><s class="text-
red">Rp. {{ formatPrice(product.price) }}</s> /
<strong>{{ product.discount }} %</strong></h6>
<h5 class="mb-0 font-weight-semibold mt-3 text-
success">Rp. {{ formatPrice(calculateDiscount(product)) }}
</h5>
<div class="mt-3">
<div v-html="product.description"></div>
</div>
<div class="table-responsive">
<table class="table table-sm table-borderless mb-0">
<tbody>
891
<client-only>
<tr>
<th class="pl-0 w-25"
scope="row"><strong>BERAT</strong></th>
<td><strong>{{ product.weight }}</strong>
gram</td>
</tr>
<tr>
<th class="pl-0 w-25"
scope="row"><strong>STOK</strong></th>
<td><strong>{{ product.stock }}</strong></td>
</tr>
</client-only>
</tbody>
</table>
</div>
<hr>
<button @click="addToCart(product.id,
calculateDiscount(product), product.weight)" class="btn btn-lg btn-
warning border-0 shadow-sm"><i class="fa fa-shopping-cart"></i> TAMBAH
KE
KERANJANG</button>
</div>
</div>
</div>
</div>
892
</div>
<div class="col-md-11">
<client-only>
<vue-star-rating class="mb-2"
:rating="review.rating" :star-size="20" :read-only="true" :show-
rating="false">
</vue-star-rating>
</client-only>
<strong>
<span class="text-dark">{{ review.customer.name
}}</span>
</strong>
<div class="description mt-2">
<span style="color: rgb(119, 118, 118);font-
size:15px;font-style:italic">
{{ review.review }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
//meta
head() {
return {
title: `${this.product.title} - MI STORE - Distributor Xiaomi
Indonesia Resmi`,
meta: [{
hid: 'og:title',
name: 'og:title',
content: `${this.product.title} - MI STORE - Distributor
Xiaomi Indonesia Resmi`,
},
{
hid: 'og:site_name',
893
name: 'og:site_name',
content: `${this.product.title} - MI STORE - Distributor
Xiaomi Indonesia Resmi`,
},
{
hid: 'og:image',
name: 'og:image',
content: this.product.image
},
{
hid: 'description',
name: 'description',
content: `${this.product.title.substr(0, 30)}...`,
},
]
}
},
//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('web/product/getDetailProduct',
route.params.slug)
},
//computed
computed: {
//product
product() {
return this.$store.state.web.product.product
},
},
//method
methods: {
//method "addToCart"
async addToCart(productId, price, weight) {
//redirect
return this.$router.push({
name: 'customer-login'
})
894
}
//redirect
return this.$router.push({
name: 'customer-login'
})
}
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Product Berhasil Ditambahkan di Keranjang!",
icon: 'success',
showConfirmButton: false,
timer: 3000
})
})
}
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita manmabhkan event @click di dalam button
TAMBAH KE KERANJANG, dimana event tersebut kita arahkan ke dalam method yang
895
bernama addToCart.
//redirect
return this.$router.push({
name: 'customer-login'
})
}
Yang kedua, pengecekan apakah user yang login tersebut itu customer atau bukan, jika
buka customer maka akan diarahkan juga ke dalam route login customer.
//redirect
return this.$router.push({
name: 'customer-login'
})
}
Jika kedua kondisi di atas sudah terpenuhi, sekarang kita bisa melakukan proses add to cart.
Disini kita melakukan dispatch ke dalam action yang bernama storeCart dengan
mengirimkan beberapa parameter.
896
//dispatch to action "storeCart" vuex
await this.$store.dispatch('web/cart/storeCart', {
product_id: productId,
price: price,
qty: 1,
weight: weight
})
Jika proses add to cart yang ada di dalam action Vuex berhasil dilakukan, maka kita akan
menampilkan alert sukses menggunakan Sweet Alert2.
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Product Berhasil Ditambahkan di Keranjang!",
icon: 'success',
showConfirmButton: false,
timer: 3000
})
Sekarang silahkan buka salah satu product dan klik button TAMBAH KE KERANJANG. Jika
kita belum melakukan otentikasi, maka akan diarahkan ke halaman login. Tapi jika sudah
melakukan otentikasi dan klik button tersebut, maka kita akan mendapatkan hasil seperti
berikut ini :
897
Dan kita bisa memastikan apakah data cart tersebut sudah berhasil dimasukkan, kita bisa
cek di dalam database dan di datable carts. Kurang lebih seperti berikut ini :
898
Menampilkan Data Cart di Header
Jika kita perhatikan di materi sebelumnya, saat kita berhasil melakukan proses add to cart,
di dalam header / navbar kita belum menampilkan data cart tersebut secara realtime /
reactive. Yups, karena memang kita belum menambahkan kode untuk menampilkan data
cart tersebut.
<template>
<header class="section-header fixed-top">
<section class="header-main border-bottom">
<div class="container-fluid">
<div class="row align-items-center">
<div class="col-lg-3 col-sm-4 col-md-4 col-5">
<nuxt-link to="/" class="brand-wrap" data-abc="true">
<img src="/images/xiaomi.png" width="35" class="bg-light
p-2 rounded">
<span class="logo">MI STORE</span>
</nuxt-link>
</div>
<div class="col-lg-4 col-xl-5 col-sm-8 col-md-4 d-none d-md-
block">
<div class="search-wrap">
<div class="input-group w-100">
<input type="text" class="form-control search-form" v-
model="search" @keypress.enter="searchData" style="width:55%;"
placeholder="mau belanja apa hari ini ?">
<div class="input-group-append">
<button @click="searchData" class="btn btn-primary
search-button"> <i class="fa fa-search"></i> </button>
</div>
</div>
</div>
</div>
<div class="col-lg-5 col-xl-4 col-sm-8 col-md-4 col-7">
<div class="d-flex justify-content-end">
<nuxt-link :to="{name: 'cart'}" class="btn search-button
btn-md d-md-block ml-4"><i class="fa fa-shopping-cart"></i> <span
899
class="ml-2">{{ cartTotal }}</span> | Rp. {{ formatPrice(cartPrice)
}}</nuxt-link>
</div>
</div>
</div>
</div>
</section>
<nav class="navbar navbar-expand-md navbar-main border-bottom p-2">
<div class="container-fluid">
<div class="d-md-none my-2">
<div class="input-group">
<input type="search" name="search" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="mau belanja apa
hari ini ?">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"> <i
class="fa fa-search"></i></button>
</div>
</div>
</div>
<button class="navbar-toggler collapsed" type="button" data-
toggle="collapse" data-target="#dropdown6"
aria-expanded="false"> <span class="navbar-toggler-
icon"></span> </button>
<div class="navbar-collapse collapse" id="dropdown6">
<ul class="navbar-nav mr-auto">
<li class="nav-item dropdown"> <a class="nav-link dropdown-
toggle" href="#" data-toggle="dropdown"
data-abc="true" aria-expanded="false"><i class="fa fa-
list-ul"></i> KATEGORI</a>
<div class="dropdown-menu">
<nuxt-link :to="{name: 'categories-slug', params: {slug:
category.slug}}" class="dropdown-item" v-for="category in categories"
:key="category.id">
<img :src="category.image" width="50"> {{
category.name }}
</nuxt-link>
<div class="dropdown-divider"></div>
<nuxt-link :to="{name: 'categories'}" class="dropdown-
item active text-center" href="" data-abc="true">
LIHAT SEMUA KATEGORI <i class="fa fa-long-arrow-alt-
right"></i>
</nuxt-link>
</div>
</li>
<li class="nav-item"> <nuxt-link :to="{name: 'products'}"
900
class="nav-link" data-abc="true"><i class="fa fa-shopping-bag"></i>
SEMUA PRODUK</nuxt-link> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-info-circle"></i> TENTANG</a> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-comments"></i> KONTAK</a> </li>
</ul>
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown" v-if="!$auth.loggedIn">
<nuxt-link :to="{name: 'customer-login'}" class="nav-link"
href="#" role="button" aria-expanded="false"> <i class="fa fa-user-
circle"></i>
ACCOUNT</nuxt-link>
</li>
<li class="nav-item dropdown" v-if="$auth.loggedIn">
<nuxt-link :to="{name: 'customer-dashboard'}" class="nav-
link" href="#" role="button" aria-expanded="false"> <i class="fa fa-
tachometer-alt"></i>
DASHBOARD</nuxt-link>
</li>
</ul>
</div>
</div>
</nav>
</header>
</template>
<script>
export default {
//hook "fetch"
async fetch() {
}
},
901
//computed
computed: {
//categories
categories() {
return this.$store.state.web.category.categories
},
//cartPrice
cartPrice() {
return this.$store.state.web.cart.cartPrice
},
//cartTotal
cartTotal() {
return this.$store.state.web.cart.carts.length
},
},
//data function
data() {
return {
//state search
search: ''
}
},
//method
methods: {
searchData() {
this.$router.push({
name: 'search',
query: {
q: this.search
}
});
}
}
}
</script>
<style scoped>
.btn {
font-size: initial;
}
902
</style>
Dari penambahan kode di atas, pertama di dalam hook fetch kita melakukan dispatch ke
dalam 2 action Vuex, yaitu getCartsData dan getCartPrice.
Di atas, kita buat sebuah kondisi terlebih dahulu sebelum melakukan dispatch, yaitu apakah
user tersebut sudah melakukan proses otentikasi dan memiliki role sebagai customer.
Setelah itu, kita akan memanggil data total price dan total cart di dalam computed properti,
yang mana datanya akan diambil dari state yang ada di dalam Vuex.
//cartPrice
cartPrice() {
return this.$store.state.web.cart.cartPrice
},
//cartTotal
cartTotal() {
return this.$store.state.web.cart.carts.length
},
Dan untuk menampilkan data tersebut di dalam template, kita bisa menggunakan kode
seperti berikut ini :
903
menampilkan data carts tersebut di dalam header. Kurang lebih seperti berikut ini :
904
Menampilkan Cart Setelah Login dan Menghapus Cart
Setelah Logout
Sekarang kita mengalami problem lagi, silahkan logout dan perhatikan di dalam header data
carts tersebut masih ada. Seharusnya jika kita sudah melakukan proses logout, maka data
carts juga ikut dihilangkan. Sekarang silahkan reload / refresh halamannya, baru data carts
tersebut berhasil hilang di dalam header.
Dan disini kita mengalami problem lagi yang kedua, setelah berhasil logout, sekarang
silahkan login lagi ke dalam akun customer dan perhatikan di dalam header, data carts tidak
ditampilkan. Seharusnya ketika berhasil login maka data carts tersebut harus ditampilkan.
905
async login() {
await this.$auth.loginWith('customer', {
data: {
email: this.user.email,
password: this.user.password
}
})
.then(() => {
//redirect
this.$router.push({
name: 'customer-dashboard'
})
})
.catch(error => {
//assign validation
this.validation = error.response.data
})
}
906
async login() {
await this.$auth.loginWith('customer', {
data: {
email: this.user.email,
password: this.user.password
}
})
.then(() => {
//redirect
this.$router.push({
name: 'customer-dashboard'
})
})
.catch(error => {
//assign validation
this.validation = error.response.data
})
}
Di atas, kita melakukan dispatch ke dalam 2 action jika proses otentikasi berhasil dilakukan.
Sekarang, silahkan login dan perhatikan di header, maka data carts sudah berhasil
ditampilkan.
907
Silahkan buka file components/web/sidebar.vue, kemudian cari kode berikut ini :
//method "logout"
async logout() {
//logout auth
await this.$auth.logout()
//method "logout"
async logout() {
//logout auth
await this.$auth.logout()
//set state
this.$store.commit('web/cart/SET_CARTS_DATA', [])
this.$store.commit('web/cart/SET_CART_PRICE', 0)
Di atas, kita melakukan commit ke dalam 2 mutation setelah proses logout berhasil, yaitu
melakukan set ke SET_CARTS_DATA dengan array kosong dan SET_CART_PRICE dengan
angka 0.
Sekarang, silahkan lakukan proses logout, maka data carts di header juga akan dihapus atau
dihilangkaan.
908
Menampilkan Halaman Cart
Setelah berhasil membuat proses add to cart, maka sekarang kita lanjutkan belajar
bagaimana cara membuat halaman yang digunakan untuk menampilkan data carts
tersebut.
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row" v-if="carts.length > 0">
<!-- jika data carts ada, maka tampilkan -->
<div class="col-md-7">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<h5>DETAIL PESANAN</h5>
<hr>
<table class="table table-cart">
<tbody>
<client-only>
<tr v-for="cart in carts" :key="cart.id"
style="background: #edf2f7;">
<td class="b-none" width="25%">
<div class="wrapper-image-cart">
<img :src="cart.product.image" style="width:
100%;border-radius: .5rem">
</div>
</td>
<td class="b-none" width="50%">
<h5><b>{{ cart.product.title }}</b></h5>
<table class="table-borderless table-font">
<tr>
<td style="padding: .20rem">QTY</td>
<td style="padding: .20rem">:</td>
<td style="padding: .20rem"><b>{{ cart.qty
}}</b></td>
</tr>
909
</table>
</td>
<td class="b-none text-right">
<p class="m-0 font-weight-bold">Rp. {{
formatPrice(cart.price) }}
</p>
<p class="m-0">
<s style="text-decoration-color:red">Rp.
{{ formatPrice(cart.product.price * cart.qty)
}}</s>
</p>
<br>
<div class="text-right">
<button class="btn btn-sm btn-danger">
<i class="fa fa-trash"></i>
</button>
</div>
</td>
</tr>
</client-only>
</tbody>
</table>
910
Rp.</td>
<td class="set-td border-0 text-right">
<p class="m-0" id="ongkir-cart">
</p>
</td>
</tr>
<tr>
<td class=" text-left border-0">
<p class="font-weight-bold m-0 h5 text-
uppercase">Grand Total </p>
</td>
<td class=" border-0 text-right"> : Rp.</td>
<td class=" border-0 text-right">
<p class="font-weight-bold m-0 h5" align="right">
</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-5">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<h5>DETAIL CUSTOMER</h5>
<hr>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="font-weight-bold">NAMA LENGKAP</label>
<input type="text" class="form-control"
id="nama_lengkap" placeholder="Nama Lengkap"
v-model="customer.name">
<div v-if="validation.name" class="mt-2 alert alert-
danger">
Masukkan Nama Lengkap
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="font-weight-bold">NO. HP /
WHATSAPP</label>
911
<input type="number" class="form-control" id="phone"
placeholder="No. HP / WhatsApp"
v-model="customer.phone">
<div v-if="validation.phone" class="mt-2 alert alert-
danger">
Masukkan No. Telp
</div>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label class="font-weight-bold">ALAMAT LENGKAP</label>
<textarea class="form-control" id="alamat" rows="3"
placeholder="Alamat Lengkap"
v-model="customer.address"></textarea>
<div v-if="validation.address" class="mt-2 alert
alert-danger">
Masukkan Alamat Lengkap
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row justify-content-center" v-else>
<!-- data carts tidak tersedia -->
<div class="col-md-10">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<div class="col-sm-12 empty-cart-cls text-center">
<img src="/images/shopping-cart.png" width="150"
height="150" class="img-fluid mb-4 mr-3">
<h3><strong>Keranjang Belanja Kosong :)</strong></h3>
<nuxt-link :to="{name: 'products'}" class="btn btn-warning
btn-lg mt-4" data-abc="true">LANJUTKAN BELANJA
</nuxt-link>
</div>
</div>
</div>
</div>
</div>
</div>
912
</div>
</template>
<script>
export default {
//middleware
middleware: 'isCustomer',
//meta
head() {
return {
title: 'Cart - MI STORE - Distributor Xiaomi Indonesia Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/shopping-cart.png'
},
{
hid: 'description',
name: 'description',
content: 'Cart - Official Toko Online Penjualan Produk
Xiaomi'
},
]
}
},
//hook asyncData
async asyncData({ store }) {
913
},
//computed
computed: {
//cart data
carts() {
return this.$store.state.web.cart.carts
},
//cart weight
cartWeight() {
return this.$store.state.web.cart.cartWeight
},
//cart price
cartPrice() {
return this.$store.state.web.cart.cartPrice
},
},
//data function
data() {
return {
//state customer
customer: {
name: '',
phone: '',
address: ''
},
//state validation
validation: {
name: false,
phone: false,
address: false
},
}
},
}
</script>
<style scoped>
.table-cart {
border-style: solid !important;
border-color: rgb(198, 206, 214) !important;
914
}
.table-font {
font-size: 14px;
}
</style>
Dari penambahan kode di atas, pertama kita atur component / view ini menggunakan
middleware isCustomer, artinya hanya bisa diakses jika memiliki role sebagai customer.
//middleware
middleware: 'isCustomer',
Dan kita atur juga meta tag dari component / view ini, seperrti title, og:image dan lain-
lain.
915
//meta
head() {
return {
title: 'Cart - MI STORE - Distributor Xiaomi Indonesia Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/shopping-cart.png'
},
{
hid: 'description',
name: 'description',
content: 'Cart - Official Toko Online Penjualan Produk
Xiaomi'
},
]
}
},
Di dalam hook asyncData kita melakukan dispatch ke dalam action di dalam Vuex yang
bernama getCartsData.
//hook asyncData
async asyncData({ store }) {
},
916
Jika proses dari action di atas berhasil, maka sekarang kita akan mengambil beberapa data
dari state yang ada di Vuex, yaitu data carts, total price dan total weight.
Disini kita menggunakan computed properti untuk mengambil data-data tersebut dari state
yang ada di Vuex.
//computed
computed: {
//cart data
carts() {
return this.$store.state.web.cart.carts
},
//cart weight
cartWeight() {
return this.$store.state.web.cart.cartWeight
},
//cart price
cartPrice() {
return this.$store.state.web.cart.cartPrice
},
},
Dan di dalam data function kita membuat 2 state baru, yaitu customer dan validation.
Untuk state customer akan digunakan untuk menampung isi yang dikirimkan dari input
form. Sedangkan validation akan digunakan untuk menampilkan error valaidasi.
917
//data function
data() {
return {
//state customer
customer: {
name: '',
phone: '',
address: ''
},
//state validation
validation: {
name: false,
phone: false,
address: false
},
}
},
Di dalam template, kita membuat kondisi untuk menampilkan data carts, jika data carts
lebih dari 0, maka kita akan melakukan perulangan data carts tersebut. Tapi jika data carts
sama dengan atau kurang dari 0, maka kita akan menampilkan pesan bahwa data carts
tidak tersedia.
</div>
918
Tapi, jika kita belum memiliki data carts, maka akan mendapatkan tampilan seperti berikut
ini :
919
Membuat Fungsi Remove Cart
Setelah berhasil menampilkan data carts di component / view, sekarang kita akan lanjutkan
untuk membuat fitur remove data cart atau menghapus item cart. Kondisi ini sangat sering
terjadi, customer memasukkan data product ke cart dan ingin membatalkannya.
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row" v-if="carts.length > 0">
<!-- jika data carts ada, maka tampilkan -->
<div class="col-md-7">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<h5>DETAIL PESANAN</h5>
<hr>
<table class="table table-cart">
<tbody>
<client-only>
<tr v-for="cart in carts" :key="cart.id"
style="background: #edf2f7;">
<td class="b-none" width="25%">
<div class="wrapper-image-cart">
<img :src="cart.product.image" style="width:
100%;border-radius: .5rem">
</div>
</td>
<td class="b-none" width="50%">
<h5><b>{{ cart.product.title }}</b></h5>
<table class="table-borderless table-font">
<tr>
<td style="padding: .20rem">QTY</td>
<td style="padding: .20rem">:</td>
<td style="padding: .20rem"><b>{{ cart.qty
}}</b></td>
</tr>
</table>
920
</td>
<td class="b-none text-right">
<p class="m-0 font-weight-bold">Rp. {{
formatPrice(cart.price) }}
</p>
<p class="m-0">
<s style="text-decoration-color:red">Rp.
{{ formatPrice(cart.product.price * cart.qty)
}}</s>
</p>
<br>
<div class="text-right">
<button @click.prevent="removeCart(cart.id)"
class="btn btn-sm btn-danger">
<i class="fa fa-trash"></i>
</button>
</div>
</td>
</tr>
</client-only>
</tbody>
</table>
921
<td class="set-td border-0 text-right">
<p class="m-0" id="ongkir-cart">
</p>
</td>
</tr>
<tr>
<td class=" text-left border-0">
<p class="font-weight-bold m-0 h5 text-
uppercase">Grand Total </p>
</td>
<td class=" border-0 text-right"> : Rp.</td>
<td class=" border-0 text-right">
<p class="font-weight-bold m-0 h5" align="right">
</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-5">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<h5>DETAIL CUSTOMER</h5>
<hr>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="font-weight-bold">NAMA LENGKAP</label>
<input type="text" class="form-control"
id="nama_lengkap" placeholder="Nama Lengkap"
v-model="customer.name">
<div v-if="validation.name" class="mt-2 alert alert-
danger">
Masukkan Nama Lengkap
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="font-weight-bold">NO. HP /
WHATSAPP</label>
<input type="number" class="form-control" id="phone"
922
placeholder="No. HP / WhatsApp"
v-model="customer.phone">
<div v-if="validation.phone" class="mt-2 alert alert-
danger">
Masukkan No. Telp
</div>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label class="font-weight-bold">ALAMAT LENGKAP</label>
<textarea class="form-control" id="alamat" rows="3"
placeholder="Alamat Lengkap"
v-model="customer.address"></textarea>
<div v-if="validation.address" class="mt-2 alert
alert-danger">
Masukkan Alamat Lengkap
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row justify-content-center" v-else>
<!-- data carts tidak tersedia -->
<div class="col-md-10">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<div class="col-sm-12 empty-cart-cls text-center">
<img src="/images/shopping-cart.png" width="150"
height="150" class="img-fluid mb-4 mr-3">
<h3><strong>Keranjang Belanja Kosong :)</strong></h3>
<nuxt-link :to="{name: 'products'}" class="btn btn-warning
btn-lg mt-4" data-abc="true">LANJUTKAN BELANJA
</nuxt-link>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
923
</template>
<script>
export default {
//middleware
middleware: 'isCustomer',
//meta
head() {
return {
title: 'Cart - MI STORE - Distributor Xiaomi Indonesia Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/shopping-cart.png'
},
{
hid: 'description',
name: 'description',
content: 'Cart - Official Toko Online Penjualan Produk
Xiaomi'
},
]
}
},
//hook asyncData
async asyncData({ store }) {
},
924
//computed
computed: {
//cart data
carts() {
return this.$store.state.web.cart.carts
},
//cart weight
cartWeight() {
return this.$store.state.web.cart.cartWeight
},
//cart price
cartPrice() {
return this.$store.state.web.cart.cartPrice
},
},
//data function
data() {
return {
//state customer
customer: {
name: '',
phone: '',
address: ''
},
//state validation
validation: {
name: false,
phone: false,
address: false
},
}
},
//method
methods: {
//method "removeCart"
async removeCart(cartId) {
await this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
925
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
}).then((result) => {
if (result.isConfirmed) {
.then(async () => {
//dispatch action "getCartPrice"
await
this.$store.dispatch('web/cart/getCartPrice')
//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Product Berhasil Dihapus dari
Keranjang!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
})
}
})
},
}
}
</script>
<style scoped>
.table-cart {
border-style: solid !important;
border-color: rgb(198, 206, 214) !important;
}
.table-font {
font-size: 14px;
926
}
</style>
Dari pernamabahan kode di atas, pertama kita berikan event @click di dalam button
delete cart, yang mana kita arahkan ke dalam method yang bernama removeCart dengan
memberikan parameter cart_id di dalamnya.
Setelah itu, kita membuat method tersebut dan di dalamnya kita kombinasikan dengan
Sweet Alert2.
await this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
})
Jika kita klik button YA, HAPUS!, maka akan menjalankan dispatch ke dalam action yang
bernama removeCart dengan parameter cart_id.
Dan jika proses action tersebut berhasil dilakukan, maka kita akan melakukan dispatch lagi
untuk memperbaruin total price yang ada di dalam carts.
927
//dispatch action "getCartPrice"
await this.$store.dispatch('web/cart/getCartPrice')
Kemudian kita tampilkan alert sukses hapus data menggunakan Sweet Alert2. Kurang lebih
seperti berikut ini :
//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Product Berhasil Dihapus dari Keranjang!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
928
Membuat Vuex Web RajaOngkir
Sebelum kita lanjutkan lagi di halaman cart untuk menampilkan informasi provinsi,
kota/kabupaten dan perhitungan biaya ongkos kirim, maka kita harus melakukan konfigurasi
Vuex untuk RajaOngkir terlebih dahulu.
//state
export const state = () => ({
//provinces
provinces: [],
//cities
cities: [],
//costs ongkir
costs: []
})
//mutations
export const mutations = {
//mutation "SET_PROVINCES_DATA"
SET_PROVINCES_DATA(state, payload) {
//mutation "SET_CITIES_DATA"
SET_CITIES_DATA(state, payload) {
929
//mutation "SET_COSTS_DATA"
SET_COSTS_DATA(state, payload) {
//actions
export const actions = {
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//set promise
return new Promise((resolve, reject) => {
930
//commit ti mutation "SET_CITIES_DATA"
commit('SET_CITIES_DATA', response.data.data)
//resolve promise
resolve()
})
})
},
//getOngkirData
getOngkirData({ commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
}
Dari penambahan kode di atas, pertama kita menambahkan 3 state baru, yaitu provinces,
cities dan costs.
931
//provinces
provinces: [],
//cities
cities: [],
//costs ongkir
costs: []
Untuk state provinces nantinya akan digunakan untuk menyimpan list data provinsi di
Indonesia. Dan untuk state cities akan digunakan untuk menampilkan list data
kota/kabupaten berdasarkan provinsi tertentu. Kemudian yang terakhir, yaitu state costs
akan digunakan untuk menyimpan data ongkos kirim dari kurir tertentu.
SET_PROVINCES_DATA
SET_CITIES_DATA
SET_COSTS_DATA
SET_PROVINCES_DATA
Mutation ini akan digunakan untuk mengubah isi dari state provinces dengan data yang
dikirimkan dari action. Data tersebut berupa response list data provinsi yang di dapatkan
dari Rest API.
//mutation "SET_PROVINCES_DATA"
SET_PROVINCES_DATA(state, payload) {
SET_CITIES_DATA
Mutation ini digunakan untuk mengubah isi dari state cities, dimana datanya akan
dikirimkan oleh action berupa list data kota/kabupaten berdasarkan provinsi tertentu.
932
//mutation "SET_CITIES_DATA"
SET_CITIES_DATA(state, payload) {
SET_COSTS_DATA
Mutation ini digunakan untuk mengubah isi dari state costs dengan data yang dikirimkan
oleh action dan data yang dikirimkan akan berupa informasi biaya pengiriman / ongkos kirim
dari kurir.
//mutation "SET_COSTS_DATA"
SET_COSTS_DATA(state, payload) {
Dan di dalam actions kita juga menambahkan 3 method baru, diantaranya adalah
getProvincesData
getCitiesData
getOngkirData
getProvincesData
Action ini akan digunakan untuk mendapatkan list data provinsi dari database. Dimana di
dalam action ini kita melakukan http request menggunakan Axios dengan method GET ke
dalam endpoint /api/web/rajaongkir/provinces.
Jika proses http request di atas berhasil dilakukan, maka akan melakukan commit ke dalam
mutation yang bernama SET_PROVINCES_DATA dengan parameter berupa data response.
933
//commit ti mutation "SET_PROVINCES_DATA"
commit('SET_PROVINCES_DATA', response.data.data)
getCitiesData
Action ini akan digunakan untuk mendapatkan list data kota/kabupaten berdasarkan
provinsi tertentu. Disini kita melakukan http request menggunakan Axios dengan method
POST ke dalam endpoint /api/web/rajaongkir/cities dan kita kirimkan juga
parameter payload yang isinya adalah ID dari provinsi .
Jika dari proses fetching di atas berhasil dilakukan, maka akan melakukan commit ke dalam
mutation yang bernama SET_CITIES_DATA dengan parameter berupa data response.
getOngkirData
Action ini akan digunakan untuk melakukan perhitungan biaya ongkos kirim. Disini kita
melakukan http request menggunakan Axios dengan method POST ke dalam endpoint
/api/web/rajaongkir/checkOngkir dan kita tambahkan parameter payload. Dimana
di dalam parameter tersebut akan berisi beberapa data, diantaranya seperti ID
kota/kabupaten tujuan, weight dan courier.
Jika dari proses perhitungan biaya ongkos kirim berhasil dilakukan, maka akan melakukan
commit ke dalam mutation yang bernama SET_COSTS_DATA dengan parameter data
response.
934
//commit ti mutation "SET_COSTS_DATA"
commit('SET_COSTS_DATA', response.data.data)
935
Menghitung Biaya Ongkos Kirim
Pada materi kali ini, kita akan menambahkan proses perhitungan biaya ongkos kirim di
dalam halaman cart. Disini kita akan menampilkan list data provinsi, kota/kabupaten dan
kurir. Ketiga data tersebutlah yang akan digunakan untuk menentukan biaya ongkos kirim di
dalam RajaOngkir.
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row" v-if="carts.length > 0">
<!-- jika data carts ada, maka tampilkan -->
<div class="col-md-7">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<h5>DETAIL PESANAN</h5>
<hr>
<table class="table table-cart">
<tbody>
<client-only>
<tr v-for="cart in carts" :key="cart.id"
style="background: #edf2f7;">
<td class="b-none" width="25%">
<div class="wrapper-image-cart">
<img :src="cart.product.image" style="width:
100%;border-radius: .5rem">
</div>
</td>
<td class="b-none" width="50%">
<h5><b>{{ cart.product.title }}</b></h5>
<table class="table-borderless table-font">
<tr>
<td style="padding: .20rem">QTY</td>
<td style="padding: .20rem">:</td>
<td style="padding: .20rem"><b>{{ cart.qty
}}</b></td>
</tr>
936
</table>
</td>
<td class="b-none text-right">
<p class="m-0 font-weight-bold">Rp. {{
formatPrice(cart.price) }}
</p>
<p class="m-0">
<s style="text-decoration-color:red">Rp.
{{ formatPrice(cart.product.price * cart.qty)
}}</s>
</p>
<br>
<div class="text-right">
<button @click.prevent="removeCart(cart.id)"
class="btn btn-sm btn-danger">
<i class="fa fa-trash"></i>
</button>
</div>
</td>
</tr>
</client-only>
</tbody>
</table>
937
<td class="set-td border-0 text-right"> :
Rp.</td>
<td class="set-td border-0 text-right">
<p class="m-0" id="ongkir-cart">
{{ formatPrice(courier.courier_cost) }}
</p>
</td>
</tr>
<tr>
<td class=" text-left border-0">
<p class="font-weight-bold m-0 h5 text-
uppercase">Grand Total </p>
</td>
<td class=" border-0 text-right"> : Rp.</td>
<td class=" border-0 text-right">
<p class="font-weight-bold m-0 h5" align="right">
{{ formatPrice(grandTotal) }}
</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-5">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<h5>DETAIL CUSTOMER</h5>
<hr>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="font-weight-bold">NAMA LENGKAP</label>
<input type="text" class="form-control"
id="nama_lengkap" placeholder="Nama Lengkap"
v-model="customer.name">
<div v-if="validation.name" class="mt-2 alert alert-
danger">
Masukkan Nama Lengkap
</div>
</div>
</div>
<div class="col-md-6">
938
<div class="form-group">
<label class="font-weight-bold">NO. HP /
WHATSAPP</label>
<input type="number" class="form-control" id="phone"
placeholder="No. HP / WhatsApp"
v-model="customer.phone">
<div v-if="validation.phone" class="mt-2 alert alert-
danger">
Masukkan No. Telp
</div>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label class="font-weight-bold">PROVINSI</label>
<select class="form-control" v-
model="rajaongkir.province_id" @change="getCities">
<option value="">-- pilih provinsi --</option>
<option v-for="province in provinces"
:key="province.id" :value="province.province_id">
{{ province.name }}</option>
</select>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label class="font-weight-bold">KOTA /
KABUPATEN</label>
<select class="form-control" v-
model="rajaongkir.city_id" @change="showCourier">
<option value="">-- pilih kota --</option>
<option v-for="city in cities" :key="city.id"
:value="city.city_id">{{ city.name }}</option>
</select>
</div>
</div>
<div class="col-md-12">
<div class="form-group" v-if="courier.showCourier">
<label class="font-weight-bold">KURIR
PENGIRIMAN</label>
<br>
<div class="form-check form-check-inline">
<input class="form-check-input select-courier"
939
type="radio" name="courier" id="ongkos_kirim-jne"
value="jne" v-model="courier.courier_name"
@change="showService">
<label class="form-check-label font-weight-bold
mr-4" for="ongkos_kirim-jne">
JNE</label>
<input class="form-check-input select-courier"
type="radio" name="courier" id="ongkos_kirim-tiki"
value="tiki" v-model="courier.courier_name"
@change="showService">
<label class="form-check-label font-weight-bold
mr-4" for="ongkos_kirim-jnt">TIKI</label>
<input class="form-check-input select-courier"
type="radio" name="courier" id="ongkos_kirim-pos"
value="pos" v-model="courier.courier_name"
@change="showService">
<label class="form-check-label font-weight-bold"
for="ongkos_kirim-jnt">POS</label>
</div>
</div>
</div>
<div class="col-md-12">
<div class="form-group" v-if="courier.showService">
<hr>
<label class="font-weight-bold">SERVICE KURIR</label>
<br>
<div v-for="value in costs" :key="value.service"
class="form-check form-check-inline">
<input class="form-check-input" type="radio"
name="cost" :id="value.service"
:value="value.cost[0].value+'|'+value.service" v-
model="courier.courier_service_cost"
@change="getServiceCost">
<label class="form-check-label font-weight-normal
mr-5" :for="value.service">
{{ value.service }} - Rp. {{
formatPrice(value.cost[0].value) }}</label>
</div>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label class="font-weight-bold">ALAMAT LENGKAP</label>
<textarea class="form-control" id="alamat" rows="3"
940
placeholder="Alamat Lengkap"
v-model="customer.address"></textarea>
<div v-if="validation.address" class="mt-2 alert
alert-danger">
Masukkan Alamat Lengkap
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row justify-content-center" v-else>
<!-- data carts tidak tersedia -->
<div class="col-md-10">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<div class="col-sm-12 empty-cart-cls text-center">
<img src="/images/shopping-cart.png" width="150"
height="150" class="img-fluid mb-4 mr-3">
<h3><strong>Keranjang Belanja Kosong :)</strong></h3>
<nuxt-link :to="{name: 'products'}" class="btn btn-warning
btn-lg mt-4" data-abc="true">LANJUTKAN BELANJA
</nuxt-link>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
//middleware
middleware: 'isCustomer',
941
//meta
head() {
return {
title: 'Cart - MI STORE - Distributor Xiaomi Indonesia Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/shopping-cart.png'
},
{
hid: 'description',
name: 'description',
content: 'Cart - Official Toko Online Penjualan Produk
Xiaomi'
},
]
}
},
//hook asyncData
async asyncData({ store }) {
},
//computed
computed: {
//cart data
942
carts() {
return this.$store.state.web.cart.carts
},
//cart weight
cartWeight() {
return this.$store.state.web.cart.cartWeight
},
//cart price
cartPrice() {
return this.$store.state.web.cart.cartPrice
},
//provinces
provinces() {
return this.$store.state.web.rajaongkir.provinces
},
//cities
cities() {
return this.$store.state.web.rajaongkir.cities
},
//costs
costs() {
return this.$store.state.web.rajaongkir.costs
}
},
//data function
data() {
return {
//state customer
customer: {
name: '',
phone: '',
address: ''
},
//state validation
validation: {
name: false,
phone: false,
address: false
},
943
//state rajaongkir
rajaongkir: {
province_id: '',
city_id: ''
},
//state courier
courier: {
showCourier: false,
showService: false,
courier_name: '',
courier_service_cost: '',
courier_service: '',
courier_cost: ''
},
//grandTotal
grandTotal: 0,
//method
methods: {
//method "removeCart"
async removeCart(cartId) {
await this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
}).then((result) => {
if (result.isConfirmed) {
944
.then(async () => {
//dispatch action "getCartPrice"
await this.$store.dispatch('web/cart/getCartPrice')
//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Product Berhasil Dihapus dari Keranjang!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
})
}
})
},
//method "getCities"
getCities() {
this.$store.dispatch('web/rajaongkir/getCitiesData', {
province_id: this.rajaongkir.province_id
})
},
//method "showCourier"
showCourier() {
this.courier.showCourier = true
},
//method "showService"
async showService() {
await this.$store.dispatch('web/rajaongkir/getOngkirData', {
destination: this.rajaongkir.city_id,
weight: this.cartWeight,
945
courier: this.courier.courier_name
})
.then(() => {
this.courier.showService = true
})
},
//method "getServiceCost"
getServiceCost() {
//sum grandTotal
this.grandTotal = parseInt(this.cartPrice) +
parseInt(this.courier.courier_cost)
}
</script>
<style scoped>
.table-cart {
border-style: solid !important;
border-color: rgb(198, 206, 214) !important;
}
.table-font {
font-size: 14px;
}
</style>
Dari penambahan kode di atas, mari kita bahas satu-persatu. Pertama kita menambahakn
action di dalam hook asyncData yang mana action tersebut akan dijalankan pertama kali
saat halaman dibuka.
946
//call action vuex "getProvincesData"
await store.dispatch('web/rajaongkir/getProvincesData')
Setelah itu, kita akan ambil data provinsi di dalam state Vuex menggunakan computed
properti. Kurang lebih seperti berikut ini :
//provinces
provinces() {
return this.$store.state.web.rajaongkir.provinces
},
Dan untuk menampilkan list data provinsi di dalam template, kita bisa menggunakan kode
seperti berikut ini :
<div class="form-group">
<label class="font-weight-bold">PROVINSI</label>
<select class="form-control" v-model="rajaongkir.province_id"
@change="getCities">
<option value="">-- pilih provinsi --</option>
<option v-for="province in provinces" :key="province.id"
:value="province.province_id">
{{ province.name }}
</option>
</select>
</div>
947
//method "getCities"
getCities() {
this.$store.dispatch('web/rajaongkir/getCitiesData', {
province_id: this.rajaongkir.province_id
})
},
Di dalam method getCities di atas, kita melakukan dispatch ke dalam action yang
bernama getCitiesData dengan mengirimkan parameter ID Province.
Setelah proses di dalam action berhasil dilakukan, maka kita akan mengambil list data
kota/kabupaten yang ada di dalam state Vuex menggunakan computed properti.
//cities
cities() {
return this.$store.state.web.rajaongkir.cities
},
Dan kita akan menampilkan data kota/kabupaten tersebut di dalam template menggunakan
perulangan v-for.
<div class="form-group">
<label class="font-weight-bold">KOTA / KABUPATEN</label>
<select class="form-control" v-model="rajaongkir.city_id"
@change="showCourier">
<option value="">-- pilih kota --</option>
<option v-for="city in cities" :key="city.id"
:value="city.city_id">{{ city.name }}</option>
</select>
</div>
Sama seperti sebelumnya, di dalam select option city kita menambahkan event @change
yang mengarah ke dalam method showCourier.
948
//method "showCourier"
showCourier() {
this.courier.showCourier = true
},
Di atas, bisa kita perhatikan, jika state courier.showCourier bernilai true, maka pilihan
kurir akan di tampilkan. Di atas, kita memiliki 3 pilihan kurir, yaitu JNE, TIKI dan POS.
v-if="courier.showCourier"
Di dalam input kurir di atas, kita menggunakan event @change yang akan di arahkan ke
dalam method yang bernama showService, method inilah yang akan digunakan untuk
menghitung biaya ongkos kirim.
949
//method "showService"
async showService() {
await this.$store.dispatch('web/rajaongkir/getOngkirData', {
destination: this.rajaongkir.city_id,
weight: this.cartWeight,
courier: this.courier.courier_name
})
.then(() => {
this.courier.showService = true
})
},
Di atas, di dalam method showService pertama kita lakukan pengecekan untuk berat dari
cart, jika berat sama dengan 0, artinya data cart masih kosong, maka akan menampilkan
sebuah alert/message silahkan pilih produk terlebih dahulu!.
Setelah itu, kita melakukan dispatch ke dalam action Vuex untuk proses perhitungan biaya
ongkos kirim. Dan di dalamnya kita berikan beberapa parameter.
await this.$store.dispatch('web/rajaongkir/getOngkirData', {
destination: this.rajaongkir.city_id,
weight: this.cartWeight,
courier: this.courier.courier_name
})
Jika dari proses action di atas berhasil dilakukan, maka kita akan ambil data biaya ongkos
kirim di state costs yang ada di Vuex dengan computed properti.
950
//costs
costs() {
return this.$store.state.web.rajaongkir.costs
}
Dan kita juga tampilkan hasil dari perhitungan biaya ongkos kirim tersebut di dalam
template.
this.courier.showService = true
Di atas, ketika kita memilih jasa service kurir, maka akan menjalankan event @change yang
mengarah ke dalam method yang bernama getServiceCost, di dalam method ini kita
akan melakukan penjumlahan untuk mendapatkan grand total dari pembelian, dimana
yang akan di hitung adalah total cart + biaya kurir.
951
//method "getServiceCost"
getServiceCost() {
//sum grandTotal
this.grandTotal = parseInt(this.cartPrice) +
parseInt(this.courier.courier_cost)
Saat kita memilih service kurir dan menjalankan method getServiceCost, maka akan
mengirim 2 data yaitu biaya ongkoso kirim dan nama service pengiriman.
value.cost[0].value+'|'+value.service
Di atas, 2 data tersebut di pisahkan menggunakan |. Oleh sebab itu di dalam method
getServiceCost kita melakukan split dengan | yang bertujuan untuk memisahkan data
tersebut.
Setelah itu, kita masukkan 2 data tersebut ke dalam variable yang berbeda.
952
Kemudian melakukan penjumlahan grand total, yang diambil dari total price cart dan biaya
ongkos kirim.
//sum grandTotal
this.grandTotal = parseInt(this.cartPrice) +
parseInt(this.courier.courier_cost)
953
Sekarang, silahkan pilih data provinsi di dalam select option, maka kita akan menampilkan
data kota berdasarkan provinsi tersebut.
Kemudian, ketika kita memilih data kota, maka pilihan kurir akan di tampilkan, sepeti
berikut ini :
954
Sekarang, jika kita klik salah satu kurir, maka akan menampilkan service dan biaya ongkos
kirim.
955
Dan jika kita pilih service dan biaya ongkos kirim, maka akan mengupdate nilai grand
total dan menampilkan button checkout.
956
957
Menampilkan Categories di Header
Sekarang, kita akan belajar bagaimana cara menampilkan data categories di Header atau
Navbar website yang sedang kita buat.
<template>
<header class="section-header fixed-top">
<section class="header-main border-bottom">
<div class="container-fluid">
<div class="row align-items-center">
<div class="col-lg-3 col-sm-4 col-md-4 col-5">
<nuxt-link to="/" class="brand-wrap" data-abc="true">
<img src="/images/xiaomi.png" width="35" class="bg-light
p-2 rounded">
<span class="logo">MI STORE</span>
</nuxt-link>
</div>
<div class="col-lg-4 col-xl-5 col-sm-8 col-md-4 d-none d-md-
block">
<div class="search-wrap">
<div class="input-group w-100">
<input type="text" class="form-control search-form"
style="width:55%;" placeholder="mau belanja apa hari ini ?">
<div class="input-group-append">
<button class="btn btn-primary search-button"> <i
class="fa fa-search"></i> </button>
</div>
</div>
</div>
</div>
<div class="col-lg-5 col-xl-4 col-sm-8 col-md-4 col-7">
<div class="d-flex justify-content-end">
<a href="#" class="btn search-button btn-md d-md-block
ml-4"><i class="fa fa-shopping-cart"></i> <span class="ml-2">0</span> |
Rp. 0</a>
</div>
</div>
958
</div>
</div>
</section>
<nav class="navbar navbar-expand-md navbar-main border-bottom p-2">
<div class="container-fluid">
<div class="d-md-none my-2">
<div class="input-group">
<input type="search" name="search" class="form-control"
placeholder="mau belanja apa hari ini ?">
<div class="input-group-append">
<button class="btn btn-warning"> <i class="fa fa-
search"></i></button>
</div>
</div>
</div>
<button class="navbar-toggler collapsed" type="button" data-
toggle="collapse" data-target="#dropdown6"
aria-expanded="false"> <span class="navbar-toggler-
icon"></span> </button>
<div class="navbar-collapse collapse" id="dropdown6">
<ul class="navbar-nav mr-auto">
<li class="nav-item dropdown"> <a class="nav-link dropdown-
toggle" href="#" data-toggle="dropdown"
data-abc="true" aria-expanded="false"><i class="fa fa-
list-ul"></i> KATEGORI</a>
<div class="dropdown-menu">
<nuxt-link :to="{name: 'categories-slug', params: {slug:
category.slug}}" class="dropdown-item" v-for="category in categories"
:key="category.id">
<img :src="category.image" width="50"> {{
category.name }}
</nuxt-link>
<div class="dropdown-divider"></div>
<nuxt-link :to="{name: 'categories'}" class="dropdown-
item active text-center" href="" data-abc="true">
LIHAT SEMUA KATEGORI <i class="fa fa-long-arrow-alt-
right"></i>
</nuxt-link>
</div>
</li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-shopping-bag"></i> SEMUA PRODUK</a> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-info-circle"></i> TENTANG</a> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-comments"></i> KONTAK</a> </li>
959
</ul>
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown" v-if="!$auth.loggedIn">
<nuxt-link :to="{name: 'customer-login'}" class="nav-link"
href="#" role="button" aria-expanded="false"> <i class="fa fa-user-
circle"></i>
ACCOUNT</nuxt-link>
</li>
<li class="nav-item dropdown" v-if="$auth.loggedIn">
<nuxt-link :to="{name: 'customer-dashboard'}" class="nav-
link" href="#" role="button" aria-expanded="false"> <i class="fa fa-
tachometer-alt"></i>
DASHBOARD</nuxt-link>
</li>
</ul>
</div>
</div>
</nav>
</header>
</template>
<script>
export default {
//hook "fetch"
async fetch() {
//computed
computed: {
//categories
categories() {
return this.$store.state.web.category.categories
},
}
}
</script>
<style scoped>
.btn {
font-size: initial;
}
960
</style>
Dari perubahan kode di atas, pertama kita memanggil hook yang bernama fetch,
fungsinya untuk memanggil atau melakukan dispatch ke dalam action Vuex secara SSR
atau server side rendering.
//hook "fetch"
async fetch() {
Setelah itu, kita menggunakan computed properti untuk mengambil data dari state yang
bernama categories di dalam Vuex.
//computed
computed: {
//categories
categories() {
return this.$store.state.web.category.categories
},
}
Dan untuk menampilkan data-nya, kita menggunakan directive v-for, karena data yang
ditampilkan lebih dari 1.
//...
</nuxt-link>
961
Langkah 2 - Melihat Hasil Categories di Header
Silahkan reload / refresh halaman website-nya, jika berhasil maka kita akan melihat
tampilan menu dropdown berisi data categories, kurang lebih seperti berikut ini :
962
Membuat Proses Checkout
Setelah berhasil melakukan perhitungan biaya ongkos kirim di dalam halaman cart, maka
sekarang kita akan lanjutkan untuk membuat proses checkout. Dimana di dalam proses
checkout tersebut kita akan melakukan request ke dalam server Midtrans untuk
mengenerate sebuah snap token. Dan sebelum itu, kita juga akan membuat module di Vuex
dulu untuk menghandle proses checkout di Nuxt.js.
963
//actions
export const actions = {
//store checkout
storeCheckout({ dispatch, commit }, payload) {
//set promise
return new Promise((resolve, reject) => {
//success
.then(response => {
//resolve promise
resolve(response.data.data)
//dispatch cart
dispatch('web/cart/getCartsData', null, { root: true })
})
//error
.catch(error => {
reject(error)
})
})
},
Dari penambahan kode di atas, kita hanya menambahkan 1 action baru yang bernama
storeCheckout, action tersebut yang nanti digunakan mengirimkan data ke server untuk
proses checkout.
Di dalam action tersebut kita melakukan sending data menggunakan Axios dengan method
POST ke dalam endpoint /api/web/checkout. Dan kita kirimkan parameter payload
yang mana isinya adalah data-data seperti nama, phone, alamat provinsi dan lain-lain.
964
//store to Rest API "/api/web/checkout" with method "POST"
this.$axios.post('/api/web/checkout', payload)
Jika proses di atas berhasil, maka kita akan lakukan resolve dengan parameter response
data. Karena kita akan menggunakan response data tersebut di dalam component / view.
//resolve promise
resolve(response.data.data)
Dan terakhir kita melakukan dispatch ke dalam action yang bernama getCartsData,
action ini berada di luar module checkout, oleh sebab itu kita harus menambahkan root:
true. Dengan melakukan dispatch ke dalam action tersebut, maka data cart yang ada akan
diperbarui atau dihapus.
//dispatch cart
dispatch('web/cart/getCartsData', null, { root: true })
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row" v-if="carts.length > 0">
<!-- jika data carts ada, maka tampilkan -->
<div class="col-md-7">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<h5>DETAIL PESANAN</h5>
<hr>
<table class="table table-cart">
<tbody>
<client-only>
<tr v-for="cart in carts" :key="cart.id"
style="background: #edf2f7;">
<td class="b-none" width="25%">
965
<div class="wrapper-image-cart">
<img :src="cart.product.image" style="width:
100%;border-radius: .5rem">
</div>
</td>
<td class="b-none" width="50%">
<h5><b>{{ cart.product.title }}</b></h5>
<table class="table-borderless table-font">
<tr>
<td style="padding: .20rem">QTY</td>
<td style="padding: .20rem">:</td>
<td style="padding: .20rem"><b>{{ cart.qty
}}</b></td>
</tr>
</table>
</td>
<td class="b-none text-right">
<p class="m-0 font-weight-bold">Rp. {{
formatPrice(cart.price) }}
</p>
<p class="m-0">
<s style="text-decoration-color:red">Rp.
{{ formatPrice(cart.product.price * cart.qty)
}}</s>
</p>
<br>
<div class="text-right">
<button @click.prevent="removeCart(cart.id)"
class="btn btn-sm btn-danger">
<i class="fa fa-trash"></i>
</button>
</div>
</td>
</tr>
</client-only>
</tbody>
</table>
966
</td>
<td class="set-td text-right" width="30%"> :
Rp.</td>
<td class="text-right set-td ">
<p class="m-0" id="subtotal"> {{
formatPrice(cartPrice) }}
</p>
</td>
</tr>
<tr>
<td class="set-td text-left border-0">
<p class="m-0">ONGKOS KIRIM (<strong>{{ cartWeight
}}</strong> gram)</p>
</td>
<td class="set-td border-0 text-right"> :
Rp.</td>
<td class="set-td border-0 text-right">
<p class="m-0" id="ongkir-cart">
{{ formatPrice(courier.courier_cost) }}
</p>
</td>
</tr>
<tr>
<td class=" text-left border-0">
<p class="font-weight-bold m-0 h5 text-
uppercase">Grand Total </p>
</td>
<td class=" border-0 text-right"> : Rp.</td>
<td class=" border-0 text-right">
<p class="font-weight-bold m-0 h5" align="right">
{{ formatPrice(grandTotal) }}
</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-5">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<h5>DETAIL CUSTOMER</h5>
<hr>
<div class="row">
967
<div class="col-md-6">
<div class="form-group">
<label class="font-weight-bold">NAMA LENGKAP</label>
<input type="text" class="form-control"
id="nama_lengkap" placeholder="Nama Lengkap"
v-model="customer.name">
<div v-if="validation.name" class="mt-2 alert alert-
danger">
Masukkan Nama Lengkap
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="font-weight-bold">NO. HP /
WHATSAPP</label>
<input type="number" class="form-control" id="phone"
placeholder="No. HP / WhatsApp"
v-model="customer.phone">
<div v-if="validation.phone" class="mt-2 alert alert-
danger">
Masukkan No. Telp
</div>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label class="font-weight-bold">PROVINSI</label>
<select class="form-control" v-
model="rajaongkir.province_id" @change="getCities">
<option value="">-- pilih provinsi --</option>
<option v-for="province in provinces"
:key="province.id" :value="province.province_id">
{{ province.name }}</option>
</select>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label class="font-weight-bold">KOTA /
KABUPATEN</label>
<select class="form-control" v-
model="rajaongkir.city_id" @change="showCourier">
968
<option value="">-- pilih kota --</option>
<option v-for="city in cities" :key="city.id"
:value="city.city_id">{{ city.name }}</option>
</select>
</div>
</div>
<div class="col-md-12">
<div class="form-group" v-if="courier.showCourier">
<label class="font-weight-bold">KURIR
PENGIRIMAN</label>
<br>
<div class="form-check form-check-inline">
<input class="form-check-input select-courier"
type="radio" name="courier" id="ongkos_kirim-jne"
value="jne" v-model="courier.courier_name"
@change="showService">
<label class="form-check-label font-weight-bold
mr-4" for="ongkos_kirim-jne">
JNE</label>
<input class="form-check-input select-courier"
type="radio" name="courier" id="ongkos_kirim-tiki"
value="tiki" v-model="courier.courier_name"
@change="showService">
<label class="form-check-label font-weight-bold
mr-4" for="ongkos_kirim-jnt">TIKI</label>
<input class="form-check-input select-courier"
type="radio" name="courier" id="ongkos_kirim-pos"
value="pos" v-model="courier.courier_name"
@change="showService">
<label class="form-check-label font-weight-bold"
for="ongkos_kirim-jnt">POS</label>
</div>
</div>
</div>
<div class="col-md-12">
<div class="form-group" v-if="courier.showService">
<hr>
<label class="font-weight-bold">SERVICE KURIR</label>
<br>
<div v-for="value in costs" :key="value.service"
class="form-check form-check-inline">
<input class="form-check-input" type="radio"
name="cost" :id="value.service"
:value="value.cost[0].value+'|'+value.service" v-
969
model="courier.courier_service_cost"
@change="getServiceCost">
<label class="form-check-label font-weight-normal
mr-5" :for="value.service">
{{ value.service }} - Rp. {{
formatPrice(value.cost[0].value) }}</label>
</div>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label class="font-weight-bold">ALAMAT LENGKAP</label>
<textarea class="form-control" id="alamat" rows="3"
placeholder="Alamat Lengkap"
v-model="customer.address"></textarea>
<div v-if="validation.address" class="mt-2 alert
alert-danger">
Masukkan Alamat Lengkap
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row justify-content-center" v-else>
<!-- data carts tidak tersedia -->
<div class="col-md-10">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<div class="col-sm-12 empty-cart-cls text-center">
<img src="/images/shopping-cart.png" width="150"
height="150" class="img-fluid mb-4 mr-3">
<h3><strong>Keranjang Belanja Kosong :)</strong></h3>
<nuxt-link :to="{name: 'products'}" class="btn btn-warning
btn-lg mt-4" data-abc="true">LANJUTKAN BELANJA
</nuxt-link>
</div>
970
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
//middleware
middleware: 'isCustomer',
//meta
head() {
return {
title: 'Cart - MI STORE - Distributor Xiaomi Indonesia Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/shopping-cart.png'
},
{
hid: 'description',
name: 'description',
content: 'Cart - Official Toko Online Penjualan Produk
Xiaomi'
},
]
}
},
//hook asyncData
971
async asyncData({ store }) {
},
//computed
computed: {
//cart data
carts() {
return this.$store.state.web.cart.carts
},
//cart weight
cartWeight() {
return this.$store.state.web.cart.cartWeight
},
//cart price
cartPrice() {
return this.$store.state.web.cart.cartPrice
},
//provinces
provinces() {
return this.$store.state.web.rajaongkir.provinces
},
//cities
cities() {
return this.$store.state.web.rajaongkir.cities
},
//costs
costs() {
return this.$store.state.web.rajaongkir.costs
}
},
//data function
data() {
972
return {
//state customer
customer: {
name: '',
phone: '',
address: ''
},
//state validation
validation: {
name: false,
phone: false,
address: false
},
//state rajaongkir
rajaongkir: {
province_id: '',
city_id: ''
},
//state courier
courier: {
showCourier: false,
showService: false,
courier_name: '',
courier_service_cost: '',
courier_service: '',
courier_cost: ''
},
//grandTotal
grandTotal: 0,
//method
methods: {
//method "removeCart"
async removeCart(cartId) {
await this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
973
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
}).then((result) => {
if (result.isConfirmed) {
.then(async () => {
//dispatch action "getCartPrice"
await this.$store.dispatch('web/cart/getCartPrice')
//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Product Berhasil Dihapus dari Keranjang!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
})
}
})
},
//method "getCities"
getCities() {
this.$store.dispatch('web/rajaongkir/getCitiesData', {
province_id: this.rajaongkir.province_id
})
},
//method "showCourier"
showCourier() {
974
this.courier.showCourier = true
},
//method "showService"
async showService() {
await this.$store.dispatch('web/rajaongkir/getOngkirData', {
destination: this.rajaongkir.city_id,
weight: this.cartWeight,
courier: this.courier.courier_name
})
.then(() => {
this.courier.showService = true
})
},
//method "getServiceCost"
getServiceCost() {
//sum grandTotal
this.grandTotal = parseInt(this.cartPrice) +
parseInt(this.courier.courier_cost)
//method "checkout"
async checkout() {
975
this.customer.address && this.cartWeight) {
//define formData
let formData = new FormData();
formData.append('courier', this.courier.courier_name)
formData.append('courier_service',
this.courier.courier_service)
formData.append('courier_cost', this.courier.courier_cost)
formData.append('weight', this.cartWeight)
formData.append('name', this.customer.name)
formData.append('phone', this.customer.phone)
formData.append('address', this.customer.address)
formData.append('city_id', this.rajaongkir.city_id)
formData.append('province_id', this.rajaongkir.province_id)
formData.append('grand_total', this.grandTotal)
//success
.then(response => {
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Checkout Berhasil Dilakukan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
})
976
if (!this.customer.name) {
this.validation.name = true
}
}
</script>
<style scoped>
.table-cart {
border-style: solid !important;
border-color: rgb(198, 206, 214) !important;
}
.table-font {
font-size: 14px;
}
</style>
DI atas, pertama kita menambahkan sebuah event @click di dalam button checkout yang
kita arahkan ke dalam method yang bernama checkout.
Dan di dalam method checkout pertama-tama kita membuat validasi terlebih dahulu,
apakah state customer.name, customer.phone, customer.address dan cartWeight
memiliki value.
977
//ceck apakah ada nama, phone, address dan berat produk ?
if (this.customer.name && this.customer.phone && this.customer.address
&& this.cartWeight) {
//proses checkout
//validasi
Jika semua state tersebut sudah terpenuhi, artinya semua kolom sudah memiliki value dan
disini kita akan membuat inisialisasi sebuah FormData, yang nanti akan digunakan untuk
menampung data yang didapatkan dari input form.
//define formData
let formData = new FormData();
Setelah berhasil melakukan inisialisasi, sekarang kita append beberapa data yang
didapatkan dari input form ke dalam formData.
formData.append('courier', this.courier.courier_name)
formData.append('courier_service', this.courier.courier_service)
formData.append('courier_cost', this.courier.courier_cost)
formData.append('weight', this.cartWeight)
formData.append('name', this.customer.name)
formData.append('phone', this.customer.phone)
formData.append('address', this.customer.address)
formData.append('city_id', this.rajaongkir.city_id)
formData.append('province_id', this.rajaongkir.province_id)
formData.append('grand_total', this.grandTotal)
Jika sudah, maka kita akan melakukan dispatch ke dalam action yang bernama
storeCheckout dengan mengirimkan parameter formData.
978
Jika proses checkout berhasil dilakukan di dalam action Vuex, maka kita akan menampilkan
pesan sukses menggunakan Sweet Alert2.
//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Checkout Berhasil Dilakukan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})
Setelah itu, kita arahkan ke dalam halaman yang memiliki nama route customer-
invoices-show-snap_token dan kita sertakan parameter snap_token yang didapatkan
dari hasil response checkout di action Vuex.
979
//check validasi name
if (!this.customer.name) {
this.validation.name = true
}
980
Sekarang, silahkan isi nama, phone dan alamat dan coba lagi untuk klik button CHECKOUT,
jika berhasil kita akan di arahkan ke halaman detail order, seperti berikut ini :
981
982
983
Melakukan Pembayaran Dengan Midtrans
Setelah berhasil melakukan proses checkout, maka sekarang kita akan belajar bagaimana
cara untuk melakukan pembayaran di dalam Midtrans.
Silaahkan masuk ke dalam detail invoice order dan klik button BAYAR SEKARANG. Jika
berhasil maka kita akan mendapatkan taampilan SNAP PAY dari Midrans.
Setelah itu, silahkan klik CONTINUE, maka kita akan mendapatkan banyak pilihan metode
pembayaran, seperti :
BANK Transfer
Credit Card
Gopay
ShopeePay
BCA KlikPay
Indomaret
Alfamart
Dan lain-lain.
Sekarang kita lanjutkan, disini kita akan mencoba melakukan simulasi pembayaran
menggunakan BCA Virtual Account. Silahkan klik ATM/Bank Transfer, maka akan
mendapatkan tampilan seperti berikut ini :
984
Klik BCA, dan setelah itu, klik SEE ACCOUNT NUMBER, maka kita akan berhasil
mendapatkan nomor Virtual Account dari Bank BCA dan klik PLEASE COMPLETE
PAYMENT.
Dan sekarang kita akan lakukan pembayaran menggunakan simulator dari Midtrans.
Silahkan buka link berikut ini https://simulator.sandbox.midtrans.com/bca/va/index, kurang
lebih tampilannya seperti ini :
985
Silahkan masukkan nomor Virtual Account yang didapatkan dari SNAP PAY ke dalam
input simulator di atas dan klik Inquire.
986
Sekarang, silahkan klik button BAYAR SEKARANG yang ada di dalam detail invoice order
lagi, jika berhasil maka kita akan mendapatkan hasil seperti berikut ini :
INFORMASI PENTING : untuk status invoice akan tetap pending, karena masih di dalam
localhost, ketika website sudah di online-kan dan kita melakukan konfigurasi notifikasi
handler Midtrans, maka status invoice akan otomatis berubah menjadi success.
987
Membuat Vuex Web Slider
Pada materi ini kita akan belajar membuat state mutation dan action untuk data slider
di Vuex. Dengan menggunakan Vuex maka data akan bersifat global dan bisa digunakan
disemua component yang ada, dan tentu saja proses maintenance lebih mudah dilakukan.
//state
export const state = () => ({
//sliders
sliders: []
})
//mutations
export const mutations = {
//mutation "SET_SLIDERS_DATA"
SET_SLIDERS_DATA(state, payload) {
//actions
export const actions = {
//set promise
return new Promise((resolve, reject) => {
988
//success
.then((response) => {
//resolve promise
resolve()
})
})
},
Dari penambahan kode di atas, pertama kita membuat state baru dengan nama sliders,
state tersebut yang nanti akan digunakan untuk menyimpan data response dari Rest API
untuk di tampilkan di dalam component / view.
//sliders
sliders: []
//mutation "SET_SLIDERS_DATA"
SET_SLIDERS_DATA(state, payload) {
Dan di dalam action, kita juga menambahkan 1 method baru dengan nama
getSlidersData.
989
//get sliders data
getSlidersData({ commit }) {
//...
}
Di dalam method tersebut, kita melakukan http request menggunakan Axios dengan
method GET ke dalam endpoint /api/web/sliders.
Jika proses http request berhasil dilakukan, maka kita akan lakukan commit ke dalam
mutation yang bernama SET_SLIDERS_DATA dengan parameter data response yang di
dapatkan dari Rest API.
990
Membuat dan Menampilkan Component Slider
Setelah berhasil membuat store di dalam Vuex, maka sekarang kita lanjutkan membuat
component slider dan menampilkan component tersebut di halaman homepage website.
Silahkan buat file baru dengan nama slider.vue di dalam folder components/web,
kemudian masukkan kode berikut ini di dalamnya.
<template>
<div class="container-fluid mt-custom">
<div id="mycarousel" class="carousel slide" data-ride="carousel"
data-interval="4000">
<div class="carousel-inner">
</div>
<script>
export default {
991
//hook "fetch"
async fetch() {
},
//computed
computed: {
//sliders
sliders() {
return this.$store.state.web.slider.sliders
}
}
}
</script>
<style>
</style>
Di atas, kita memanggil atau melakukan dispatch ke dalam action yang bernama
getSlidersData di dalam hook fetch, tujuannya agar proses tersebut dilakukan secara
SSR atau server side rendering.
//hook "fetch"
async fetch() {
},
Jika proses di dalam action berhasil dilakukan, maka kita akan mengambil datanya di dalam
state sliders yang ada di dalam Vuex. Dan disini kita akan menggunakan computed
properti.
992
//computed
computed: {
//sliders
sliders() {
return this.$store.state.web.slider.sliders
}
}
Dan untuk menampilkan data sliders di dalam template, kita akan menggunakn directive v-
for karena datanya bisa jadi lebih dari 1 atau banyak.
//...
</div>
Silahkan buka file pages/index.vue kemudian ubah kode-nya menjadi seperti berikut ini :
<template>
<div class="mr-custom mb-5">
<div class="fade-in">
</div>
</div>
</template>
<script>
//import slider
import Slider from '@/components/web/slider.vue'
993
export default {
//register components
components: {
Slider
},
//meta
head() {
return {
title: 'MI STORE - Distributor Xiaomi Indonesia Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/logo.png'
},
{
hid: 'description',
name: 'description',
content: 'Official Toko Online Penjualan Produk Xiaomi'
},
]
}
},
}
</script>
<style>
</style>
Dari perubahan kode di atas, pertama kita melakukan import component slider.
994
//import slider
import Slider from '@/components/web/slider.vue'
Setelah itu, kita register component tersebut agar bisa digunakan di dalam template.
//register components
components: {
Slider
},
Dan kita panggil component tersebut di dalam template, kurang lebih seperti berikut ini :
Dan kita juga atur untuk meta tag website, seperti title, og:site, og:name, og:image
dan description. Meta tersebut berguna untuk menunjang SEO pada website yang kita
buat agar mudah dicari di mesin pencarian google.
995
//meta
head() {
return {
title: 'MI STORE - Distributor Xiaomi Indonesia Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: 'images/logo.png'
},
{
hid: 'description',
name: 'description',
content: 'Official Toko Online Penjualan Produk Xiaomi'
},
]
}
},
Sekarang, silahkan reload / refresh halaman homepage website atau ke URL berikut ini
http://localhost:3000/, jika berhasil maka kita akan mendapatkan hasil seperti berikut ini :
996
997
Membuat Vuex Web Product
Sekarang kita akan belajar membuat module product Vuex yang nantinya akan digunakan
untuk menampilkan data di dalam component / view. Disini kita akan menambahkan state
untuk menampung data yang di dapatkan dari Rest API, mutation untuk mengubah nilai
state dan action untuk melakukan http request ke Rest API.
//state
export const state = () => ({
//products
products: [],
//page
page: 1,
})
//mutations
export const mutations = {
//mutation "SET_PRODUCTS_DATA"
SET_PRODUCTS_DATA(state, payload) {
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
998
//actions
export const actions = {
//search
let search = payload ? payload : ''
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
Dari penambahan kode di atas, pertama kita membuat 2 state baru, yaitu products dan
page.
//products
products: [],
//page
page: 1,
SET_PRODUCTS_DATA
SET_PAGE
999
SET_PRODUCTS_DATA
Mutation ini akan digunakan untuk mengubah isi dari state products dengan data
response yang dikirimakan oleh action.
//mutation "SET_PRODUCTS_DATA"
SET_PRODUCTS_DATA(state, payload) {
SET_PAGE
Mutation ini akan digunakan untuk mengubah isi dari state page dengan data berupa nomor
pagination yang nanti akan dikirimkan oleh component / view.
//mutation "SET_PAGE"
SET_PAGE(state, payload) {
Di dalam action kita juga menambahkan 1 method baru dengan nama getProductsData.
//...
}
Di dalam action tersebut, pertama kita membuat sebuah variable dengan nama search
dengan value payload yang nanti isinya akan didapatkan dari component / view.
1000
//search
let search = payload ? payload : ''
Setelah itu, kita melakukan http request menggunakan Axios dengan method GET ke
dalam endpoint /api/web/products?q=""&page="". Untuk parameter q akan diisi
variable search dan parameter page diambil dari state yang bernama page.
Jika proses fetching di atas berhasil, maka kita akan melakukan commit ke dalam mutation
yang bernama SET_PRODUCTS_DATA dengan parameter berupa data response yang di
dapatkan dari Rest API.
1001
Menampilkan Products di Homepage
Setelah berhasil menambahkan module Vuex, sekarang kita akan coba menampilkan data
product di halaman homepage dari website toko online yang sedang kita buat.
<template>
<div class="mr-custom mb-5">
<div class="fade-in">
1002
product.title }}</nuxt-link>
</h6>
<nuxt-link :to="{name: 'categories-slug', params: {slug:
product.category.slug}}" class="text-muted" data-abc="true">{{
product.category.name }}</nuxt-link>
</div>
<h6 class="mb-0 font-weight-semibold"><s class="text-
red">Rp. {{ formatPrice(product.price) }}</s> / <strong>{{
product.discount }} %</strong></h6>
<h5 class="mb-0 font-weight-semibold mt-3 text-
success">Rp. {{ formatPrice(calculateDiscount(product)) }}</h5>
<hr>
<client-only>
<vue-star-rating
:rating="parseFloat(product.reviews_avg_rating)" :increment="0.5" :star-
size="20" :read-only="true" :show-rating="false" :inline="true"></vue-
star-rating>
(<strong>{{ product.reviews_count }}</strong> ulasan)
</client-only>
</div>
</div>
</div>
</div>
</div>
<!-- end product -->
</div>
</div>
</template>
<script>
//import slider
import Slider from '@/components/web/slider.vue'
export default {
//register components
1003
components: {
Slider
},
//meta
head() {
return {
title: 'MI STORE - Distributor Xiaomi Indonesia Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: 'images/logo.png'
},
{
hid: 'description',
name: 'description',
content: 'Official Toko Online Penjualan Produk Xiaomi'
},
]
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('web/product/getProductsData')
},
//computed
computed: {
//products
products() {
return this.$store.state.web.product.products
},
},
1004
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita melakukan dispatch ke dalam action Vuex
yang bernama getProductsData, yang mana proses-nya ada di dalam hook asyncData,
tujuannya agar berjalan secara SSR atau server side rendering.
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('web/product/getProductsData')
},
Setelah itu, kita akan ambil data products yang didapatkan dari action di dalam state yang
ada di Vuex. Dan disini kita akan menggunakan computed properti untuk mengambilnya.
//computed
computed: {
//products
products() {
return this.$store.state.web.product.products
},
},
Dan sekarang, kita akan menampilkan data products tersebut di dalam template
menggunakan directive v-for, karena data yang ditampilkan bisa lebih dari 1 atau banyak.
//...
</div>
1005
Di dalam block perulangan data product, kita menampilkan beberapa data, seperti harga,
harga yang telah di diskon, rating dan lain-lain.
Rp. {{ formatPrice(product.price) }}
Kode di atas, digunakan untuk menampilkan harga asli dari productnya. Dan berikut ini
digunakan untuk menampilkan harga yang telah dipotong diskon. Disini kita letakkan di
dalam method yang bernama formatPrice dan calculateDiscount, dimana kedua
method tersebut adalah helper yang sudah kita buat sebelumnya di dalam plugin mixins.
Rp. {{ formatPrice(calculateDiscount(product)) }}
Kemudian, untuk menampilkan rating, kita menggunakan kode seperti berikut ini :
<client-only>
<vue-star-rating :rating="parseFloat(product.reviews_avg_rating)"
:increment="0.5" :star-size="20" :read-only="true" :show-rating="false"
:inline="true"></vue-star-rating>
(<strong>{{ product.reviews_count }}</strong> ulasan)
</client-only>
Di atas, ada properti :rating dimana properti tersebut yang akan digunakan untuk
menampilkan rating di dalm website, dan disini kita set value-nya menggunakan data
average rating yaitu product.reviews_avg_rating dan kita jadikan type datanya
float dengan method parseFloat.
Dan kita juga menampilkan jumlah rating yang diberikaan oleh product tersebut,
menggunakan kode seperti berkut ini :
1006
1007
Menampilkan Index Data Products
Setelah berhasil menampilkan data products di halaman homepage, sekarang kita lanjutkan
untuk menampilkannya di halaman index products sendiri. Dan disini kita tidak perlu
melakukan konfigurasi Vuex lagi, karena akan menggunakan Vuex yang sudah kita buat
sebelumnya.
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3 mt-1 mb-4" v-for="product in
products.data" :key="product.id">
<div class="card h-100 border-0 rounded shadow-sm">
<div class="card-body">
<div class="card-img-actions">
<img :src="product.image" class="card-img img-fluid">
</div>
</div>
<div class="card-body bg-light-custom text-center rounded-
bottom">
<div class="mb-2">
<h6 class="font-weight-semibold mb-2">
<nuxt-link :to="{name: 'products-slug', params: {slug:
product.slug}}" class="text-default mb-2" data-abc="true">{{
product.title }}</nuxt-link>
</h6>
<nuxt-link :to="{name: 'categories-slug', params: {slug:
product.category.slug}}" class="text-muted" data-abc="true">{{
product.category.name }}</nuxt-link>
</div>
<h6 class="mb-0 font-weight-semibold"><s class="text-
red">Rp. {{ formatPrice(product.price) }}</s> / <strong>{{
product.discount }} %</strong></h6>
<h5 class="mb-0 font-weight-semibold mt-3 text-
success">Rp. {{ formatPrice(calculateDiscount(product)) }}</h5>
1008
<hr>
<client-only>
<vue-star-rating
:rating="parseFloat(product.reviews_avg_rating)" :increment="0.5" :star-
size="20" :read-only="true" :show-rating="false" :inline="true"></vue-
star-rating>
(<strong>{{ product.reviews_count }}</strong> ulasan)
</client-only>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
//meta
head() {
return {
title: 'Products - MI STORE - Distributor Xiaomi Indonesia
Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Products - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/logo.png'
},
{
hid: 'description',
name: 'description',
content: 'Jual Produk Original Xiaomi Indonesia Bergaransi
Resmi'
1009
},
]
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('web/product/getProductsData')
},
//computed
computed: {
//products
products() {
return this.$store.state.web.product.products
},
},
}
</script>
<style>
</style>
Di atas, pertama kita menambahkan meta tag untuk website kita, yaitu title, og:image
dan lain-lain. Tujuannya agar website kita dapat di cari di google dengan lebih mudah.
1010
//meta
head() {
return {
title: 'Products - MI STORE - Distributor Xiaomi Indonesia
Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Products - MI STORE - Distributor Xiaomi
Indonesia Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/logo.png'
},
{
hid: 'description',
name: 'description',
content: 'Jual Produk Original Xiaomi Indonesia
Bergaransi Resmi'
},
]
}
},
Dan di dalam hook asyncData, kita melakukan dispatch ke dalam action yang ada di Vuex
dengan nama getProductsData. Dengan menggunakan asyncData, maka proses diaptch
akan berjalan secara SSR.
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('web/product/getProductsData')
},
Setelah itu, kita ambil data yang ada di dalam state products di Vuex menggunakan
computed properti, kurang lebih seperti berikut ini :
1011
//computed
computed: {
//products
products() {
return this.$store.state.web.product.products
},
},
Dan untuk menampilkan data product di template, tentu saja kita akan menggunakan
directive v-for, karena data yang ditampilkan lebih dari 1 atau banyak.
//...
</div>
Di atas, kita ubah yang semula masih menggunakan href="#" menjadi <nuxt-link dan
kita arahkan ke dalam route yang bernama products.
1012
Sekarang, silahkan klik menu di navbar SEMUA PRODUK atau bisa ke URL berikut ini
http://localhost:3000/products, dan jika berhasil maka kita akan mendapatkan hasil seperti
berikut ini :
1013
Silahkan buka file pages/products/index.vue, kemudian ubah semua kode-nya menjadi
seperti berikut ini :
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3 mt-1 mb-4" v-for="product in
products.data" :key="product.id">
<div class="card h-100 border-0 rounded shadow-sm">
<div class="card-body">
<div class="card-img-actions">
<img :src="product.image" class="card-img img-fluid">
</div>
</div>
<div class="card-body bg-light-custom text-center rounded-
bottom">
<div class="mb-2">
<h6 class="font-weight-semibold mb-2">
<nuxt-link :to="{name: 'products-slug', params: {slug:
product.slug}}" class="text-default mb-2" data-abc="true">{{
product.title }}</nuxt-link>
</h6>
<nuxt-link :to="{name: 'categories-slug', params: {slug:
product.category.slug}}" class="text-muted" data-abc="true">{{
product.category.name }}</nuxt-link>
</div>
<h6 class="mb-0 font-weight-semibold"><s class="text-
red">Rp. {{ formatPrice(product.price) }}</s> / <strong>{{
product.discount }} %</strong></h6>
<h5 class="mb-0 font-weight-semibold mt-3 text-
success">Rp. {{ formatPrice(calculateDiscount(product)) }}</h5>
<hr>
<client-only>
<vue-star-rating
:rating="parseFloat(product.reviews_avg_rating)" :increment="0.5" :star-
size="20" :read-only="true" :show-rating="false" :inline="true"></vue-
star-rating>
(<strong>{{ product.reviews_count }}</strong> ulasan)
</client-only>
</div>
</div>
</div>
</div>
1014
<!-- pagination -->
<div class="row justify-content-center mt-4 mb-4">
<div class="text-center">
<b-pagination align="center"
:value="products.current_page" :total-rows="products.total"
:per-page="products.per_page" @change="changePage"
aria-controls="my-table"></b-pagination>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
//meta
head() {
return {
title: 'Products - MI STORE - Distributor Xiaomi Indonesia
Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Products - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/logo.png'
},
{
hid: 'description',
name: 'description',
content: 'Jual Produk Original Xiaomi Indonesia Bergaransi
Resmi'
},
]
}
},
1015
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('web/product/getProductsData')
},
//computed
computed: {
//products
products() {
return this.$store.state.web.product.products
},
},
//method
methods: {
//method "changePage"
changePage(page) {
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita menambahkan component baru dari Boostrap
Vue, yaitu <b-paginate>. Dan di dalamnya kita berikan data yang diambil dari computed
properti yang berisi data pagination.
Ketika navigasi pagination di klik, maka akan menjalankan event @change yang mengarah
ke dalam method yang bernama changePage.
1016
<b-pagination align="center" :value="products.current_page" :total-
rows="products.total" :per-page="products.per_page" @change="changePage"
aria-controls="my-table"></b-pagination>
//method "changePage"
changePage(page) {
Di dalam method di atas, pertama kita melakukan commit ke dalam mutation yang bernama
SET_PAGE yang berada di dalam store/web/product.js dan untuk parameter-nya akan
berupa data angka pagination yang dikirim dari component <b-paginate>.
Dan yang kedua akan menjalankan dispatch ke dalam action yang bernama
getProductsData, dengan tujuan agar melakukan fetching ulang dengan data yang
sesuai dengan pagination.
Sekarang, silahkan reload halaman index products, jika berhasil maka kita akan
mendapatkan hasil kurang lebih seperti berikut ini :
1017
1018
Menampilkan Index Data Categories
Kita akan lanjutkan menampilkan list data category di halaman index categories. Dan
karena sebelumnya sudah membuaat konfigurasi Vuex, maka kita tinggal menggunakannya
saja di dalam component / view.
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row justify-content-center">
<div class="col-md-3 mb-3" v-for="category in categories"
:key="category.id">
<div class="card border-0 rounded shadow-sm">
<div class="card-body text-center">
<nuxt-link :to="{name: 'categories-slug', params:
{slug: category.slug}}">
<img :src="category.image" width="100">
<hr>
{{ category.name }}
</nuxt-link>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
//meta
head() {
return {
title: 'Semua Kategori - MI STORE - Distributor Xiaomi Indonesia
Resmi',
1019
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'Semua Kategori - MI STORE - Distributor Xiaomi
Indonesia Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Semua Kategori - MI STORE - Distributor Xiaomi
Indonesia Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/logo.png'
},
{
hid: 'description',
name: 'description',
content: 'Semua Kategori dari Produk Xiaomi'
},
]
}
},
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('web/category/getCategoriesData')
},
//computed
computed: {
//categories
categories() {
return this.$store.state.web.category.categories
},
},
}
</script>
<style>
</style>
1020
Dari penambahan kode di atas, pertama kita atur meta tag dari component /view ini agar
sesuai dengan data yang ditampilkan.
//meta
head() {
return {
title: 'Semua Kategori - MI STORE - Distributor Xiaomi Indonesia
Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'Semua Kategori - MI STORE - Distributor Xiaomi
Indonesia Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Semua Kategori - MI STORE - Distributor Xiaomi
Indonesia Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/logo.png'
},
{
hid: 'description',
name: 'description',
content: 'Semua Kategori dari Produk Xiaomi'
},
]
}
},
Setelah itu, kita melakukan dispatch ke dalam action Vuex yang bernama
getCategoriesData. Dan kita melakukan dispatch tersebut di dalam hook asyncData,
agar prosesnya dilakukan secara SSR atau server side rendering.
1021
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('web/category/getCategoriesData')
}
Jika dari proses dispatch action Vuex di atas berhasil dilakukan, maka kita akan mengambil
data categories yang ada di dalam state categories di Vuex. Dan disini kita menggunakan
computed properti untuk mengambilnya.
//computed
computed: {
//categories
categories() {
return this.$store.state.web.category.categories
},
},
Dan untuk menampilkan data categories di dalam template, kita menggunakan directive v-
for, karena datanya lebih dari satu.
//...
</div>
1022
1023
Menampilkan Data Product Berdasarkan Category
Sekarang kita akan lanjutkan belajar bagaimana cara menampilkan detail data category,
dimana di dalam detail data tersebut akan berisi beberapa data product yang related
dengan category tersebut. Tentu saja kita akan menambahkan beberapa konfigurasi terlebih
dahulu di dalam Vuex untuk state, mutation dan action.
Silahkan buka file store/web/category.js, kemudian ubah semua isi yang ada di
dalamnya dengan kode berikut ini :
//state
export const state = () => ({
//categories
categories: [],
//category
category: {},
})
//mutations
export const mutations = {
//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {
//mutation "SET_CATEGORY_DATA"
SET_CATEGORY_DATA(state, payload) {
1024
//actions
export const actions = {
//set promise
return new Promise((resolve, reject) => {
//resolve promise
resolve()
})
})
},
//set promise
return new Promise((resolve, reject) => {
//success
.then(response => {
//resolve promise
resolve()
})
1025
})
},
Dari penambahan kode di atas, pertama kita tambahkan 1 state baru dengan nama
category. State tersebut nantinya akan berisi data detail category dan beberapa products
yang related dengan category tersebut.
//category
category: {},
//mutation "SET_CATEGORY_DATA"
SET_CATEGORY_DATA(state, payload) {
//...
}
Di dalam action di atas, kita akan melakukan http request menggunakan Axios dengan
method GET ke dalam endpoint /api/web/categories/:slug.
1026
//get to Rest API "/api/web/categories/:slug" with method "GET"
this.$axios.get(`/api/web/categories/${payload}`)
Jika proses fetching di atas berhasil dilakukan, maka akan melakukan commit ke dalam
mutation yang bernama SET_CATEGORY_DATA dengan parameter data response yang di
dapatkan dari Rest API.
<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<h3>CATEGORY : <strong>{{ category.name.toUpperCase()
}}</strong></h3>
<!-- Solid divider -->
<hr class="solid">
</div>
</div>
<div class="row">
<div class="col-md-3 mt-1 mb-4" v-for="product in
category.products" :key="product.id">
<div class="card h-100 border-0 rounded shadow-sm">
<div class="card-body">
<div class="card-img-actions">
<img :src="product.image" class="card-img img-fluid">
</div>
</div>
<div class="card-body bg-light-custom text-center rounded-
bottom">
<div class="mb-2">
<h6 class="font-weight-semibold mb-2">
1027
<nuxt-link :to="{name: 'products-slug', params: {slug:
product.slug}}" class="text-default mb-2" data-abc="true">{{
product.title }}</nuxt-link>
</h6>
<nuxt-link :to="{name: 'categories-slug', params: {slug:
product.category.slug}}" class="text-muted" data-abc="true">{{
product.category.name }}</nuxt-link>
</div>
<h6 class="mb-0 font-weight-semibold"><s class="text-
red">Rp. {{ formatPrice(product.price) }}</s> / <strong>{{
product.discount }} %</strong></h6>
<h5 class="mb-0 font-weight-semibold mt-3 text-
success">Rp. {{ formatPrice(calculateDiscount(product)) }}</h5>
<hr>
<client-only>
<vue-star-rating
:rating="parseFloat(product.reviews_avg_rating)" :increment="0.5" :star-
size="20" :read-only="true" :show-rating="false" :inline="true"></vue-
star-rating>
(<strong>{{ product.reviews_count }}</strong> ulasan)
</client-only>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
//meta
head() {
return {
title: `Category : ${this.category.name} - MI STORE -
Distributor Xiaomi Indonesia Resmi`,
meta: [{
hid: 'og:title',
name: 'og:title',
content: `Category : ${this.category.name} - MI STORE -
Distributor Xiaomi Indonesia Resmi`
},
{
hid: 'og:site_name',
1028
name: 'og:site_name',
content: `Category : ${this.category.name} - MI STORE -
Distributor Xiaomi Indonesia Resmi`
},
{
hid: 'og:image',
name: 'og:image',
content: `${this.category.image}`
},
{
hid: 'description',
name: 'description',
content: `Category : ${this.category.name} - MI STORE -
Distributor Xiaomi Indonesia Resmi`
},
]
}
},
//hook "asyncData"
async asyncData({ store , route}) {
await store.dispatch('web/category/getDetailCategory',
route.params.slug)
},
//computed
computed: {
//category
category() {
return this.$store.state.web.category.category
},
},
}
</script>
<style>
</style>
Dari penambahan kode di atas, pertama kita atur meta tag secara dinamis dengan
menambil data dari computed properti.
1029
//meta
head() {
return {
title: `Category : ${this.category.name} - MI STORE -
Distributor Xiaomi Indonesia Resmi`,
meta: [{
hid: 'og:title',
name: 'og:title',
content: `Category : ${this.category.name} - MI STORE -
Distributor Xiaomi Indonesia Resmi`
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: `Category : ${this.category.name} - MI STORE -
Distributor Xiaomi Indonesia Resmi`
},
{
hid: 'og:image',
name: 'og:image',
content: `${this.category.image}`
},
{
hid: 'description',
name: 'description',
content: `Category : ${this.category.name} - MI STORE -
Distributor Xiaomi Indonesia Resmi`
},
]
}
},
Dan di dalam hook asyncData kita melakukan dispatch ke dalam action Vuex yang
bernama getDetailCategory dengan menambahkan parameter slug yang di dapatkan
dari URL browser.
//hook "asyncData"
async asyncData({ store , route}) {
await store.dispatch('web/category/getDetailCategory',
route.params.slug)
},
1030
Dan kita akan mengambil data dari state category yang ada di dalam Vuex menggunakan
computed properti, kurang lebih seperti berikut ini :
//computed
computed: {
//category
category() {
return this.$store.state.web.category.category
},
},
Di dalam template, kita menampilkan nama category dengan kode seperti berikut ini :
Dan untuk menampilkan data products yang related dengan category, maka kita perlu
menggunakan perulangan v-for, karena datanya akan lebih dari 1.
//...
</div>
1031
1032
Deployment Project Laravel di cPanel (Shared Hosting)
Setelah menyelesaikan semua materi di dalam ebook, baik dari sisi backend (Laravel) dan
frontend (Nuxt.js), maka sekarang kita akan lanjutkan lagi belajar bagaimana cara
melakukan deployment atau proses meng-onlinekan project yang sudah kita buat agar
dapat diakses secara global di internet.
Disini saya akan berikan rekomendasi Hosting atau cPanel yang sudah support Laravel dan
akan mendapatkan potongan harga dengan memasukkan kode kupon. Dan untuk fitur-fitur
dari cPanel yang direkomendasikan ini kurang lebih seperti berikut :
Git
SSH
Composer
Terminal
Dan lain-lain.
Gratis SSL (https://)
Disini saya akan menggunakan domain https://appdev.my.id dan untuk Laravel atau
backend-nya nanti akan kita letakkan di dalam subdomain, katakanlah di subdomain berikut
ini https://ecommerce.appdev.my.id. Untuk nama subdomain ini sifatnya bebas dan silahkan
disesuaikan dengan keinginan masing-masing.
1033
Jika berhasil melakukan otentikasi, maka akan masuk ke dalam halaman dashboard dari
cPanel, kurang lebih seperti berikut ini :
Setelah itu, silahkan klik menu Subdomains dan jika berhasil akan membuka halaman
seperti berikut ini :
1034
Sekarang, kita akan membuat subdomain baru yang nantinya akan kita gunakan untuk
menaruh project Laravel. Silahkan buat subdomain sesuai dengan keinginan masing-masing.
Dalam contoh kali ini saya membuat seperti berikut ini :
Di atas saya berikan contoh untuk nama subdomain-nya adalah ecommerce dan untuk
domain yang digunakan adalah appdev.my.id. Maka nanti hasilnya seperti berikut ini :
https://ecommerce.appdev.my.id.
1035
Langkah 3 - Buat dan Import Database di cPanel
Setelah di lokal database kita berhasil di export, sekarang kita lanjutkan untuk import
database kita di cPanel. Silahkan klik menu MySQL Database, seperti pada gambar di
bawah ini :
1036
Silahkan buat nama database terlebih dahulu, di atas saya contohkan untuk nama database-
nya adalah appdev_ecommerce, setelah itu klik create database.
Setelah database sudah berhasil terbuat, sekarang kita lanjutkan untuk membuat user dan
password untuk database kita. Masih di menu yang sama di MySQL Database, akan tetapi
kita secroll kebawah dan kita akan temukan inputan untuk membuat user dan password,
kurang lebih seperti ini :
Di atas saya membuat user-nya sama dengan nama ecommerce, dan ini sesuai keinginan
kita, dengan syarat nanti harus bisa mengingatnya ya.
Kemudian untuk password silahkan diisi dengan password yang kita inginkan. Setelah itu
klik create user dan tunggu prosesnya sampai selesai.
Kemudian sekarang kita lanjutkan untuk assign untuk user dan database. masih di menu
yang sama, silahkan scroll kebawah dan kurang lebih seperti ini :
Di atas silahkan pilih user dan database yang sudah kita buat sebelumnya, maka kurang
lebih hasilnya seperti di atas. Kemudian klik add. Kemudian akan muncul Manage User
Privileges, Silahkan centang semua dan klik Make Changes.
1037
Sekarang kita lanjutkan untuk proses import database kita di cPanel, silahkan kembali ke
halaman awal cPanel, kemudian masuk ke menu phpmyadmin. Kurang lebih seperti berikut
ini :
Tunggu beberapa saat, kita akan di arahkan ke halaman phpmyadmin milik cPanel dan kita
bisa lakukan proses import database yang sudah kita export sebelumnya.
1038
Langkah 4 - Upload Project Laravel
Setelah kita berhasil import database di cPanel, sekarang kita lanjutkan untuk upload
project website-nya, langsung saja kita mulai. Silahkan klik kanan pada project Laravel dan
compress menjadi format .zip, kurang lebih seperti berikut ini :
Setelah project sudah berhasil menjadi zip file, sekarang kita kembali ke halaman cPanel
dan klik menu File Manager, kurang lebih seperti berikut ini tampilannya.
1039
Di atas, silahkan upload project dengan format zip. Jika sudah berhasil terupload, kurang
lebih seperti berikut ini :
1040
Langkah 5 - Konfigurasi Project Laravel di cPanel
Setelah file project dengan format zip sudah terupload, sekarang kita akan lakukan
konfigurasinya. Langsung saja kita mulai. Silahkan extract file zip-nya kurang lebih seperti
beriku ini :
1041
Di atas kita akan extract projectnya dengan nama backend-ecommerce kemudian silahkan
tunggu proses extracting sampai selesai. Kemudian klik reload di filemanager, maka kita
sudah mendapatkan sebuah folder dengan nama backend-ecommerce kemudian buka
folder tersebut maka kita akan mendapatkan folder lagi dengan nama backend-
ecommerce. Kemudian masuk ke folder tersebut dan itulah project kita, hasilnya seperti
berikut ini :
Kemudian kita lanjutkan untuk menampilkan hidden file, secara default cPanel meng-
hidden file dengan awalan dot (.) oleh karena itu file .htaccess, .env, dan lain-lain
semua di hidden.
1042
Untuk menampilkannya kita bisa seperti berikut ini :
Disini kita akan melakukan move data 2 kali, silahkan masuk ke folder backend-
ecommerce/backend-ecommerce/public kemudian select all file dan folder
kemudian pilih move. Kurang lebih seperti berikut ini :
1043
Di atas kita move ke dalam folder /ecommerce.appdev.my.id.
Setelah itu kita juga akan move lagi semua file yang ada di dalam folder backend-
ecommerce ke dalam folder backend-ecommerce yang diluar.
1044
Sekarang, kita lanjutkan untuk merubah beberapa kode di dalam file index.php di folder
ecommerce.appdev.my.id, silahkan masuk ke ecommerce.appdev.my.id kemudian
pilih file index.php klik kanan dan klik edit. Kurang lebih seperti berikut ini :
1045
Ubah di bagian autoload.php menjadi seperti berikut ini :
require __DIR__.'/../backend-ecommerce/vendor/autoload.php’;
Kemudian kita juga akan merubah beberapa kode lagi untuk App Service Providers,
silahkan ikuti gambar di bawah ini :
1046
Dari gambar di atas, pada bagian function boot, silahkan tambahkan kode berikut ini :
$this->app->bind('path.public', function() {
return base_path().'/../ecommerce.appdev.my.id';
});
Jadi di atas kita set untuk base path dari project kita adalah folder
ecommerce.appdev.my.id. Setelah itu kita akan lanjutkan untuk konfigurasi file .env
untuk mengatur koneksi database kita, silahkan buka file .env di dalam folder backend-
1047
ecommerce. Kemudian klik kanan dan edit.
1048
Sekarang kita lanjutkan untuk menjalankan php artisan storage:link di cPanel,
silahkan buka menu terminal di halaman utama cPanel, kurang lebih seperti berikut ini :
1049
Sebelum menjalankan perintah berikut ini, silahkan perhatikan gambar di atas, silahkan
ganti appdev dengan username cPanel kalian dan ganti ecommerce.appdev.my.id
dengan subdomain atau domain kalain. Setelah itu jalankan perintah berikut ini di dalam
terminal :
ln -s /home/username_cpanel/backend-ecommerce/storage/app/public
/home/username_cpanel/subdomain_kalian.com/storage
1050
Terus kita coba akses salah satu Rest API-nya di link berikut ini
https://ecommerce.appdev.my.id/api/web/products, jika berhasil maka kurang lebih hasilnya
seperti berikut ini :
1051
Deploy Project Nuxt.js di Vercel (SSR)
Pada tahap kali ini kita semua akan belajar bagaimana cara melakukan deployment project
Nuxt.js di dalam Vercel. Jadi apa itu Vercel ?
Vercel merupakan salah satu layanan build tools yang memiliki fitur CI/CD atau Continue
Integration dan Continue Development. Dengan Vercel kita dapat men-deploy website
static ataupun SSR dengan Git host yang sudah terkenal seperti Github, GitLab dan
Bitbucket.
Konsepnya nanti, kita akan membuat repository di salah satu Git host di atas, dalam praktek
ini yang akan kita gunakan adalah GitHub dan kita akan menggunakan jenis repository
private agar source codenya tidak bisa dilihat oleh orang lain.
Setelah source code berhasil di push atau upload ke GitHub selanjutnya kita akan
sambungkan dengan Vercel untuk proses deployment secara otomatis. Ketika kita merubah
source code dan kita push ulang di GitHub maka Vercel juga akan melakukan proses
CI/CD dari perubahan yang dilakukan di dalam repository.
1052
Dari konfigurasi di atas, silahkan disesuaikan sendiri untuk nama dan deskripsi dari project
Nuxt.js kita di GitHub. Setelah berhasil membuat repository langkah selanjutnya kita akan
upload project Nuxt.js kita di GitHub.
Konfigurasi baseURL
1053
axios: {
baseURL: 'http://localhost:8000'
},
axios: {
baseURL: 'https://ecommerce.appdev.my.id'
},
Silahkan buat file baru di dalam root project Nuxt.js dengan nama vercel.json, dimana
file ini sejejar dengan file nuxt.config.js, package.json dan lain-lain. Setelah itu,
silahkan masukkan kode berikut ini di dalam file tersebut :
{
"version": 2,
"builds": [
{
"src": "nuxt.config.js",
"use": "@nuxtjs/vercel-builder"
}
]
}
1054
Setelah itu, buat lagi file baru dengan nama now.json dan masukkan kode berikut ini :
{
"version": 2,
"env": {
"ON_VERCEL": "true"
},
"builds": [
{
"src": "api/**/*.js",
"use": "@now/node"
},
{
"src": "nuxt.config.js",
"use": "@nuxtjs/now-builder"
}
],
"routes": [
{ "src": "/api/(.*)", "dest": "api/index.js" },
{ "src": "/api", "dest": "api/index.js" },
{ "src": "/(.*)", "dest": "$1" }
]
}
1055
Delete File package-lock.json
Silahkan hapus file yang bernama package-lock.json di dalam root project Nuxt.js.
git init
git add .
Perintah di atas digunakan untuk menambahkan semua file dan folder ke dalam git.
Perintah di atas digunakan untuk memberikan komentar dari file-file dan folder yang kita
1056
tambahkan di atas, kita bisa mengganti kata initial commit dengan sesuai keinginan.
Perintah di atas digunakan untuk menambahkan alamat origin dengan repository yang
sudah kita buat sebelumnya. Di atas contohnya untuk alamat repository saya adalah
https://github.com/maulayyacyber/nuxt-ecommerce.git, maka silahkan
disesuaikan dengan alamat dari repository masing-masing.
Perintah di atas digunakan untuk melakukan push/upload semua file project ke dalam
repository GitHub. Silahkan tunggu proses uploadnya sampai selesai.
Jika semua file project sudah ter-upload, maka hasilnya kurang lebih seperti berikut ini :
1057
Setelah itu, silahkan LOGIN atau SIGN UP di dalam website Vercel, disini kita bisa
melakukan proses SIGN UP menggunakan GitHub agar mempermudah proses deployment-
nya.
Setelah berhasil mendaftar, maka kita akan mendapatkan tampilan kurang lebih seperti
berikut ini :
1058
Dari gambar di atas, kita akan mendapatkan list respository yang ada di dalam akun GitHub
kita, sekarang kita pilih respository dari project Nuxt.js yang sebelumnya sudah kita upload.
Silahkan klik IMPORT, maka kurang lebih hasilnya seperti berikut ini :
Di atas, silahkan klik Skip. Karena kita akan menggunakan tipe akun personal, bukan tim.
Setelah itu, klik Deploy.
1059
Proses deployment berhasil dilakukan dan website kita sudah bisa diakses secara global di
internet.
1060
1061
Mencoba Fitur PWA
Jika kita perhatikan di dalam URL browser kita mendapti sebuah icon untuk melakukan
installasi website kita. Kurang lebih seperti berikut ini :
Jika kita Install, maka website kita bisa terbuka seolah-olah seperti aplikasi Desktop dan
jika dibuka di dalam HP, maka seolah-olah seperti aplikasi yang diunduh dari Play Store atau
App Store.
Dan jika kita cek di dalam developer tools browser, kurang lebih hasilnya seperti berikut ini :
1062
Konfigurasi Notifikasi Handler Midtrans
Sekarang, kita akan lanjutkan untuk melakukan konfigurasi notifikasi handler di dalam
Midtrans, notifikasi ini digunakan untuk menerima pembaruan status dari data order yang
dilakukan ke dalam Midtrans dan meng-update status tersebut di dalam database kita.
Silahkan login ke Midtrans, dan jangan lupa pilih environment menggunakan Sandbox,
karena kita masih dalam tahap pengembangan.
1063
Di atas, kita harus mengatur 6 endpoint, yang digunakan untuk menerima notifikasi dari
Midtrans dan action redirect dari Midtrans.
Alamat
dimana
Midtrans akan
Payment
mengirimkan
Notification https://ecommerce.appdev.my.id/api/web/notification
notifikasi
URL*
melalui
request HTTP
Post.
Alamat
dimana
Midtrans akan
Recurring mengirimkan
Notification https://ecommerce.appdev.my.id/api/web/notification notifikasi
URL* berulang
melalui
permintaan
HTTP Post.
1064
attribute value keterangan
Alamat
dimana
Midtrans akan
Pay
mengirimkan
Account
https://ecommerce.appdev.my.id/api/web/notification notifikasi
Notification
berulang
URL*
melalui
permintaan
HTTP Post.
Halamanan
Finish
ketika
Redirect https://nuxt-ecommerce-ssr.vercel.app/customer/invoices
pembayaran
URL*
berhasil
Halamanan
Unfinish ketika
Redirect https://nuxt-ecommerce-ssr.vercel.app/customer/invoices pembayaran
URL* belum di
proses
Halamanan
Error
ketika
Redirect https://nuxt-ecommerce-ssr.vercel.app/customer/invoices
pembayaran
URL*
error/gagal
CATATAN! : Silahkan ganti nama domain dengan yang kalian miliki.
Sekarang, jika kita melakukan pembelian di dalam website dan melakukan pembayaran,
maka di database website kita juga akan ikut terupdate sesuai dengan status yang
dikirimkan oleh Midtrans dan tentu saja stock product juga akan berkurang sesuai dengan
quantity di dalam data order.
1065
Source Code
Untuk link unduh project silahkan bias mengunjungi link berikut ini :
Laravel (BackEnd):
https://drive.google.com/file/d/1njZG3mpoLhtR_77m4dbR7DwU1KDWUBlX/view?usp=s
haring
Nuxt.js (FrontEnd):
https://drive.google.com/file/d/1PLWdIL_Yz9XCzKIWPdvsbSll0pqGHR5o/view?usp=shari
ng
PERINGATAN ! : jangan gunakan source code ini apabila belum mengikuti materi sampai
selesai.
1066
Penutup
Terima kasih saya sampaikan kepada teman-teman semuanya yang sudah belajar melalui
buku yang sangat-sangat jauh dari kata sempurna. Tak lupa saya memohon maaf atas
segala kesalahan penulisan, code maupun gambar yang saya sajikan dalam tulisan ini.
Semoga dengan selesainya mempelajari semua isi yang ada di dalam buku ini dapat
memberikan gambaran bagaiaman cara menjadi seorang FullStack Web Developer yang
bisa diandalkan di era digital yang berkembang dengan sangat pesat ini.
Saya berharap juga kepada teman-teman yang sudah membeli buku ini untuk tidak
membagikan kepada orang lain secara GRATIS. Karena mempelajari atau menggunakan
sesuatu yang sifatnya bajakan tidak akan membuat ilmu dan rezeki menjadi berkah.
Jangan lupa nantikan seri buku yang lainnya dari SantriKoding.com dan teruslah belajar
untuk menambah skill dan jangan mudah puas dengan apa yang sudah kita raih saat ini.
"Kebutuhan manusia terhadap ilmu jauh lebih besar daripada kebutuhannya terhadap
makan dan minum karena makanan dan minuman hanya dibutuhkan sekali atau dua kali
saja dalam sehari, sedang ilmu, dibutuhkan dalam setiap embusan napas."
1067