Anda di halaman 1dari 805

Table of Contents

PENDAHULUAN ................................................................................................................... 6

Kata Pengantar .............................................................................................................. 7

License Buku .................................................................................................................. 8

Tentang Buku ................................................................................................................. 9

PERANCANGAN ................................................................................................................ 11

Perancangan Aplikasi ................................................................................................... 12

Struktur Database ........................................................................................................ 16

Wirefrime UI Desain Website ........................................................................................ 17

INSTALLASI & PERSIAPAN - BACKEND ......................................................................... 28

Persiapan dan Persyaratan .......................................................................................... 29

Membuat Project Laravel dengan Composer ................................................................ 33

Upgrade Versi Laravel .................................................................................................. 36

Membuat Helpers di Laravel ........................................................................................ 37

Installasi dan Konfigurasi Tailwind CSS di Laravel ........................................................ 41

DATABASE ......................................................................................................................... 48

Koneksi Database ........................................................................................................ 49

Membuat Model dan Migration ..................................................................................... 51

Eloquent Relationships ................................................................................................. 65

Eloquent Mutators & Casting ........................................................................................ 83

Membuat Data Seeder User ......................................................................................... 98

AUTHENTICATION - FORTIFY ....................................................................................... 101

Apa itu Laravel Fortify ? ............................................................................................. 102


Installasi dan Konfigurasi Laravel Fortify .................................................................... 104

Membuat Proses Login ............................................................................................... 111

Membuat Proses Forgot dan Reset Password ............................................................. 118

HALAMAN ADMIN - BACKEND ...................................................................................... 129

Membuat Halaman Dashboard ................................................................................... 130

Membuat CRUD Category .......................................................................................... 145

Membuat CRUD Campaign ......................................................................................... 173

Menampilkan Data Donatur ....................................................................................... 206

Menampilkan Data Donasi ......................................................................................... 214

Profile User dan Two Factor Authentication ............................................................... 226

Membuat CRUD Slider ................................................................................................ 246

RESTFUL API .................................................................................................................. 262

Apa itu API ? ............................................................................................................... 263

Install dan Konfigurasi Laravel Passport ..................................................................... 265

Membuat Restful API Register .................................................................................... 274

Membuat Restful API Login ........................................................................................ 281

Membuat Restful API Category ................................................................................... 288

Membuat Restful API Campaign ................................................................................. 296

Membuat Restful API Slider ........................................................................................ 303

Membuat Restful API Profile ....................................................................................... 307

Installasi dan Konfigurasi Midtrans ............................................................................. 319

Membuat Restful API Donation ................................................................................... 324

PENGENALAN VUE JS & VUEX ...................................................................................... 340

Berkenalan Dengan Vue Js ......................................................................................... 341

Composition API dan Reactivity API ............................................................................ 348

Lifecycle Hooks .......................................................................................................... 355

Berkenalan Dengan Vuex ........................................................................................... 371


Membuat Aplikasi Pertama Dengan Vuex .................................................................. 381

INSTALLASI & PERSIAPAN - FRONTEND ..................................................................... 403

Installasi dan Perispan Frontend ................................................................................ 404

Membuat Project Vue Js dengan Vue CLI .................................................................... 408

Installasi Library Pendukung ...................................................................................... 411

Installasi dan Konfigurasi Tailwind CSS di Vue Js ........................................................ 416

Membuat Helpers Menggunakan Mixins ..................................................................... 419

Konfigurasi Midtrans di Vue Js .................................................................................... 427

Membuat Component Header dan Footer .................................................................. 429

HALAMAN DONATUR ..................................................................................................... 436

Konfigurasi Global API Endpoint ................................................................................. 437

Konfigurasi Router untuk Authentication ................................................................... 438

Konfigurasi Module Auth Vuex ................................................................................... 448

Membuat Proses Register Donatur ............................................................................. 454

Menampilkan Halaman Dashboard Donatur ............................................................... 472

Membuat Proses Logout Donatur ............................................................................... 485

Membuat Proses Login Donatur ................................................................................. 496

Konfigurasi Router untuk Donation ............................................................................ 515

Konfigurasi Module Donation Vuex ............................................................................ 521

Menampilkan Data Donation dari Donatur ................................................................. 525

Konfigurasi Router untuk Profile ................................................................................. 539

Konfigurasi Module Profile Vuex ................................................................................. 544

Menampilkan Data Profile .......................................................................................... 548

Update Profile Donatur ............................................................................................... 556

Konfigurasi Router untuk Update Password ............................................................... 568

Update Password Donatur .......................................................................................... 574

HALAMAN FRONTEND ................................................................................................... 587


Konfigurasi Module Slider Vuex .................................................................................. 588

Menampilkan Data Category ...................................................................................... 592

Menampilkan Detail Category .................................................................................... 600

Konfigurasi Router untuk Campaign ........................................................................... 613

Menampilkan Data Campaign .................................................................................... 621

Menampilkan Detail Campaign .................................................................................. 635

Konfigurasi Router untuk Proses Donasi .................................................................... 655

Membuat Proses Donasi ............................................................................................. 662

Menampilkan Snap Pay Midtrans ............................................................................... 675

Konfigurasi Router untuk Search ................................................................................ 685

Membuat Realtime Search ......................................................................................... 693

Membuat Component Slider ....................................................................................... 708

Konfigurasi Module Category Vuex ............................................................................ 716

Membuat Component Category Home ....................................................................... 720

Konfigurasi Router untuk Homepage ......................................................................... 728

Menampilkan Component Slider dan Category di Halaman Homepage ..................... 734

Konfigurasi Module Campaign Vuex ........................................................................... 738

Menampilkan Data Campaign di Homepage .............................................................. 742

Konfigurasi Router untuk Category ............................................................................ 764

DEPLOYMENT ................................................................................................................. 771

Deploy Project Laravel di Shared Hosting (cPanel) .................................................... 772

Deploy Project Vue Js di Netlify .................................................................................. 791

Konfigurasi Notifikasi Handler Midtrans ..................................................................... 800

PENUTUP ......................................................................................................................... 803

Source Code ............................................................................................................... 804

Kesimpulan ................................................................................................................ 805


PENDAHULUAN

6
Kata Pengantar

Bismillahirrahmannirrahiim.

Assalamu'alaikum Warahmatullahi Wabarakatuh.

Alhamdulillah, segala puji dan syukur penulis panjatkan kehadirat Tuhan Yang Maha Esa.
Karena berkat limpahan karunia-Nya, kami dapat menyelesaikan penulisan buku
Membangun Website Donasi Online dengan Laravel, Vue Js, Tailwind CSS 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.

Wassalamu'alaikum Warahmatullahi Wabarakatuh.

Jika kamu tidak tahan terhadap penatnya belajar, maka kamu akan menanggung bahayanya
kebodohan - Imam Syafi'i

7
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.

Sukses bukan kebetulan, tapi sebuah pahatan patung yang setiap detailnya di tentukan oleh
perbuatan kita

8
Tentang Buku

Buku ini berjudul "Membangun Website Donasi Online dengan Laravel, Vue Js,
Tailwind CSS dan Payment Gateway", di dalam buku ini kita semua akan belajar
bersama-sama bagaimana cara membangun sebuah website donasi online dengan Laravel
sebagai Backend, Vue Js sebagai Frontend, Tailwind CSS sebagai UI atau user interface dan
terakhir kita menggunakan Payment Gateway (Midtrans) untuk proses pembayaran donasi
secara otomatis.

Di dalam buku ini, kita akan belajar dari 0 bagaimana proses pembuatan website donasi
sampai ke tahap deployment online, agar website yang kita bagung dapat diakses secara
global di internet.

Di dalam buku ini, akan dibagi menjadi beberapa materi, diantaranya yaitu bagaimana cara
membangun halaman Admin dan Restful API menggunakan Laravel, membangun halaman
Frontend dengan Vue Js dan sekaligus mengintegrasikannya dengan Tailwind CSS dan yang
terakhir kita akan menggunakan Payment Gateway sebagai metode pembayaran.

Di awal, kita akan fokus belajar dengan Laravel seperti bagaimana cara membuat Model,
Migration, Authentication yang mana akan kita integrasikan dengan Two Factor
Authentication atau Autentikasi 2 Langkah dengan harapan agar website yang dibangun
menjadi lebih aman.

Setelah itu, kita juga akan belajar tentang bagaimana cara membuat halaman Admin
beserta proses CRUD di Laravel untuk membuat beberapa master data, seperti Category,
Campaign, Statistik dan Filter data donasi berdasarkan 2 tanggal yang berbeda.

Setelah halaman Admin dan master data sudah selesai dibuat, selanjutnya kita akan belajar
membuat Restful API untuk beberapa proses, yaitu Authentication dengan Laravel Passport,
Campaign, Donation, Profile dan masih banyak lagi.

Di materi selanjutnya, kita akan belajar bagaimana cara mengkonsumsi API atau memanggil
API yang sebelumnya sudah kita buat di Laravel ke dalam Vue Js. Dan disini kita juga akan
melakukan installasi dan konfigurasi Tailwind CSS sebagai UI atau user interface di website
donasi yang sedang kita bangun. Untuk melakukan centralize data di dalam Vue Js, kita
akan menggunakan Vuex dan menerapkan module system agar kode menjadi lebih
maintenable dan mudah di kembangkan kedepannya.

Agar kode di dalam Vue Js tidak ditulis berulang-ulang, maka kita akan menggunakan fitur
terbaru di dalam Vue Js 3, yaitu Composition API, dengan fitur ini kode yang kita buat di
dalam aplikasi menjadi lebih reusable atau bisa digunakan secara berulang-ulang di dalam
komponen lain.

Setelah semua proses membangun website donasi selesai, kita akan lanjutkan lagi untuk
belajar bagaimana cara melakukan proses deployment atau meng-online-kan aplikasi kita di

9
internet. Disini kita akan menggunakan Shared Hosting untuk project Laravel atau Backend-
nya dan Vercel untuk project Vue Js atau Frontend.

Ilmu itu seperti air. Jika ia tidak bergerak, maka ia akan menjadi keruh lalu membusuk -
Imam Syafi'i

10
PERANCANGAN

11
Perancangan Aplikasi

Berikut ini merupakan diagram alur bagaimana website donasi berjalan, disini kita akan
pisah menjadi 2 bagian, yang pertama adalah Laravel sebagai Backend untuk membuat
halaman Admin dan Rest API dan Vue Js sebagai Frontend untuk
menkonsumsi/menggunakan Rest API yang sudah dibuat.

Dalam kenyataan, pembuatan diagram sangatlah penting sebelum kita masuk ke dalam
tahapan implementasi kode, dengan diagram kita bisa mengetahui bagaimana cara kerja
dari aplikasi yang sedang kita bangun dan akan mempermudah dalam proses development
aplikasi.

Dengan memetakan fitur-fitur menggunakan diagram, seorang software developer akan


sangat terbantu, kususunya saat membangun aplikasi dengan sekala besar, kebanyakan
dari seseorang pasti akan mengabaikan proses ini, padahal dengan adanya diagaram
seperti ini kita akan menjadi lebih paham apa fitur-fitur dan alur aplikasi saat berjalan.

Diagram Alur Laravel (Backend)

12
13
Dari diagram di atas Laravel (Backend) memiliki 2 konsep, yaitu untuk membuat halaman
Dashboard / Admin dan untuk membuat Rest API.

Dashboard / Admin

Pada tahap ini kita akan membuat sebuah halaman Dashboard / Admin dimana di dalamnya
terdapat fitur authentication, setelah proses authentication berhasil, maka kita bisa
mengakses beberapa modules, seperti Category, Campaign, Donatur, dan lain-lain. Di dalam
module-module tersebut juga terdapat action, seperti hanya bisa melakukan create, update
dan delete sebuah data.

Rest API

Di tahap ini, kita akan membuat 2 jenis Rest API, yaitu Rest API yang bersifat global dan
bersifat private atau harus menggunakan authentication untuk dapat mengakses data di
dalamnya.

Untuk Rest Api yang bersifat private, nanti untuk melakukan proses get data kita akan
membutuhkan proses authentication terlebih dahulu dan dari proses authentication tersebut
akan mendapatkan sebuah token, yang mana token tersebut akan dijadikan sebuah
parameter untuk proses mendapatkan data ke dalam server.

Diagram Alur Vue Js (Frontend)

14
Dari diagram di atas Vue Js (Frontend) memiliki 2 konsep, yaitu untuk membuat halaman
Dashboard / User dan halaman Front Web.

Halaman Dashboard / User

Pada halaman ini kita akan membutuhkan sebuah authentication, dimana proses ini akan
memanggil Rest API yang sudah kita buat di Laravel, setelah proses authentication berhasil,
maka kita bisa mengakses beberapa modules, diantaranya adalah data profile, update
password dan data donasi.

Halaman Front Web

Halaman ini bersifat global dan dapat diakses oleh siapapun tanpa harus melakukan proses
authentication, di dalam halaman ini akan menampilkan beberapa data, seperti Slider,
Category dan juga Campaign.

15
Struktur Database

Untuk struktur dan relasi database yang akan kita buat nanti, kurang lebih seperti gambar
berikut ini :

Untuke attribute status di dalam table donations memiliki tipe data enum, yang isinya
adalah pending, success, expired dan failed.

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 donasi 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

Pertama adalah halaman homepage, pada halaman ini akan kita berikan beberapa
informasi, seperti menampilkan Slider, Kategori dan juga data Campaign. Kurang lebih
seperti berikut ini :

17
18
Detail Campaign

Selanjutnya adalah halaman detail campaign, di dalam halaman ini akan kita buat untuk
menampilkan beberapa informasi, seperti gambar dari campaign, judul, penggalang dana,
jumlah orang yang berdonasi, jumlah uang yang sudah terkumpul dan deskripsi dari
campaign. Kurang lebih seperti berikut ini :

19
20
Donation

Kemudian adalah halaman donation, halaman ini akan muncul ketika kita menekan atau klik
tombol DONASI SEKARANG pada halaman detail campaign, pada halaman ini kita akan
menampilkan sebuah form yang berfungsi untuk melakukan pembayaran donasi, disini kita
membuat 2 buat input, yaitu nominal donasi dan juga kata-kata/doa. Kurang lebih seperti
berikut ini :

21
22
Dashboard

Sekarang adalah halaman dashboard, halaman ini akan muncul ketika kita sudah berhasil
melakukan proses register dan login di dalam website, dihalaman ini akan menampilkan
beberapa informasi menu, seperti donasi saya, profile, password dan juga logout. Kurang
lebih seperti beirkut ini :

23
24
Update Profile

Terakhir adalah halaman update profile, halaman ini akan muncuk ketika kita melakukan
klik button EDIT PROFILE yang ada di dalam halaman dashboard, pada halaman ini kita
bisa melakukan update data, seperti mengganti foto dan juga mengganti informasi nama.
Kurang lebih seperti berikut ini :

25
26
Itulah beberapa wirefrime atau gambaran dari tata letak website donasi yang akan kita
bangun nanti, disini kita tidak akan membahas semua wirefrime dari website donasi, di atas
hanya bagian-bagian inti dari website donasi nantinya.

27
INSTALLASI & PERSIAPAN -
BACKEND

28
Persiapan dan Persyaratan

Sebelum kita memulai membangun sebuah website dengan Laravel dan Vue Js, maka ada
beberapa persiapan dan persyaratan yang harus kita lakukan. Diantaranya menyiapkan
sebuah web server seperti Apache, Nginx dan lain-lain, kemudian untuk database biasanya
kita mengunakan MySQL. dan yang terakhir adalah PHP.

Ketiga tools di atas, biasanya sudah di bundle atau dijadikan 1 dalam sebuah aplikasi yang
bernama XAMPP, WAMPP, Laragon dan masih banyak lagi yang lainnya. Karena kita akan
membuat project dengan Laravel dan Vue Js, maka ada tambahan lagi untuk tools yang
akan kita gunakan, yaitu Composer dan Node Js.

Composer adalah tools dependency manager pada PHP, yang digunakan untuk men-
download library-library tambahan ke dalam project yang sedang kita buat. Contohnya, jika
kita ingin mengembangkan project berbasis ecommerce, maka kita membutuhkan library
tambahan yaitu RajaOngkir untuk mendapatkan biaya ongkos kirim secara realtime, dan
Composer digunakan untuk melakukan proses download library RajaOngkir tersebut ke
dalam project ecommerce yang kita bagun.

Node Js adalah JavaScript runtime yang bersifat open-source dan di kembangkan dengan
engine V8 milik Google. Jika kita tahu, bahwa JavaScript umumnya berjalan disisi client atau
browser, dan dengan Node Js, maka JavaScript bisa di jalankan di sisi server dan tanpa harus
menggunakan browser. Node Js juga memiliki server sendiri oleh sebab itu kita bisa
menjalankan Node Js tanpa harus menggunakan web server tambahan seperti Apache dan
Nginx.

Di dalam Node Js ada tools dependency manager yaitu NPM, sama seperti Composer,
NPM juga digunakan untuk men-download library-library yang dibutuhkan saat kita
membuat project berbasis Node Js dan JavaScript.

Berikut ini syarat yang harus kita penuhi sebelum kita melanjutkan membangun website
donasi dengan Laravel dan Vue Js.

NAMA KETERANGAN

XAMPP / WAMPP / LARAGON / VALET/


Web Server, Database dan PHP
HOMESTEAD (minimal PHP versi 7.3)

Composer tools dependency manager pada PHP

Node Js (minimal versi 14 / 15) JavaScript runtime

NPM tools dependency manager pada Node Js

29
Installasi XAMPP
Berikut ini beberapa link yang bisa kita gunakan untuk melakukan download XAMPP
dengan versi PHP minimal 7.3. SIlahkan disesuaikan dengan sistem operasi yang digunakan.

Windows (64-bit) :
https://www.apachefriends.org/xampp-files/7.3.27/xampp-windows-x64-7.3.27-0-VC15
-installer.exe
Linux (64-bit) :
https://www.apachefriends.org/xampp-files/7.3.27/xampp-linux-x64-7.3.27-0-installer.r
un
MacOS (64-bit) :
https://www.apachefriends.org/xampp-files/7.3.27/xampp-osx-7.3.27-0-installer.dmg

Installasi Composer
Jika menggunakan XAMPP, maka untuk Composer kita harus melakukan installasi manual,
karena tidak ikut di dalam aplikasi XAMPP tersebut, akan tetapi jika menggunakan
Homestead maka kita tidak perlu melakukan installasi Composer, karena secara default,
paket Composer sudah tersedia di dalamnya. Berikut ini link untuk melakukan installasi
Composer dan silahkan di sesuaikan dengan sistem operasi yang digunakan.

Installation - Linux / Unix / macOS :


https://getcomposer.org/doc/00-intro.md#installation-linux-unix-macos
Installation - Windows : https://getcomposer.org/doc/00-intro.md#installation-windows

Untuk memverifikasi apakah Composer sudah berhasil terinstall di dalam komputer, kita
bisa menjalankan perintah berikut ini di dalam CMD/terminal :

composer

Jika berhasil, maka akan muncul tampilan yang kurang lebih seperti berikut ini :

30
Installasi Node Js
Untuk installasi Node Js sangat mudah sekali, kita bisa mengunduh binary file dari situs
resminya atau bisa menggunakan command line. Untuk installasi silahkan bisa
membukanya di link berikut ini : https://nodejs.org/en/download/

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

Jika berhasil, maka akan muncul tampilan yang kurang lebih seperti berikut ini :

31
32
Membuat Project Laravel dengan Composer

Sebenarnya ada beberapa cara untuk membuat project baru dengan Laravel, jika kita baca
di dokumetasi resmi Laravel, ada beberapa teknik yang bisa kita gunakan. Dan pada ebook
ini kita akan menggunakan Composer dengan perintah composer create-project.
Cara seperti ini yang umumnya dipakai dan bisa memilih versi Laravel yang ingin
digunakan.

Langkah 1 - Membuat Project Laravel


Sekarang kita akan belajar membuat project Laravel menggunakan Composer, silahkan
masuk ke dalam folder dimana kita akan menyimpan project-nya. Jika menggunakan XAMPP,
maka masuk di dalam folder htdocs, kemudian buka CMD/terminal dan arahkan ke dalam
folder htdocs tersebut, dan jalankan perintah berikut ini :

composer create-project laravel/laravel:^8.0 backend-donasi

Perintah di atas, akan membuat project Laravel baru dengan nama backend-donasi,
silahkan tunggu proses installasinya sampai selesai.

33
Langkah 2 - Menjalankan Project
Setelah proses installasi project selesai, sekarang kita akan belajar bagaimana cara
menjalankan project Laravel tersebut, silahkan jalankan perintah berikut ini :

cd backend-donasi

Perintah cd digunakan untuk melakukan enter atau masuk ke dalam sebuah direktori/folder,
dalam contoh di atas kita masuk ke dalam folder backend-donasi, dimana folder ini
merupakan project Laravel-nya. Setelah itu jalankan perintah berikut ini :

php artisan serve

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 :

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:

34
php artisan storage:link

Jika berhasil, maka teman-teman akan melihat file/folder dengan nama storage di dalam
folder public.

35
Upgrade Versi Laravel

Halaman ini akan di update ketika ada pembaruan versi Laravel

36
Membuat Helpers di Laravel

Disini kita akan belajar bagaimana cara membuat Helpers atau fungsi tambahan yang mana
fungsi ini bisa digunakan secara global di dalam project kita. Kita akan membuat 1 buah
helpers yang digunakan untuk merubah format angka/number menjadi mata uang
Indonesia, jadi di dalam helper nanti kita akan tambahkan kata Rp. dan menambahkan
pemisah angka/number untuk ribuan, jutaan dan lainnya menggunakan notasi ..

Langkah 1 - Membuat Helpers


Sekarang, silahkan buka terlebih dahulu project-nya menggunakan text editor, bisa Sublime
Text, VS Code dan lain-lain, ini akkan mempermudah kita dalam proses development.

Setelah sudah dibuka menggunakan text editor, sekarang silahkan buat folder baru dengan
nama Helpers di dalam folder app. Kemudian di dalam folder Helpers silahkan buat file
baru dengan nama helpers.php dan masukkan kode berikut ini :

37
<?php

if (! function_exists('moneyFormat')) {
/**
* moneyFormat
*
* @param mixed $str
* @return void
*/
function moneyFormat($str) {
return 'Rp. ' . number_format($str, '0', '', '.');
}
}

Di atas kita membuat 1 function/method dengan parameter $str, parameter tersebut


bernilai dinamis, dimana akan diisi dengan angka yang akan di format nantinya.

Kemudian di dalam function/method tersebut kita melakukan return dengan menambahkan


string Rp. dan kita tambahkan fungsi dari PHP yaitu number_format, dimana di dalamnya
terdapat 3 parameter, yang pertama adalah number yang akan di format, kedua kita isi
kosong dan terakhir kita berikan untuk notasinya, yaitu ..

Untuk menggunakan Helpers ini akan kita praktekan nanti di dalam project, untuk contoh
penggunaan dari helper ini kurang lebih seperti berikut ini :

<?php

$angka = 10000;

//format angka dengan helper


$hasil = moneyFormat($angka);

echo $hasil;

//hasilnya akan menampilkan seperti berikut ini : Rp. 10.000

?>

38
Langkah 2 -Register Helpers
Agar helpers dapat digunakan secara global di dalam project, maka kita harus melakukan
register di dalam file composer.json. Silahkan buka file composer.json yang berada di
dalam root project, kemudian pada bagian autoload tambahkan kode seperti berikut ini :

"autoload": {
......
"files": [
"app/Helpers/helpers.php"
]
},

Kurang lebih seperti berikut ini :

Setelah itu, simpan file composer.json dan jalankan perintah berikut ini :

composer dump-autoload

39
Dan sekarang, helpers kita sudah bisa digunakan secara global di dalam project.

40
Installasi dan Konfigurasi Tailwind CSS di Laravel

Sekarang kita akan belajar bagaimana cara installasi dan konfigurasi Tailwind CSS di
dalam project Laravel. Dan disini kita juga akan menambahkan plugin official dari Tailwind
CSS untuk mempercantik input form yang akan kita buat nanti di dalam website.

Apa itu Tailwind CSS ?


Tailwind CSS merupakan CSS framework yang bersifat utility-first, yang mana bisa
digunakan untuk membangun tampilan antar muka sebuah website menjadi lebih cepat. Jika
menurut website resminya kurang lebih seperti berikut ini :

A utility-first CSS framework packed with classes like flex, pt-4, text-center and
rotate-90 that can be composed to build any design, directly in your markup.

Tailwind CSS sangat populer sekali saat ini, kita bisa melihat statistik dari penggunaan
Tailwind CSS yang meloncat sangat tinggi jika dibandingkan dengan framework CSS lain di
tahun 2019 dan 2020.

statistik di tahun 2019

Sumber : https://2019.stateofcss.com/technologies/css-frameworks/

41
Statistik di tahun 2020

Sumber : https://2020.stateofcss.com/en-US/technologies/css-frameworks/

Dari gambar statistik di atas, kita bisa ambil kesimpulan bahwa Tailwind CSS memang
sangat populer dan banyak digunakan untuk saat ini, maka kita juga akan mencoba
menggunakan Tailwind CSS di dalam project Laravel.

Langkah 1 - Installasi Tailwind CSS


Sekarang kita akan belajar bagaimana cara installasi Tailwind CSS di dalam project
Laravel. Silahkan jalankan perintah berikut ini di dalam terminal/CMD :

npm install -D tailwindcss@2.0.4 postcss@8.2.8 autoprefixer@10.2.5

Langkah 2 - Publish File Konfigurasi Tailwind CSS


Setelah proses installasi selesai sekarang kita akan membuat file konfigurasi dari Tailwind
CSS, silahkan jalankan perintah berikut ini di dalam terminal/CMD :

42
npx tailwindcss init

Jika perintah di atas berhasil, maka kita akan mendapatkan file baru dengan nama
tailwind.config.js di dalam root folder project. Dan isinya kurang lebih seperi berikut
ini :

module.exports = {
purge: [],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}

Langkah 3 - Konfigurasi Dengan Laravel MIX


Sekarang kita akan lanjutkan untuk menambahkan Tailwind CSS di dalam file konfigurasi
dari Laravel MIX, ini bertujuan agar kita dapat melakukan compile Tailwind CSS
menggunakan Laravel MIX.

Silahkan buka file webpack.mix.js kemudian ubah kode-nya menjadi seperti berikut ini :

43
const mix = require('laravel-mix');

/*
|----------------------------------------------------------------------
----
| Mix Asset Management
|----------------------------------------------------------------------
----
|
| Mix provides a clean, fluent API for defining some Webpack build
steps
| for your Laravel applications. By default, we are compiling the CSS
| file for the application as well as bundling up all the JS files.
|
*/

mix.js('resources/js/app.js', 'public/js')
.postCss('resources/css/app.css', 'public/css', [
require("tailwindcss"), // <-- tambahkan ini
]);

Langkah 4 - Konfigurasi Style Yang Tidak Digunakan di


Dalam Production
Sekarang kita akan menambahkan konfigurasi untuk menghapus class-class yang tidak
digunakan saat aplikasi kita sudah dalam mode production. Ini akan bertujuan agar file
compile yang dihasilkan menjadi sangat kecil, sehingga load website menjadi cepat.

Silahkan buka file tailwind.config.js kemudian ubah kode-nya menjadi seperti berikut
ini :

44
module.exports = {
purge: [
'./resources/**/*.blade.php',
'./resources/**/*.js',
],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}

Di atas kita set pada bagian array purge dimana file-file template kita berada. Dan Purge
CSS akan mengambil class-class yang terdapat di dalam file tersebut untuk dilakukan
compile.

Langkah 5 - Menambahkan Plugin Form


Sekarang kita akan menambahkan plugin form dari Tailwind CSS, agar input form yang
akan kita buat nanti bisa jadi lebih bagus. Silahkan jalankan perintah berikut ini di dalam
terminal/CMD :

npm install @tailwindcss/forms@0.1.4

Setelah proses installasi selesai, sekarang silahkan buka file tailwind.config.js untuk
mengaktifkan plugin tersebut. Dan ubah kode-nya menjadi seperti berikut ini :

45
module.exports = {
purge: [
'./resources/**/*.blade.php',
'./resources/**/*.js',
],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [
require('@tailwindcss/forms'), // <-- kita tambahkan ini
],
}

Langkah 6 - Include Tailwind CSS di Aplikasi


Sekarang, silahkan buka file resources/css/app.css, kemudian ubah kode-nya menjadi
seperti berikut ini :

@tailwind base;
@tailwind components;
@tailwind utilities;

Langkah 7 - Compile Tailwind CSS dengan Laravel MIX


Sekarang kita lakukan compile Tailwind CSS dengan Laravel MIX agar kita dapat
menggunakanya di dalam template nantinya. Silahkan jalankan perintah berikut ini di dalam
terminal/CMD :

npm run dev

Jika perintah di atas berhasil, maka kita akan mendapatkan 2 file baru di dalam folder :

public/css/app.css

46
public/js/app.js

Kedua file itulah yang akan kita gunakan di dalam project nanti.

47
DATABASE

48
Koneksi Database

Sekarang kita akan belajar bagaimana cara menghubungkan project website donasi kita
dengan database, silahkan buka file .env dan cari kode berikut ini :

DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

Kemudian ubah menjadi seperti berikut ini :

DB_DATABASE=db_backend_donasi
DB_USERNAME=root
DB_PASSWORD=

Di atas, kita set untuk DB_DATABASE menggunakan db_backend_donasi 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.

Sekarang kita lanjutkan membuat databasenya melalui PhpMyAdmin. Silahkan aktifkan


fitur MySQL di XAMPP (Jika menggunakan XAMPP) dan buka link berkut ini di dalam web

49
browser : http://localhost/phpmyadmin dan silahkan buat database baru dengan nama
db_backend_donasi, kurang lebih seperti berikut ini :

50
Membuat Model dan Migration

Sebelumnya, kita telah belajar bagaimana cara menghubungkan project website donasi
dengan database, sekarang kita akan belajar bagaimana cara membuat sebuah Model dan
Migration untuk kebutuhan project website donasi nantinya.

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.

51
Langkah 1 - Modifikasi Model User
Pertama kita akan lakukan beberapa modifikasi pada Model dan file Migartion User, secara
default Laravel sudah menyediakan kita Model dan juga Migration untuk User, akan tetapi
disini kita akan melakukan sedikit perubahan untuk kebutuhan project website donasi yang
akan kita bangun.

Silahkan buka file


database/migrations/2014_10_12_000000_create_users_table.php, kemudian
ubah pada bagian function up menjadi seperti berikut ini :

public function up()


{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->string('avatar')->nullable();
$table->rememberToken();
$table->timestamps();
});
}

Di atas, kita hanya menambahkan 1 attribute yaitu avatar dan kita setting menjadi
nullable, yang artinya tidak wajib diisi.

Sekarang, kita lanjutkan untuk menambahkan mass assigment untuk field avatar di atas di
dalam Model, ini digunakan agar field tersebut dapat melakukan manipulasi/insert data.
Silahkan buka file app/Models/User.php, kemudian cari kode berikut ini :

52
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
];

Kemudian ubah menjadi seperti berikut ini :

/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
'avatar' // <-- tambahkan ini
];

Di atas, kita menambahkan field avatar di dalam properti $fillable.

Langkah 2 - Membuat Model dan Migration Category


Sekarang kita lanjutkan untuk membuat Model dan Migration untuk Category, silahkan
jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:model Category -m

Perintah di atas digunakan untuk membuat sebuah Model dengan nama Category dan
karena kita menambahkan flag -m artinya file Migration akan ikut ter-generate juga. Kita
bisa melihat 2 file tersebut di dalam folder :

53
app/Models/Category.php
database/migrations/2021_02_17_002827_create_categories_table.php

INFO!: Untuk nama dari file migration akan random sesuai dengan tanggal pembuatannya.

Sekarang kita lanjutkan untuk melakukan beberapa penambahan attribute di dalam file
migration, silahkan buka file
database/migrations/2021_02_17_002827_create_categories_table.php dan
pada function up ubah kode-nya menjadi seperti berikut ini :

public function up()


{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug');
$table->string('image');
$table->timestamps();
});
}

Di atas, kita menambahkan 3 attribute baru, yaitu :

name - menggunakan tipe data string


slug - menggunakan tipe data string
image - menggunakan tipe data string

KETERANGAN! : attribute slug akan digunakan untuk membuat URL dari data category
agar menjadi lebih friendly di SEO (Search engine optimization) nantinya.

Setelah menambahkan beberapa attribute di file migration category, sekarang kita lanjutkan
di dalam Model Category, 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/Moldes/Category.php, kemudian ubah kode-nya menjadi seperti


berikut ini :

54
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Category extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'slug', 'image'
];
}

Di atas kita menambahkan properti $fillable, dimana properti tersebut digunakan untuk
mass assigment dari attribute yang sudah kita tambahkan di file migration.

Langkah 3 - Membuat Model dan Migration Campaign


Setelah berhasil membuat Model dan Migration untuk Category, sekarang kita lanjutkan
untuk Campaign, silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:model Campaign -m

Perintah di atas akan membuat Model baru dengan nama Campaign dan akan otomatis
membuat file migration juga, karena di dalam perintah di atas kita menambahkan flag -m.
Maka dari perintah diatas kita akan mendapatkan 2 file baru, yaitu :

app/Models/Campaign.php
database/migrations/2021_02_17_004817_create_campaigns_table.php

Pertama, kita akan menambahkaan beberapa attribute terlebih dahulu di dalam file
migraion campaign, silahkan buka file
database/migrations/2021_02_17_004817_create_campaigns_table.php dan

55
pada function up ubah kode-nya menjadi seperti berikut ini :

public function up()


{
Schema::create('campaigns', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('slug');
$table->unsignedInteger('category_id');
$table->bigInteger('target_donation');
$table->date('max_date');
$table->text('description');
$table->string('image');
$table->unsignedInteger('user_id');
$table->timestamps();
});
}

Dari penambahan kode di dalam file migration di atas, kita menambahkan 8 attribute,
diantaranya adalah :

title - menggunakan tipe data string


slug - menggunakan tipe data string
category_id - menggunakan jenis unsignedInteger, yang artinya attribute ini
akan digunakan untuk melakukan relasi dengan table categories
target_donation - menggunakan tipe data bigInteger
max_date - menggunakan tipe data date
description - menggunakan tipe data text
image - menggunakan tipe data string
user_id - menggunakan jenis unsignedInteger, artinya attribute ini akan
digunakan untuk melakukan relasi dengan table users

Setelah menambahkan beberapa attribute di dalam file migration campaign, sekarang kita
lanjutkan untuk menambahkan mass assigment di dalam Molde Campaign, ini bertujuan
agar attribute yang sudah kita tambahkan di atas dapat melakukan manipulasi data, seperti
proses insert, update dan delete ke dalam database.

Silahkan buka file app/Models/Campaign.php dan ubah kode-nya menjadi seperti berikut
ini :

56
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Campaign extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'title', 'slug', 'category_id', 'target_donation', 'max_date',
'description', 'image', 'user_id',
];
}

Di atas, kita menambahkan properti $fillable, properti tersebut bertujuan untuk


mendefinisikan attribute yang dapat melakukan mass assigment untuk menipulasi data ke
dalam database.

Langkah 4 - Membuat Model dan Migration Donatur


Sekarang kita lanjutkan untuk membuat Model dan Migration untuk Donatur, silahkan
jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:model Donatur -m

Perintah di atas akan melakukan generate 2 file, yaitu Model Donatur dan file Migration
donatur, yang kurang lebih berada di dalam folder :

app/Models/Donatur.php
database/migrations/2021_02_17_013015_create_donaturs_table.php

Setelah 2 file diatas berhasil di generate, maka sekarang kita lanjutkan untuk
menambahkan beberapa attribute di dalam file migration.

57
Silahkan buka file
database/migrations/2021_02_17_013015_create_donaturs_table.php dan
pada function up ubah kode-nya menjadi seperti berikut ini :

public function up()


{
Schema::create('donaturs', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->string('avatar')->nullable();
$table->rememberToken();
$table->timestamps();
});
}

Dari penambahan kode file migration di atas, kita menambahkan 6 attribute baru,
diantaranya adalah :

name - menggunakan tipe data string


email - menggunakan tipe data string dan kita setting menjadi unique, artinya isi
di dalamnya tidak boleh ada yang sama.
email_verified_at - menggunakan tipe data timestamp dan kita setting menjadi
nullable, artinya field/kolom ini tidak wajib diisi.
password - menggunakan tipe data string
avatar - menggunakan tipe data string
rememberToken - akan ter-generate dengan tipe data string

Setelah berhasil menambahkan beberapa attribute untuk file migration donatur, maka
sekarang kita lanjutkan untuk menambahkan mass assigment di dalam Model Donatur,
silahkan buka file app/Models/Donatur.php dan ubah semua kode-nya menjadi seperti
berikut ini :

58
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Donatur extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password', 'avatar'
];
/**
* hidden
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
}

Di atas kita menambahkan sebuah properti yang bernama $fillable, properti tersebut
akan berfungsi sebagai mass assigment yang akan mengizinkan attribute yang ada di
dalamnya dapat melakukan manipulasi data.

Kita juga menambahkan properti $hidden, properti ini akan digunakan untuk
menyembunyikan isi dari attribute password dan remember_token saat kita melakukan
get data.

Langkah 5 - Membuat Model dan Migration Donation


Setelah berhasil menambahkan Model dan Migration Donatur, sekarang kita lanjutkan
untuk membuat Model dan Migration untuk data Donation. Silahkan jalankan perintah
berikut ini di dalam terminal/CMD :

59
php artisan make:model Donation -m

Perintah di atas akan melakukan sebuah generate file Model dan Migration, yang kurang
lebih ada di dalam folder :

app/Models/Donation.php
database/migrations/2021_02_17_021017_create_donations_table.php

Pertama, kita akan menambahkan beberapa attribute terlebih dahulu di dalam file
migration, silahkan buka file
database/migrations/2021_02_17_021017_create_donations_table.php dan
pada function up ubah kode-nya menjadi seperti berikut ini :

public function up()


{
Schema::create('donations', function (Blueprint $table) {
$table->id();
$table->string('invoice');
$table->unsignedInteger('campaign_id');
$table->unsignedInteger('donatur_id');
$table->bigInteger('amount');
$table->text('pray')->nullable();
$table->string('snap_token')->nullable();
$table->enum('status', array('pending', 'success', 'expired',
'failed'));
$table->timestamps();
});
}

Dari penambahan kode di atas, kita menambahkan 7 attribute untuk table donations,
yaitu :

invoice - menggunakan tipe data string


campaign_id - menggunakan jenis unsignedInteger dan akan digunakan untuk
melakukan relasi dengan table campaigns
donatur_id - menggunakan jenis unsignedInteger dan akan digunakan untuk
melakukan relasi dengan table donaturs
amount - menggunakan tipe data bigInteger
pray - menggunakan tipe data text dan kita setting menjadi nullable yang artinya
attribute ini tidak wajib diisi atau boleh di kosongkan
snap_token - menggunakan tipe data string dan kita setting menjadi nullable
yang artinya attribute ini tidak wajib diisi atau boleh di kosongkan

60
status - menggunakan tipe data enum yang di dalamnya ada beberapa pilihan,
seperti pending, success, expired dan failed

Setelah menambahkan beberapa attribute di dalam file migration, sekarang kita lanjutkan
untuk menambahkan pengaturan mass assigment agar attribute di atas dapat melakukan
manipulasi data ke dalam database.

Silahkan buka file app/Models/Donation.php kemudian ubah kode-nya menjadi seperti


berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Donation extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice', 'campaign_id', 'donatur_id', 'amount', 'pray',
'status', 'snap_token'
];
}

Dari penambahan kode diatas, kita menambahkan properti $fillable, properti tersebut
digunakan untuk melakukan proses mass assigment agar attribute yang ada di dalamnya
dapat melakukan manipulasi data ke dalam database.

Langkah 6 - Membuat Model dan Migration Slider


Terakhir, kita akan membuat Model dan Migration untuk data Slider, silahkan jalankan
perintah berikut ini di dalam terminal/CMD :

61
php artisan make:model Slider -m

Dari perintah di atas, kita akan mendapatkan 2 file baru, yaitu Model dan juga file Migration,
yang kurang lebih berada di dalam folder :

app/Models/Slider.php
database.migrations/2021_02_17_024346_create_sliders_table.php

Pertama, kita akan melakukan penambahan beberapa kolom di dalam file migration slider,
silahkan buka file
database/migrations/2021_02_17_024346_create_sliders_table.php dan pada
function up ubah menjadi seperti berikut ini :

public function up()


{
Schema::create('sliders', function (Blueprint $table) {
$table->id();
$table->string('image');
$table->string('link');
$table->timestamps();
});
}

Dari penambahan kode di atas, kita menambahkan 2 field/kolom, yaitu :

image - menggunakan tipe data string


link - menggunakan tipe data string

Setelah menambahkan attribute di dalam migration, sekarang kita lanjutkan untuk


menambahkan mass assigment di dalam Model Slider. Silahkan buka file
app/Models/Slider.php dan ubah semua kode-nya menjadi seperti berikut ini :

62
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Slider extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'image', 'link'
];
}

Di atas, kita menambahkan sebuah properti yang bernama $fillable, properti tersebut
digunakan untuk melakukan mass assigment dan mengizinkan attribute yang di definisikan
di dalamnya dapat melakukan manipulasi data ke dalam database.

Langkah 7 - Menjalankan Migration


Setelah kita berhasil menambahkan banyak Model dan juga Migration, maka sekarang kita
akan belajar bagaimana cara menjalankan proses migrate agar table-table dan attribute
yang sudah kita buat di atas akan ter-generate ke dalam database.

Silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan migrate

63
Jika proses migrate berhasil, maka kita akan melihat hasilnya seperti gambar di atas dan jika
kita cek di database, maka kita bisa melihat semua table-table sudah ter-generate.

64
Eloquent Relationships

Jika kita mengembangkan sebuah aplikasi, sering kali akan melibatkan banyak table-table di
dalamnya dan memungkinkan antara satu table dengan table yang lain saling terhubung.
Dengan Laravel kita bisa melakukan hal tersebut dengan sangat mudah, yaitu dengan
menggunakan fitur Eloquent Relationships. Dengan menggunakan fitur ini kita dapat
melakukan berbagai macam relasi antara satu table ke table yang lainnya, seperti one-to-
one, one-to-many dan many-to-many.

Dalam membangun aplikasi tidak mungkin kita akan menyimpan sebuah data di dalam 1
table, apalagi aplikasi yang di kembangkan sangat besar, maka dengan relasi kita bisa
memecahkan problem tersebut, yaitu memisahkan menjadi beberapa table yang saling
berhubungan. Contohnya kurang lebih seperti berikut ini :

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

65
id name

1 Fika Ridaul Maulayya

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.

66
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

2 Sukses selalu SantriKoding 2

3 Terima Kasih 3

4 Sukses tanpa ada error 1

5 Mantap bener tutorialnya 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.

67
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

68
id name

4 Node Js

table post_tag (Pivot Table)

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 kita belajar memahami tentang relationships di Laravel, maka sekarang kita akan
belajar untuk mengimplementasikannya di dalam project yang sedang kita kembangkan.

1. Relasi one-to-many (Inverse) antara table categories dengan table


campaigns, yang artinya satu data campaign hanya bisa memiliki satu category dan
satu category bisa dimiliki oleh banyak campaign.
2. Relasi one-to-many (Inverse) antara table campaigns dengan table users,
yang artinya satu user bisa memiliki banyak campaign dan satu campaign hanya
boleh dimiliki satu user.
3. Relasi one-to-many (Inverse) antara table donations dengan table
campaigns, yang artinya satu campaign bisa memiliki banyak donation dan satu
donation hanya boleh dimiliki satu campaign.
4. Relasi one-to-many (Inverse) antara table donations dengan table
donaturs, yang artinya satu donatur bisa memiliki banyak donation dan satu
donation hanya boleh dimiliki satu donatur.

Langkah 1 - One to many (Inverse) Table Categories dan


Table Campaigns
Sekarang silahkan buka file app/Models/Category.php dan ubah kode-nya menjadi
seperti berikut ini :

69
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Category extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'slug', 'image'
];

/**
* campaign
*
* @return void
*/
public function campaigns()
{
return $this->hasMany(Campaign::class);
}
}

Di atas, kita menambahkan satu method baru, yaitu campaigns. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model
Campaign atau table campaigns.

Kemudian, kita akan lanjutkan untuk membuat untuk kebalikannya, Silahkan teman-teman
buka file app/Models/Campaign.php dan ubahlah kode-nya menjadi seperti berikut ini :

70
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Campaign extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'title', 'slug', 'category_id', 'target_donation', 'max_date',
'description', 'image', 'user_id',
];

/**
* category
*
* @return void
*/
public function category()
{
return $this->belongsTo(Category::class);
}
}

Di atas, kita menambahkan method category yang di fungsikan untuk mendeklarasikan


bahwa model Category atau table categories ini dimiliki dan terhubung dengan Model
Campaign atau table campaigns.

Langkah 2 - One to many (Inverse) Table Campaigns dan


Table Users
Sekarang kita lanjutkan untuk membuat relasi one-to-many (Inverse) antara table
campaigns dan table users. SIlahkan buka file app/Models/User.php dan ubah kode-
nya menjadi seperti berikut ini :

71
<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable


{
use HasFactory, Notifiable;

/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
'avatar'
];

/**
* 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',
];
/**
* campaigns

72
*
* @return void
*/
public function campaigns()
{
return $this->hasMany(Campaign::class);
}
}

Di atas, kita menambahkan satu method baru, yaitu campaigns. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model
Campaign atau table campaigns.

Sekarang kita akan membuat inverse atau kebalikannya, agar kita dapat mengakses data
user dari Model Campaign. Silahkan buka file app/Models/Campaign.php, kemudian
ubah kode-nya menjadi seperti berikut ini :

73
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Campaign extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'title', 'slug', 'category_id', 'target_donation', 'max_date',
'description', 'image', 'user_id',
];

/**
* category
*
* @return void
*/
public function category()
{
return $this->belongsTo(Category::class);
}
/**
* user
*
* @return void
*/
public function user()
{
return $this->belongsTo(User::class);
}
}

Di atas, kita menambahkan method user yang di fungsikan untuk mendeklarasikan bahwa
model Campaign atau table campaigns ini dimiliki dan terhubung dengan Model User
atau table users.

74
Langkah 3 - One to many (Inverse) Table Donations dan
Table Campaigns
Sekarang kita akan membuat relasi one-to-many (Inverse) antara table donations
dan table campaigns, dimana kita nanti bisa memanggil semua data donation
berdasarkan campaign di dalam website.

Silahkan buka file app/Models/Campaign.php kemudian ubah kode-nya menjadi seperti


berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Campaign extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'title', 'slug', 'category_id', 'target_donation', 'max_date',
'description', 'image', 'user_id',
];

/**
* category
*
* @return void
*/
public function category()
{
return $this->belongsTo(Category::class);
}
/**
* user
*
* @return void

75
*/
public function user()
{
return $this->belongsTo(User::class);
}

/**
* donations
*
* @return void
*/
public function donations()
{
return $this->hasMany(Donation::class);
}
}

Di atas, kita menambahkan satu method baru, yaitu donations. Dimana function tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model
Donation atau table donations.

Sekarang kita akan buat kebalikannya (Inverse), agar kita juga dapat mengakses data
campaign melalui data donation. SIlahkan buka file app/Models/Donation.php dan ubah
kode-nya menjadi seperti berikut ini :

76
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Donation extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice', 'campaign_id', 'donatur_id', 'amount', 'pray',
'status', 'snap_token'
];

/**
* campaign
*
* @return void
*/
public function campaign()
{
return $this->belongsTo(Campaign::class);
}

Di atas, kita menambahkan method campaign yang di fungsikan untuk mendeklarasikan


bahwa model Campaign atau table campaigns ini dimiliki dan terhubung dengan Model
Donation atau table donations.

Langkah 4 - One to many (Inverse) Table Donations dan


Table Donaturs
Terakhir, kita akan membuat relasi one-to-many (Inverse) antara table donations
dan table donaturs. Silahkan buka file app/Models/Donatur.php dan ubah kode-nya

77
menjadi seperti berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Donatur extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password', 'avatar'
];
/**
* hidden
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* donations
*
* @return void
*/
public function donations()
{
return $this->hasMany(Donation::class);
}
}

Di atas, kita menambahkan satu method baru, yaitu donations. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model
Donation atau table donations.

78
Sekarang kita akan membuat kebalikannya agar kita juga bisa memanggil data donatur dari
data donations. Silahkan buka file app/Models/Donation.php dan ubah kode-nya
menjadi seperti berikut ini :

79
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Donation extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice', 'campaign_id', 'donatur_id', 'amount', 'pray',
'status', 'snap_token'
];

/**
* campaign
*
* @return void
*/
public function campaign()
{
return $this->belongsTo(Campaign::class);
}

/**
* donatur
*
* @return void
*/
public function donatur()
{
return $this->belongsTo(Donatur::class);
}

Di atas, kita menambahkan method donatur yang di fungsikan untuk mendeklarasikan

80
bahwa model Donation atau table donations ini dimiliki dan terhubung dengan Model
Donatur atau table donaturs.

Langkah 5 - Membuat Method Sum Donation


Disini kita akan tambahkan satu method yang digunakan untuk melakukan penjumlahan
atau proses sum dari data donation di dalam sebuah campaign.

Silahkan buka file app/Models/Campaign.php, kemudian ubah kode-nya menjadi seperti


berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Campaign extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'title', 'slug', 'category_id', 'target_donation', 'max_date',
'description', 'image', 'user_id',
];

/**
* category
*
* @return void
*/
public function category()
{
return $this->belongsTo(Category::class);
}
/**
* user
*

81
* @return void
*/
public function user()
{
return $this->belongsTo(User::class);
}

/**
* donations
*
* @return void
*/
public function donations()
{
return $this->hasMany(Donation::class);
}

/**
* sumDonation
*
* @return void
*/
public function sumDonation()
{
return
$this->hasMany(Donation::class)->selectRaw('donations.campaign_id,SUM(do
nations.amount) as total')->where('donations.status',
'success')->groupBy('donations.campaign_id');
}
}

Di atas, kita menambahkan satu method yang bernama sumDonation, dimana di dalamnya
akan melakukan proses sum atau penjumalahan data dari attribute amount yang ada di
dalam table donations dengan kondisi attribute status bernilai success yang mana
data ini akan berhubungan dengan campaign.

82
Eloquent Mutators & Casting

Laravel 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.

Mendefinisikan sebuah Accessor


Accessor digunakan untuk melakukan manipulasi nilai attribute saat diakses, untuk
mendefinisikan sebuah accessor kurang lebih seperti berikut ini :

get{namaAttribute}Attribute

namaAttribute merupakan nama attribute yang akan di format dengan accessor,


misalnya kita ingin membuat attribute name saat diakses menjadi huruf besar atau
uppercase kita bisa mendefinisikan sebuah accessor di dalam Model kurang lebih seperti
berikut ini :

83
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model


{
/**
* Get name to Uppercase
*
* @param string $value
* @return string
*/
public function getNameAttribute($value)
{
return strtoupper($value);
}
}

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.

Mendefinisikan sebuah Mutator


Mutator digunakan untuk melakukan manipulasi nilai attribute saat di set, untuk
mendefinisikan mutator kurang lebih seperti berikut ini :

84
set{namaAttribute}Attribute

namaAttribute merupakan nama attribute yang akan di set menggunakan mutator,


misalnya kita ingin melakukan insert data dengan merubahnya menjadi huruf kecil atau
lowercase, kita bisa mendefinisikan sebuah mutator di dalam Model seperti berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model


{
/**
* Set the user's first name.
*
* @param string $value
* @return void
*/
public function setNameAttribute($value)
{
$this->attributes['name'] = strtolower($value);
}
}

Setiap kita melakukan insert data name ke dalam database, maka akan di format menjadi
huruf kecil semuanya.

Mendefinisikan sebuah Casting


Attribute Casting hampir memiliki fungsi yang sama dengan accessor dan mutator, tapi
kita tidak perlu melakukan definisi sebuah method di dalam Model. Disini kita hanya
mendefinisikan sebuah properti dengan nama $cart dan bisa melakukan konversi dari
sebuah attribute ke dalam tipe data umum, seperti :

array
boolean
collection
date
datetime

85
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;

class User extends Model


{
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}

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.

Langkah 1 - Membuat Accessor di Model Campaign


Sekarang kita akan belajar bagaimana cara membuat Accessor untuk melakukan manipulasi
data image untuk campaign, disini kita akan menambahkan full-path image campaign agar

86
memudahkan kita dalam pemanggilan gambar nantinya.

Silahkan buka file app/Models/Campaign.php, kemudian ubah kode-nya menjadi seperti


berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Campaign extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'title', 'slug', 'category_id', 'target_donation', 'max_date',
'description', 'image', 'user_id',
];

/**
* category
*
* @return void
*/
public function category()
{
return $this->belongsTo(Category::class);
}
/**
* user
*
* @return void
*/
public function user()
{
return $this->belongsTo(User::class);
}

87
/**
* donations
*
* @return void
*/
public function donations()
{
return $this->hasMany(Donation::class);
}

/**
* sumDonation
*
* @return void
*/
public function sumDonation()
{
return
$this->hasMany(Donation::class)->selectRaw('donations.campaign_id,SUM(do
nations.amount) as total')->where('donations.status',
'success')->groupBy('donations.campaign_id');
}

/**
* getImageAttribute
*
* @param mixed $image
* @return void
*/
public function getImageAttribute($image)
{
return asset('storage/campaigns/'.$image);
}
}

Di atas kita menambahkan 1 method baru, yaitu getImageAttribute, dimana di


dalamnya kita akan melakukan return atau mengembalikan ke dalam folder
storage/campaigns/. Jadi jika kita memanggil attribute image makan akan otomatis
menghasilkan output seperti berikut ini :

domain.com/storage/campaigns/nama_file_image.png

Tapi, jika kita tidak menggunakan fitur Accessor, maka hasilnya akan seperti berikut ini :

88
nama_file_image.png

Langkah 2 - Membuat Accessor di Model Category


Sekarang kita akan ulangi tahap yang sama dengan diatas, yaitu menambahkan sebuah
Accessor untuk memanipulasi data image category menjadi full-path, silahkan buka file
app/Models/Category.php, kemudian ubah kode-nya menjadi seperti berikut ini :

89
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Category extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'slug', 'image'
];

/**
* campaign
*
* @return void
*/
public function campaigns()
{
return $this->hasMany(Campaign::class);
}

/**
* getImageAttribute
*
* @param mixed $image
* @return void
*/
public function getImageAttribute($image)
{
return asset('storage/categories/'.$image);
}
}

Di atas kita menambahkan 1 method baru yang bernama getImageAttribute dan di


dalamnya akan melakukan return atau mengembalikan ke dalam folder

90
storage/categories.

Langkah 3 - Menambahkan Accessor di Model Donation


Sekarang kita akan belajar menambahkan lagi sebuah Accessor di dalam Model Donation,
kali ini kita akan melakukan manipulasi data untuk attribute created_at dan updated_at
menjadi format d-m-Y (date, month, year). Silahkan buka file app/Models/Donation.php
dan ubah kode-nya menjadi seperti berikut ini :

<?php

namespace App\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Donation extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice', 'campaign_id', 'donatur_id', 'amount', 'pray',
'status', 'snap_token'
];

/**
* campaign
*
* @return void
*/
public function campaign()
{
return $this->belongsTo(Campaign::class);
}

/**
* donatur
*

91
* @return void
*/
public function donatur()
{
return $this->belongsTo(Donatur::class);
}

/**
* getCreatedAtAttribute
*
* @param mixed $date
* @return void
*/
public function getCreatedAtAttribute($date)
{
return Carbon::parse($date)->format('d-M-Y');
}
/**
* getUpdatedAtAttribute
*
* @param mixed $date
* @return void
*/
public function getUpdatedAtAttribute($date)
{
return Carbon::parse($date)->format('d-M-Y');
}
}

Di atas kita menambahkan 2 method baru, yaitu getCreatedAtAtrribute dan


getUpdatedAtAttribute, kedua method ini akan kita gunakan untuk melakukan
manipulasi data attribute dari created_at dan updated_at menjadi format d-m-Y.

Langkah 4 - Membuat Accessor di Model Donatur


Sekarang kita akan belajar membuat Accessor untuk memanipulasi attribute avatar di
dalam Model Donatur, disini kita akan membuat sebuah kondisi, jika nilai dari attribute
avatar kosong/null, maka kita akan menampilkan sebuah avatar berdasarkan nama user
dengan bantuan API dari https://ui-avatar.com. Dan jika nilai attribute avatar ada isinya,
maka akan menampilkan image dari folder storage/donaturs.

Silahkan buka file app/Models/Donatur.php, kemudian ubah kode-nya menjadi seperti


berikut ini :

92
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Donatur extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password', 'avatar'
];
/**
* hidden
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* donations
*
* @return void
*/
public function donations()
{
return $this->hasMany(Donation::class);
}
/**
* getAvatarAttribute
*
* @param mixed $avatar
* @return void
*/
public function getAvatarAttribute($avatar)
{

93
if ($avatar != null) :
return asset('storage/donaturs/'.$avatar);
else :
return 'https://ui-avatars.com/api/?name=' . str_replace('
', '+', $this->name) . '&background=4e73df&color=ffffff&size=100';
endif;
}
}

Di atas kita menambahkan 1 method baru yang bernama getAvatarAttribute, dimana


di dalamnya akan melakukan sebuah pengecekan kondisi, jika nilai attribute avatar
bernilai null, maka akan menampilkan gambar dari API https://ui-avatars.com/ dan jika nilai
attribute avatar mempunnyai value/isi, maka akan mengambil gambar dari folder
storage/donaturs.

Langkah 5 - Membuat Accessor di Model Slider


Sekarang kita lanjutkan membuat Accessor untuk memanipulasi image slider, sama seperti
sebelumnya, disini kita akan menambahkan full-path saat memanggil attribute image di
table sliders.

SIlahkan buka file app/Models/Slider.php, kemudian ubah semua kode-nya menjadi


seperti berikut ini :

94
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Slider extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'image', 'link'
];

/**
* getImageAttribute
*
* @param mixed $image
* @return void
*/
public function getImageAttribute($image)
{
return asset('storage/sliders/'.$image);
}
}

Di atas, kita menambahkan 1 method baru, yaitu getImageAttribute, di dalam method


ini kita melakukan return/mengembalikan nilai attribute image dengan menambahkan full-
path folder ke storage/sliders.

Langkah 6 - Membuat Accessor di Model User


Terakhir, kita akan membuat Accessor untuk menampilkan attribute avatar dari user, disini
kita akan membuatnya mirip dengan Model Donatur, yaitu memberikan sebuah kondisi
tertentu. Silahkan buka file app/Models/User.php dan ubah kode-nya menjadi seperti
berikut ini :

95
<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable


{
use HasFactory, Notifiable;

/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
'avatar'
];

/**
* 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',
];
/**
* campaigns

96
*
* @return void
*/
public function campaigns()
{
return $this->hasMany(Campaign::class);
}
/**
* getAvatarAttribute
*
* @param mixed $avatar
* @return void
*/
public function getAvatarAttribute($avatar)
{
if ($avatar != null) :
return asset('storage/users/'.$avatar);
else :
return 'https://ui-avatars.com/api/?name=' . str_replace('
', '+', $this->name) . '&background=4e73df&color=ffffff&size=100';
endif;
}
}

Dari penambahan kode diatas, kita menambahkan 1 method baru, yaitu


getAvatarAttribute, dimana method ini digunakan untuk menampilkan attribute
avatar secara full-path jika attribute tersebut memiliki value, tapi jika attribute tersebut
bernilai null, maka akan menampilkan avatar dari API https://ui-avatars.com.

97
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.

Langkah 1 - Membuat Class Seeder User


Untuk membuat sebuah seeder kita bisa menggunakan perintah Artisan, yaitu
make:seeder dan diikuti dengan nama seeder yang ingin dibuat. Sekarang, silahkan
jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:seeder UserSeeder

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 :

98
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;

class UserSeeder extends Seeder


{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('users')->insert([
'name' => 'Fika Ridaul Maulayya',
'email' => 'admin@gmail.com',
'password' => Hash::make('password'),
]);
}
}

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.

99
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 :

php artisan db:seed --class=UserSeeder

Jika berhasil, maka kita bisa melihat ada data user baru di dalam table users. Kurang
lebih seperti berikut ini :

100
AUTHENTICATION - FORTIFY

101
Apa itu Laravel Fortify ?

Laravel Fortify is a frontend agnostic authentication backend implementation for Laravel

Laravel Fortify adalah implementasi backend otentikasi agnostik frontend untuk Laravel.
Fortify mendaftarkan route dan controller yang diperlukan untuk mengimplementasikan
semua fitur otentikasi Laravel, termasuk login, registrasi, pengaturan ulang kata sandi,
verifikasi email, dan banyak lagi.

Di dalam Laravel Fortify kita tidak disediakan sebuah user interface layaknya seperti
Laravel UI, Laravel Breeze dan Laravel Jetstream, disini kita benar-benar akan
membuat tampilan/user interface secara manual, mulai dari login, reset password, update
password dan yang lain-lain.

Setelah kita menginstall Laravel Fortify, kita dapat menjalankan perintah Artisan
route:list untuk melihat route yang di daftarkan oleh Laravel Fortify, disini kita bisa
manfaatkan route tersebut di dalam template nantinya.

Kapan Saya Harus Menggunakan Fortify?


Kita mungkin akan bertanya-tanya, kapan saat yang tepat untuk menggunakan Laravel
Fortify. Pertama, jika kita menggunakan salah satu starter-kit Laravel, maka kita tidak
perlu melakukan installasi Laravel Fortify, karena starter-kit Laravel sudah memiliki fitur
otentikasi secara lengkap.

Jika kita tidak menggunakan starter-kit Laravel di dalam aplikasi yang kita bangun, maka
kita memiliki 2 pilihan, yaitu membuat otentikasi secara manual atau menggunakan Laravel
Fortify. Jika kita memilih menggunakan Laravel Fortify, maka kita bisa membuat tampilan
sendiri dan melakukan request ke dalam route yang sudah disediakan oleh Laravel Fortify.

Fitur-fitur Fortify
File konfigurasi fortify berisi array fitur, array ini yang akan menentukan route/fitur yang
akan dieksekusi oleh Laravel Fortify secara default. Kita bisa melihat file konfigurasi ini di
dalam folder config/fortify.php, kurang lebih seperti berikut ini :

102
'features' => [
Features::registration(),
Features::resetPasswords(),
// Features::emailVerification(),
Features::updateProfileInformation(),
Features::updatePasswords(),
Features::twoFactorAuthentication([
'confirmPassword' => true,
]),
],

File konfigurasi diatas tersedia setelah kita berhasil melakukan proses installasi Laravel
Fortify.

Untuk melihat dokumentasi resminya, kita bisa melihatnya di halaman berikut ini :
https://laravel.com/docs/8.x/fortify
Dan untuk repository pengembangannya, bisa dilihat di halaman berikut ini :
https://github.com/laravel/fortify

103
Installasi dan Konfigurasi Laravel Fortify

Sekarang kita akan belajar bagaimana cara melakukan installasi dan konfigurasi Laravel
Fortify di dalam project website donasi yang kita kembangkan.

Langkah 1 - Installasi Laravel Fortify


Silahkan jalankan perintah di bawah ini di dalam terminal/CMD dan pastikan
menjalankannya berada di dalam folder project Laravel kita :

composer require laravel/fortify:1.7.7

Langkah 2 - Publish Konfigurasi


Setelah proses installasi Laravel Fortify selesai, sekarang kita lanjutkan untuk melakukan
proses publish file konfigurasi, silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan vendor:publish --


provider="Laravel\Fortify\FortifyServiceProvider"

104
Jika perintah diatas berhasil dijalankan, maka kita akan mendapatkan beberapa file
konfigurasi, diantaranya adalah :

app/Actions/Fortify/*
config/fortify.php
app/Providers/FortifyServiceProvider.php
database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.ph
p

Di dalam folder app/Actions/Fortify terdapat beberapa file yang akan digunakan untuk
proses Laravel Fortify, seperti registrasi. login, update password dan banyak lagi.

kemudian untuk file provider app/Providers/FortifyServiceProvider.php akan kita


gunakan untuk melakukan registrasi dan konfigurasi view untuk beberapa halaman, seperti
login, reset password, update password, two-factor authentication dan confirm password.

Dan untuk file config/fortify.php kita bisa melihat beberapa konfigurasi, seperti
mengaktifkan dan menon-aktifkan fitur-fitur yang ada di dalam Laravel Fortify dan masih
banyak lagi.

kemudian untuk yang file terakhir adalah migration, disini kita mendapatkan migration baru
dengan menambahkan 2 attribute ke dalam table users, yaitu :

two_factor_secret
two_factor_recovery_codes

Kedua attribute diatas akan difungsikan saat kita mengaktifkan fitur two-factor
authentication di dalam Laravel Fortify. Karena mendapatkan file migration baru, maka kita
harus menjalankan proses migrate lagi agar attribute diatas bisa ditambahkan ke dalam
table.

Silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan migrate

Jika berhasil, sekarang kita bisa lihat di dalam table users dan kita akan mendapatkan 2
attribute tambahan, kurang lebih seperti berikut ini :

105
Langkah 3 - Mengaktifkan View di Provider
Sekarang, kita akan menambahkan beberapa kode yang bertujuan untuk proses pengaktifan
beberapa halaman, silahkan buka file app/Providers/FortifyServiceProvider.php
dan pada function boot tambahkan kode berikut ini :

106
/**
* VIEW
*/

//login
Fortify::loginView(function () {
return view('auth.login');
});

//forgot
Fortify::requestPasswordResetLinkView(function () {
return view('auth.forgot-password');
});

//reset
Fortify::resetPasswordView(function ($request) {
return view('auth.reset-password', ['request' => $request]);
});

//confirm password
Fortify::confirmPasswordView(function () {
return view('auth.confirm-password');
});

//two factor authentication


Fortify::twoFactorChallengeView(function () {
return view('auth.two-factor-challenge');
});

Di atas kita menambahkan beberapa kode untuk melakukan load view, seperti login, forgot
password, resep password, confirm password dan two factor authentication. Dimana file-file
view tersebut akan kita buat nanti di dalam folder views/auth.

Dan jika file app/Providers/FortifyServiceProvider.php di tulis secara lengkap,


kurang lebih menjadi seperti berikut ini :

<?php

namespace App\Providers;

use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use App\Actions\Fortify\UpdateUserPassword;

107
use App\Actions\Fortify\UpdateUserProfileInformation;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Fortify;

class FortifyServiceProvider extends ServiceProvider


{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}

/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Fortify::createUsersUsing(CreateNewUser::class);
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation:
:class);
Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);

RateLimiter::for('login', function (Request $request) {


return
Limit::perMinute(5)->by($request->email.$request->ip());
});

RateLimiter::for('two-factor', function (Request $request) {


return
Limit::perMinute(5)->by($request->session()->get('login.id'));
});

/**
* VIEW
*/

108
//login
Fortify::loginView(function () {
return view('auth.login');
});

//forgot
Fortify::requestPasswordResetLinkView(function () {
return view('auth.forgot-password');
});

//reset
Fortify::resetPasswordView(function ($request) {
return view('auth.reset-password', ['request' => $request]);
});

//confirm password
Fortify::confirmPasswordView(function () {
return view('auth.confirm-password');
});

//two factor authentication


Fortify::twoFactorChallengeView(function () {
return view('auth.two-factor-challenge');
});
}
}

Langkah 4 - Register Fortify Service Provider


Sekarang, kita akan lakukan registrasi Laravel Fortify di dalam Providers Laravel, silahkan
buka file config/app.php kemudian tambahkan kode berikut ini di dalam array
providers.

'providers' => [
...
App\Providers\FortifyServiceProvider::class,
];

109
Langkah 6 - Menambahkan Properti Hidden
Terakhir, kita akan menambahkan 2 attribute yaitu two_factor_secret dan
two_factor_recovery_codes ke dalam properti $hidden di dalam Model User, ini
digunakan agar ketika kita melakukan get data user, maka attribute tersebut tidak ikut di
tampilkan, karena jika ikut ditampilkan, maka bisa berakibat fatal untuk sisi keamanan.

Silahkan buka file app/Models/User.php, kemudian cari kode berikut ini :

protected $hidden = [
'password',
'remember_token',
];

Dan ubah menjadi seperti berikut ini :

protected $hidden = [
'password',
'remember_token',
'two_factor_secret',
'two_factor_recovery_codes'
];

Di atas, kita menambahkan 2 attribute tersebut di dalam properti $hidden.

110
Membuat Proses Login

Setelah berhasil melakukan installasi dan konfigurasi Laravel Fortify, sekarang kita lanjutkan
belajar bagaimana cara membuat proses login menggunakan Laravel Fortify. Disini kita
tidak akan membuat sebuah route dan controller baru, karena semuanya sudah dibuatkan
secara otomatis ketika kita melakukan installasi Laravel Fortify. Dan kita hanya perlu fokus
di dalam tampilan (user interface).

Langkah 1 - Membuat Layout Auth


Pertama kita akan belajar membuat layout terlebih dahulu, layout digunakan sebagai induk
template dari semua halaman otentikasi. Silahkan buat folder baru dengan nama layouts
di dalam folder resources/views dan di dalam folder layouts, silahkan buat file baru
dengan nama auth.blade.php dan masukkan kode berikut ini :

111
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-
scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="referrer" content="always">
<link rel="canonical" href="/login">
<link rel="shortcut icon" type="image/jpg"
href="https://i.imgur.com/UyXqJLi.png" />
<title>{{ $title }}</title>
<!-- CSS -->
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link
href="https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;50
0;600;700&display=swap" rel="stylesheet">
<!-- JS -->
<script src="{{ asset('js/app.js') }}"></script>
</head>
<body>
<!-- content -->
@yield('content')
</body>
</html>

Di atas, jika teman-teman perhatikan, kita memanggil beberapa file CSS dan JavaScript
yang berada di dalam folder public. Dimana file-file ini merupakan hasil compile dari
Tailwind CSS.

Di atas kita menggunakan sintaks blade untuk menampilkan content dengan


@yield('content'). Dimana sintaks tersebut akan otomatis mencari sebuah blade
section dengan nama content.

Langkah 2 - Membuat View/Halaman Login


Setelah diatas kita berhasil membuat layout, maka sekarang kita akan lanjutkan untuk
membuat view/halaman untuk login. Silahkan buat folder baru dengan nama auth di dalam
folder resources/views dan di dalam folder auth silahkan buat file baru dengan nama
login.blade.php dan masukkan kode berikut ini :

112
@extends('layouts.auth', ['title' => 'Login - Admin'])

@section('content')
<div class="flex justify-center items-center h-screen bg-gray-300 px-6">
<div class="p-6 max-w-sm w-full bg-white shadow-md rounded-md">
<div class="flex justify-center items-center">
<span class="text-gray-700 font-semibold
text-2xl">LOGIN</span>
</div>
@if (session('status'))
<div class="bg-green-500 p-3 rounded-md shadow-sm mt-3">
{{ session('status') }}
</div>
@endif
<form class="mt-4" action="{{ route('login') }}" method="POST">
@csrf
<label class="block">
<span class="text-gray-700 text-sm">Email</span>
<input type="email" name="email" value="{{ old('email')
}}"
class="form-input mt-1 block w-full rounded-md
focus:outline-none" placeholder="Alamat Email">
@error('email')
<div class="inline-flex max-w-sm w-full bg-red-200
shadow-sm rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{ $message
}}</p>
</div>
</div>
@enderror
</label>

<label class="block mt-3">


<span class="text-gray-700 text-sm">Password</span>
<input type="password" name="password" class="form-input
mt-1 block w-full rounded-md"
placeholder="Password">
@error('password')
<div class="inline-flex max-w-sm w-full bg-red-200
shadow-sm rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{ $message
}}</p>
</div>

113
</div>
@enderror
</label>

<div class="flex justify-between items-center mt-4">


<div>
<label class="inline-flex items-center">
<input type="checkbox" class="form-checkbox
text-indigo-600">
<span class="mx-2 text-gray-600 text-
sm">Ingatkan Saya</span>
</label>
</div>

<div>
<a class="block text-sm fontme text-indigo-700
hover:underline" href="/forgot-password">Lupa
Password?</a>
</div>
</div>

<div class="mt-6">
<button type="submit"
class="py-2 px-4 text-center bg-indigo-600 rounded-
md w-full text-white text-sm hover:bg-indigo-500 focus:outline-none">
LOGIN
</button>
</div>
</form>
</div>
</div>
@endsection

Di atas, pertama kita atur agar halaman ini menggunakan layout dari auth dan di dalamnya
kita buat array yang bernama title dan memiliki value Login - Admin, array ini akan di
tampilkan di layout untuk title dari website.

@extends('layouts.auth', ['title' => 'Login - Admin'])

Dan untuk memanggil array tersebut di dalam layout, kita bisa perhatikan di dalam file
layout resources/views/layouts/auth.blade.php di bagian kode berikut ini :

114
<title>{{ $title }}</title>

Dan untuk view login kita letakkan di antara sintaks blade @section('content') dan
@endsection yang mana akan di render pada layout auth menggunakan
yield('content').

Kemudian, pada form action login, kita arahakn ke dalam route yang bernama login.
Jika kita cek route tersebut di dalam file routes/web.php, maka kita tidak akan
menemukannya, karena route tersebut berada di dalam folder vendor dari Laravel Fortify.

Langkah 3 - Uji Coba Proses Login


Jika kita belum menjalankan project Laravel-nya, silahkan jalankan perintah berikut ini :

php artisan serve

Dan kita bisa mencoba mengakses halaman login di http://localhost:8000/login dan jika
berhasil, maka kurang lebih tampilannya seperti berikut ini :

Sekarang ketika kita mencoba melakukan klik button LOGIN tanpa mengisi papaun di dalam
input, maka kita akan mendapatkan error validasi seperti berikut ini :

115
Kemudian kita coba untuk memasukkan kredensial email dan password yang belum
terdaftar di dalam database, maka kita akan mendapatkan error login failed, kurang lebih
seperti berikut ini :

Dan terakhir, kita akan uji coba untuk memasukkan kredensial email dan password yang
sudah terdaftar di dalam database dan jika berhasil melakukan otentikasi, maka kita akan
mendapatkan tampilan seperti berikut ini :

116
Diatas, setelah proses otentikasi berhasil, kita diarahkan ke dalam url /home, dan url
tersebut memang belum tersedia di dalam route website kita, oleh sebab itu akan
menghasilkan error 404 | NOT FOUND.

Disini kita akan melakukan sedikit perubahan, jika proses otentikasi berhasil, maka akan kita
arahkan ke dalam url /admin/dashboard. Silahkan buka file
app/Providers/RouteServiceProvider.php kemudian cari kode berikut ini :

public const HOME = '/home';

Dan ubah menjadi seperti berikut ini :

public const HOME = '/admin/dashboard';

Dari perubahan kode diatas, kita melakukan routing jika proses otentikasi berhasil, maka
akan di arahkan ke dalam url /admin/dashboard. Dan untuk halaman ini akan kita buat di
tahap selanjutnya.

117
Membuat Proses Forgot dan Reset Password

Setelah berhasil membuat proses otentikasi, maka sekarang kita akan lanjutkan belajar
bagaimana cara membuat proses forgot dan reset password menggunakan Laravel Fortify.
Untuk mengirim email reset password kita akan menggunakan SMTP dari Mailtrap. Karena
masih di dalam mode development, maka kita cukup menggunakan Mailtrap untuk
melakukan debug email reset paswsord-nya.

Jika website/aplikasi kita sudah dalam mode production (online), kita bisa menggunakan
layanan SMTP lain, seperti :

Mailchimp
Sendgrid
AWS SES
Dan lain-lain.

Langkah 1 - Daftar Akun Mailtrap


Untuk mendapatkan SMTP dari Mailtrap, kita diharuskan melakukan registrasi di dalam
website resmi Mailtrap, silahkan buka link berikut ini dan silahkan lakukan registrasi :
https://mailtrap.io/register/signup.

Setelah berhasil melakukan registrasi, silahkan masuk ke Demo Inbox dan disini kita sudah
mendapatkan kredensial untuk SMTP. Kurang lebih seperti ini :

Dari gambar diatas, kita fokus pada bagian SMTP dan adapun kredensial yang akan kita

118
gunakan adalah :

Host
Port
Username
Password

Langkah 2 - Konfigurasi SMTP di Laravel


Setelah berhasil melakukan registrasi dan mendapatkan kredensial SMTP dari Mailtrap,
maka sekarang kita lanjutkan untuk melakukan konfigurasi di dalam website kita. Silahkan
buka file .env dan cari kode berikut ini :

MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"

Kemudian ubah menjadi seperti berikut ini :

MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="admin@donasi-online.com"
MAIL_FROM_NAME="${APP_NAME}"

Pada bagian MAIL_USERNAME silahkan diisi dengan username yang di dapatkan dari
Mailtrap. dan untuk MAIL_PASSWORD silaahkan diisi juga dengan password yang di
dapatkan dari Mailtrap. Kemudian MAIL_FROM_ADDRESS bisa diisi dengan alamat email
apapun, ini digunakan sebagai pengirim emailnya.

CATATAN!: ketika merubah isi dari file .env, kita diharuskan untuk melakukan restart
server Laravel

119
Langkah 3 - Membuat View/Halaman Forgot Password
Setelah melakukan konfigurasi SMTP di dalam Laravel, sekarang kita akan lanjutkan untuk
membuat view untuk menampilkan halaman forgot password. SIlahkan buat file baru
dengan nama forgot-password.blade.php di dalam folder resources/views/auth
dan masukkan kode berikut ini :

@extends('layouts.auth', ['title' => 'Forgot Password - Admin'])

@section('content')
<div class="flex justify-center items-center h-screen bg-gray-300 px-6">
<div class="p-6 max-w-sm w-full bg-white shadow-md rounded-md">
<div class="flex justify-center items-center">
<span class="text-gray-700 font-semibold text-2xl">RESET
PASSWORD</span>
</div>
@if (session('status'))
<div class="bg-green-500 p-3 rounded-md shadow-sm mt-3">
{{ session('status') }}
</div>
@endif
<form class="mt-4" action="{{ route('password.email') }}"
method="POST">
@csrf
<label class="block">
<span class="text-gray-700 text-sm">Email</span>
<input type="email" name="email" value="{{ old('email')
}}"
class="form-input mt-1 block w-full rounded-md
focus:border-indigo-600" placeholder="Alamat Email">
@error('email')
<div class="inline-flex max-w-sm w-full bg-red-200
shadow-sm rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{ $message
}}</p>
</div>
</div>
@enderror
</label>

<div class="mt-6">
<button type="submit"
class="py-2 px-4 text-center bg-indigo-600 rounded-

120
md w-full text-white text-sm hover:bg-indigo-500 focus:outline-none">
SEND PASSWORD RESET LINK
</button>
</div>
</form>
</div>
</div>
@endsection

Di atas, pertama kita atur agar halaman ini menggunakan layout dari auth dan di dalamnya
kita buat array yang bernama title dan memiliki value Forgot Password - Admin,
array ini akan di tampilkan di layout untuk title dari website.

Dan pada bagian kode berikut ini digunakan untuk menampilkan beberapa status, seperti
email berhasil terkirim, email gagal terkirim dan lain-lain.

@if (session('status'))
<div class="bg-green-500 p-3 rounded-md shadow-sm mt-3">
{{ session('status') }}
</div>
@endif

Untuk melakukan proses forgot password, pada bagian form action kita arahkan ke
sebuah route yang bernama password.email dan jika kita telusuri di dalam file route
route/web.php, maka kita tidak akan pernah menemukan definisi dari route tersebut.
Karena route tersebut berada di dalam folder vendor dari Laravel Fortify.

<form class="mt-4" action="{{ route('password.email') }}" method="POST">

Langkah 4 - Uji Coba Proses Forgot Password


Sekarang kita akan lakukan uji coba untuk proses forgot password dan konsepnya disini
ketika seorang user lupa passwordnya dan ingin memperbarui dengan password baru, maka
user tersebut akan membuat permintaan update password yang dikirim melalui email.

Silahkan buka halaman : http://localhost:8000/forgot-password dan jika kita diarahkan ke


url/halaman /admin/dashboard, maka silahkan bisa membuka link tersebut di dalam
browser lain atau dengan mode samaran, karena sebelumnya kita sudah melakukan proses
otentikasi dan kemungkinan session-nya masih tersimpan.

121
Jika halaman berhasil dibuka, kita akan mendapatkan kurang lebih tampilannya seperti
berikut ini :

Sekarang jika kita coba klik button SEND PASSWORD RESET LINK tanpa mengisi input
apapun, maka kita akan mendapatkan error validasi, kurang lebih seperti berikut ini :

Dan jika pada input email kita isi dengan email yang belum terdaftar di dalam database,
maka kita akan mendapatkaan error not found email, kurang lebih seperti berikut ini :

122
Kemudian, jika pada input email kita masukkan email yang terdaftar di table users, maka
kita akan mendapatkan status sukses, kurang lebih seperti berikut ini :

Dan sekarang kita cek di dalam Mailtrap, untuk link untuk melakukan proses update
password, kurang lebih seperti berikut ini :

123
Silahkan klik button Reset Password yang ada di dalam email dan kita akan mendapatkan
sebuah error kurang lebih seperti berikut ini :

Error diatas karena kita belum membuat sebuah view untuk menampilkan halaman
reset/update password.

Langkah 5 - Membuat View/Halaman Reset Password


Sekarang kita lanjutkan untuk membuat view untuk menampilkan halaman reset password,
silahkan buat file baru dengan nama reset-password.blade.php di dalam folder

124
resources/views/auth dan masukkan kode berikut ini :

@extends('layouts.auth', ['title' => 'Update Password - Admin'])

@section('content')
<div class="flex justify-center items-center h-screen bg-gray-300 px-6">
<div class="p-6 max-w-sm w-full bg-white shadow-md rounded-md">
<div class="flex justify-center items-center">
<span class="text-gray-700 font-semibold text-2xl">UPDATE
PASSWORD</span>
</div>
@if (session('status'))
<div class="bg-green-500 p-3 rounded-md shadow-sm mt-3">
{{ session('status') }}
</div>
@endif
<form class="mt-4" action="{{ route('password.update') }}"
method="POST">
@csrf
<input type="hidden" name="token" value="{{
$request->route('token') }}">
<label class="block">
<span class="text-gray-700 text-sm">Email</span>
<input type="email" name="email" value="{{
$request->email ?? old('email') }}"
class="form-input mt-1 block w-full rounded-md
focus:border-indigo-600" placeholder="Alamat Email">
@error('email')
<div class="inline-flex max-w-sm w-full bg-red-200
shadow-sm rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{ $message
}}</p>
</div>
</div>
@enderror
</label>

<label class="block mt-3">


<span class="text-gray-700 text-sm">Password</span>
<input type="password" name="password"
class="form-input mt-1 block w-full rounded-md
focus:border-indigo-600" placeholder="Password">
@error('password')
<div class="inline-flex max-w-sm w-full bg-red-200

125
shadow-sm rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{ $message
}}</p>
</div>
</div>
@enderror
</label>

<label class="block mt-3">


<span class="text-gray-700 text-sm">Konfirmasi
Password</span>
<input type="password" name="password_confirmation"
class="form-input mt-1 block w-full rounded-md
focus:border-indigo-600"
placeholder="Konfirmasi Password">
@error('password')
<div class="inline-flex max-w-sm w-full bg-red-200
shadow-sm rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{ $message
}}</p>
</div>
</div>
@enderror
</label>

<div class="mt-6">
<button type="submit"
class="py-2 px-4 text-center bg-indigo-600 rounded-
md w-full text-white text-sm hover:bg-indigo-500 focus:outline-none">
UPDATE PASSWORD
</button>
</div>
</form>
</div>
</div>
@endsection

Di atas, pertama kita atur agar halaman ini menggunakan layout dari auth dan di dalamnya
kita buat array yang bernama title dan memiliki value Update Password - Admin,
array ini akan di tampilkan di layout untuk title dari website.

Dan pada bagian kode berikut ini digunakan untuk menampilkan beberapa status, seperti
password berhasil diupdate, password gagal diupdate dan lain-lain.

126
@if (session('status'))
<div class="bg-green-500 p-3 rounded-md shadow-sm mt-3">
{{ session('status') }}
</div>
@endif

Dan pada form action kita arahkan ke dalam route yang bernama password.update,
sama seperti sebelumnya, jika kita lihat di dalam file route, maka kita tidak akan
mendapatkan definisi dari route ini, karena route ini di simpan di dalam folder vendor dari
Laravel Fortify.

Langkah 6 - Proses Update Password


Sekarang kita bisa klik lagi button Reset Password yang ada di dalam email Mailtrap dan
jika berhasil kita akan mendapatkan tampilan kurang lebih seperti berikut ini :

Sekarang, silahkan masukkan password dan konfirmasi password dan jika berhasil
maka akan di arahkan ke halaman login dengan pesan sukses update password, kurang
lebih seperti berikut ini :

127
128
HALAMAN ADMIN - BACKEND

129
Membuat Halaman Dashboard

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat dan menampilkan
halaman dashboard admin, di dalam halaman dashboard nantinya akan kita tampilkan
beberapa infomasi, seperti jumlah donatur, campaign dan jumlah donasi yang sudah
terkumpul.

Langkah 1 - Membuat Layout Admin


Sebelum kita lanjutkan membuat view untuk menampilkan halaman dashboard, maka kita
harus membuat layout terlebih dahulu, ini digunakan sebagai induk dari view-view yang ada
di halaman admin nantinya.

Silahkan buat file baru dengan nama app.blade.php di dalam folder


resources/views/layouts dan masukkan kode berikut ini :

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1,
shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<link rel="shortcut icon" type="image/jpg"
href="https://i.imgur.com/UyXqJLi.png" />
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ $title }}</title>
<!-- css -->
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link
href="https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;50
0;600;700&display=swap" rel="stylesheet">
<!-- js -->
<script
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js">
</script>
<script
src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.8.1/dist/alpine.min.
js" defer></script>
<script src="{{ asset('assets/js/main.js') }}"></script>

130
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@10"></script>
</head>

<body>
<div x-data="{ sidebarOpen: false }" class="flex h-screen bg-
gray-200 font-roboto">
<div :class="sidebarOpen ? 'block' : 'hidden'"
@click="sidebarOpen = false"
class="fixed z-20 inset-0 bg-black opacity-50 transition-
opacity lg:hidden"></div>

<div :class="sidebarOpen ? 'translate-x-0 ease-out' : '-


translate-x-full ease-in'"
class="fixed z-30 inset-y-0 left-0 w-64 transition
duration-300 transform bg-gray-900 overflow-y-auto lg:translate-x-0
lg:static lg:inset-0">
<div class="flex items-center justify-center mt-4">
<div class="flex items-center">
<svg class="h-12 w-12" viewBox="0 0 512 512"
fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M364.61 390.213C304.625 450.196 207.37
450.196 147.386 390.213C117.394 360.22 102.398 320.911 102.398
281.6C102.398 242.291 117.394 202.981 147.386 172.989C147.386 230.4
153.6 281.6 230.4 307.2C230.4 256 256 102.4 294.4 76.7999C320 128
334.618 142.997 364.608 172.989C394.601 202.981 409.597 242.291 409.597
281.6C409.597 320.911 394.601 360.22 364.61 390.213Z"
fill="#4C51BF" stroke="#4C51BF" stroke-
width="2" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M201.694 387.105C231.686 417.098 280.312
417.098 310.305 387.105C325.301 372.109 332.8 352.456 332.8 332.8C332.8
313.144 325.301 293.491 310.305 278.495C295.309 263.498 288 256 275.2
230.4C256 243.2 243.201 320 243.201 345.6C201.694 345.6 179.2 332.8
179.2 332.8C179.2 352.456 186.698 372.109 201.694 387.105Z"
fill="white" />
</svg>
<span class="text-white text-2xl mx-2 font-
semibold">DASHBOARD</span>
</div>
</div>

<hr>

<nav class="mt-5">

131
<a class="flex items-center mt-4 py-2 px-6 hover:bg-
opacity-25 hover:text-gray-100 {{ Request::is('admin/dashboard*') ? '
bg-gray-700 bg-opacity-25 text-gray-100' : 'text-gray-500' }}"
href="{{ route('admin.dashboard.index') }}">
<svg class="w-6 h-6" fill="none"
stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-
linejoin="round" stroke-width="2"
d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0
01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2
2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2
0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0
01-2-2v-2z">
</path>
</svg>

<span class="mx-3">Dashboard</span>
</a>

<a class="flex items-center mt-4 py-2 px-6 hover:bg-


gray-700 hover:bg-opacity-25 hover:text-gray-100 {{
Request::is('admin/category*') ? ' bg-gray-700 bg-opacity-25 text-
gray-100' : 'text-gray-500' }}"
href="#">
<svg class="w-6 h-6" fill="none"
stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-
linejoin="round" stroke-width="2"
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2
0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path>
</svg>

<span class="mx-3">Kategori</span>
</a>

<a class="flex items-center mt-4 py-2 px-6 hover:bg-


gray-700 hover:bg-opacity-25 hover:text-gray-100 {{
Request::is('admin/campaign*') ? ' bg-gray-700 bg-opacity-25 text-
gray-100' : 'text-gray-500' }}"
href="#">
<svg class="w-6 h-6" fill="none"
stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-

132
linejoin="round" stroke-width="2"
d="M3 21v-4m0 0V5a2 2 0 012-2h6.5l1 1H21l-3
6 3 6h-8.5l-1-1H5a2 2 0 00-2 2zm9-13.5V9">
</path>
</svg>

<span class="mx-3">Campaigns</span>
</a>

<a class="flex items-center mt-4 py-2 px-6 hover:bg-


gray-700 hover:bg-opacity-25 hover:text-gray-100 {{
Request::is('admin/donatur*') ? ' bg-gray-700 bg-opacity-25 text-
gray-100' : 'text-gray-500' }}"
href="#">
<svg class="w-6 h-6" fill="none"
stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-
linejoin="round" stroke-width="2"
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6
0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z">
</path>
</svg>

<span class="mx-3">Donaturs</span>
</a>

<a class="flex items-center mt-4 py-2 px-6 hover:bg-


gray-700 hover:bg-opacity-25 hover:text-gray-100 {{
Request::is('admin/donation*') ? ' bg-gray-700 bg-opacity-25 text-
gray-100' : 'text-gray-500' }}"
href="#">
<svg class="w-6 h-6" fill="none"
stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-
linejoin="round" stroke-width="2"
d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path>
</svg>

<span class="mx-3">Donation</span>
</a>

<a class="flex items-center mt-4 py-2 px-6 hover:bg-


gray-700 hover:bg-opacity-25 hover:text-gray-100 {{
Request::is('admin/profile*') ? ' bg-gray-700 bg-opacity-25 text-

133
gray-100' : 'text-gray-500' }}"
href="#">
<svg class="w-6 h-6" fill="none"
stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-
linejoin="round" stroke-width="2"
d="M5.121 17.804A13.937 13.937 0 0112 16c2.5
0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18
0 9 9 0 0118 0z">
</path>
</svg>

<span class="mx-3">Profil Saya</span>


</a>

<a class="flex items-center mt-4 py-2 px-6 hover:bg-


gray-700 hover:bg-opacity-25 hover:text-gray-100 {{
Request::is('admin/slider*') ? ' bg-gray-700 bg-opacity-25 text-
gray-100' : 'text-gray-500' }}"
href="#">
<svg class="w-6 h-6" fill="none"
stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-
linejoin="round" stroke-width="2"
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5
17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z">
</path>
</svg>

<span class="mx-3">Sliders</span>
</a>
</nav>
</div>
<div class="flex-1 flex flex-col overflow-hidden">
<header class="flex justify-between items-center py-4 px-6
bg-white">
<div class="flex items-center">
<button @click="sidebarOpen = true" class="text-
gray-500 focus:outline-none lg:hidden">
<svg class="h-6 w-6" viewBox="0 0 24 24"
fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 6H20M4 12H20M4 18H11"
stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-

134
linejoin="round" />
</svg>
</button>
</div>

<div class="flex items-center">

<div x-data="{ dropdownOpen: false }"


class="relative">
<button @click="dropdownOpen = ! dropdownOpen"
class="relative block h-8 w-8 rounded-full
overflow-hidden shadow focus:outline-none">
<img class="h-full w-full object-cover"
src="{{ auth()->user()->avatar }}">
</button>

<div x-show="dropdownOpen" @click="dropdownOpen


= false"
class="fixed inset-0 h-full w-full
z-10"></div>

<div x-show="dropdownOpen"
class="absolute right-0 mt-2 w-48 bg-white
rounded-md overflow-hidden shadow-sm z-10">
<div class="block px-4 py-2 text-sm text-
gray-700">
{{ auth()->user()->name }}
</div>
<hr>
<a href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();"
class="block px-4 py-2 text-sm text-
gray-700 hover:bg-indigo-600 hover:text-white">Logout</a>
<form id="logout-form" action="{{
route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
</div>
</div>
</div>
</header>

@yield('content')

</div>

135
</div>

<script>
@if(session()->has('success'))

Swal.fire({
icon: 'success',
title: 'BERHASIL!',
text: '{{ session('success') }}',
showConfirmButton: false,
timer: 3000
})

@elseif(session()->has('error'))

Swal.fire({
icon: 'error',
text: 'GAGAL!',
title: '{{ session('error') }}',
showConfirmButton: false,
timer: 3000
})

@endif
</script>
</body>
</html>

Langkah 2 - Membuat Controller Dashboard


Sekarang kita lanjutkan untuk membuat controller dashboard, dan untuk mempermudah
dalam maintenance maka semua controller yang berhubungan dengan admin, akan di
tempatkan di dalam folder Admin. Jadi semua controller admin nantinya berada di dalam
folder app/Http/Controllers/Admin.

Silahkan jalankan perintah dibawah ini di terminal/CMD untuk membuat controller


dashboard :

php artisan make:controller Admin\\DashboardController

Jika perintah diatas berhasil, maka kita akan mendapatkan 1 file controller baru di dalam

136
folder app/Http/Controllers/Admin/, yaitu DashboardController.php dan silahkan
buka file tersebut dan ubah kode-nya menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Campaign;
use App\Models\Donation;
use App\Models\Donatur;

class DashboardController extends Controller


{
/**
* index
*
* @return void
*/
public function index()
{

//donatur
$donaturs = Donatur::count();

//campaign
$campaigns = Campaign::count();

//donations
$donations = Donation::where('status',
'success')->sum('amount');

return view('admin.dashboard.index', compact('donaturs',


'campaigns', 'donations'));
}
}

Di atas, pertama kita melakukan import 3 Model, yaitu Campaign, Donation dan Donatur,
karena kita nanti akan melakukan count dan sum dari ke 3 Model tersebut.

137
use App\Models\Campaign;
use App\Models\Donation;
use App\Models\Donatur;

Kemudian, di dalam controller dashboard kita menambahkan 1 method baru, yaitu index.
Dimana di dalamnya kita melakukan beberapa kondisi, antara lain :

melakukan count/menghitung jumlah donatur

//donatur
$donaturs = Donatur::count();

melakukan count/menghitung jumlah campaign

//campaign
$campaigns = Campaign::count();

melakukan sum/menjumlah donasi yang masuk, berdasarkan status yang sukses

//donations
$donations = Donation::where('status', 'success')->sum('amount');

Setelah itu, kita parsing variable-variable di atas ke dalam view admin.dashboard.index


menggunakan helper compact.

return view('admin.dashboard.index', compact('donaturs', 'campaigns',


'donations'));

138
Langkah 3 - Membuat Route Dashboard
Setelah berhasil membuat controller, sekarang kita lanjutkan membuat route agar halaman
dashboard dapat di akses melalui url di browser. Silahkan buka file routes/web.php,
kemudian ubah semua kode-nya menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Admin;

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
return view('auth.login');
});

/**
* route for admin
*/

//group route with prefix "admin"


Route::prefix('admin')->group(function () {

//group route with middleware "auth"


Route::group(['middleware' => 'auth'], function() {

//route dashboard
Route::get('/dashboard', [DashboardController::class,
'index'])->name('admin.dashboard.index');

});
});

Di atas, pertama kita atur namespace untuk folder controller berada, disini kita arahkan ke
dalam folder App\Http\Controllers\Admin.

Kemudian, kita buat sebuah route dengan prefix admin. Prefix ini digunakan untuk
menambahkan katak admin di awalan URL. Jadi jika kita atur sebuah route dengan
/dashboard, maka kita akan otomatis mendapatkan hasilnya menjadi
/admin/dashboard. Karena awalan URL akan di tambahkan kata admin.

139
//group route with prefix "admin"
Route::prefix('admin')->group(function () {

//...

});

kemudian, kita lettakan semua route di dalam group dengan middleware auth. Dimana
route tersebut hanya bisa di akses jika sudah melakukan proses otentikasi atau login.

//group route with middleware "auth"


Route::group(['middleware' => 'auth'], function() {
//...
});

Dan untuk route dashboard, kurang lebih seperti berikut ini :

//route dashboard
Route::get('/dashboard', [DashboardController::class,
'index'])->name('admin.dashboard.index');

Di dalam pendefinisian route dashboard diatas, kita tambahkan name, yaitu


admin.dashboard.index, ini bertujuan agar kita lebih mudah dalam pemanggilan route
di dalam template/view nantinya.

Langkah 4 - Membuat View/Halaman Dashboard


Setelah berhasil membuat controller dan juga route, sekarang kita lanjutkan untuk membuat
view untuk menampilkan halaman dashboard. Silahkan buat folder baru dengan nama
admin di dalam folder resources/views. Setelah itu, silahkan buat folder lagi dengan
nama dashboard di dalam folder resources/views/admin dan terakhir silahkan buat file
baru di dalam folder dashboard dengan nama index.ablde.php dan masukkan kode
berikut ini :

@extends('layouts.app', ['title' => 'Dashboard - Admin'])

@section('content')
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-300">

140
<div class="container mx-auto px-6 py-8">

<div class="mt-4">
<div class="flex flex-wrap -mx-6">
<div class="w-full px-6 sm:w-1/2 xl:w-1/3">
<div class="flex items-center px-5 py-6 shadow-sm
rounded-md bg-white">
<div class="p-3 rounded-full bg-indigo-600 bg-
opacity-75">
<svg class="w-8 h-8 text-white" fill="none"
stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-
linejoin="round" stroke-width="2"
d="M17 20h5v-2a3 3 0
00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3
3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0
019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7
10a2 2 0 11-4 0 2 2 0 014 0z">
</path>
</svg>

</div>

<div class="mx-5">
<h4 class="text-2xl font-semibold text-
gray-700">{{ $donaturs }}</h4>
<div class="text-gray-500">DONATURS</div>
</div>
</div>
</div>

<div class="w-full mt-6 px-6 sm:w-1/2 xl:w-1/3 sm:mt-0">


<div class="flex items-center px-5 py-6 shadow-sm
rounded-md bg-white">
<div class="p-3 rounded-full bg-green-600 bg-
opacity-75">
<svg class="w-8 h-8 text-white" fill="none"
stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-
linejoin="round" stroke-width="2" d="M3 21v-4m0 0V5a2 2 0 012-2h6.5l1
1H21l-3 6 3 6h-8.5l-1-1H5a2 2 0 00-2 2zm9-13.5V9"></path></svg>
</div>

<div class="mx-5">

141
<h4 class="text-2xl font-semibold text-
gray-700">{{ $campaigns }}</h4>
<div class="text-gray-500">CAMPAIGNS</div>
</div>
</div>
</div>

<div class="w-full mt-6 px-6 sm:w-1/2 xl:w-1/3 xl:mt-0">


<div class="flex items-center px-5 py-6 shadow-sm
rounded-md bg-white">
<div class="p-3 rounded-full bg-pink-600 bg-
opacity-75">
<svg class="w-8 h-8 text-white" fill="none"
stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-
linejoin="round" stroke-width="2"
d="M13 7h8m0 0v8m0-8l-8 8-4-4-6
6"></path>
</svg>
</div>

<div class="mx-5">
<h4 class="text-2xl font-semibold text-
gray-700">{{ moneyFormat($donations) }}</h4>
<div class="text-gray-500">DONATIONS</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
@endsection

Di atas, pertama kita atur agar halaman ini menggunakan layout dari app dan di dalamnya
kita buat array yang bernama title dan memiliki value Dashboard - Admin, array ini
akan di tampilkan di layout untuk title dari website.

@extends('layouts.app', ['title' => 'Dashboard - Admin'])

Kemudian untuk menampilkan jumlah donatur, campaign dan donasi, kita bisa memanggil
variable yang sudah kita definisikan di dalam controller sebelumnya, kurang lebih seperti

142
berikut ini :

count data donatur

{{ $donaturs }}

count data campaign

{{ $campaigns }}

sum total donasi

{{ moneyFormat($donations) }}

Pada bagian menampilkan sum donasi, kita masukkan ke dalam helper yang sebelumnya
sudah kita buat, yaitu moneyFormat dengan menggunakan helper ini, maka angka yang
akan di tampilkan akan di tambahkan kata Rp. dan dipisahkan dengan notasi (.)
berdasarkan besar angka-nya.

Langkah 5 - Uji Coba Akses Halaman Dashboard


Sekarang, silahkan lakukan proses otentikasi dan jika berhasil, maka akan di arahkan ke
dalam url/halaman /admin/dashboard, kurang lebih seperti berikut ini :

143
144
Membuat CRUD Category

Pada tahap ini kita semua akan belajar bagaimana cara membuat CRUD yaitu create, read,
update dan delete. CRUD merupakan bagian penting dalam pengembangan sebuah
website/aplikasi. Karena dengan operasi CRUD kita akan melakukan sebuah manipulasi
data ke dalam database. Sebagai developer/programmer mempelajari CRUD merupakan hal
yang wajib dilakukan pertama kali, dengan mempelajari operasi CRUD, maka kita bisa tau
kapan data ditampilkan, dimasukkan, diupdate dan dihapus.

Dan kali ini kita semua akan belajar bagaimana cara membuat CRUD data category, yang
mana akan digunakan untuk mengelompokan data campaign yang memiliki jenis sama,
category juga akan mempermudah user dalam mencari data berdasarkan jenis/tipe tertentu.

Langkah 1 - Membuat Controller Category


Silahkan jalankan perintah berikut ini di dalam terminal/CMD untuk membuat controller baru
:

php artisan make:controller Admin\\CategoryController

Perintah di atas akan membuat controller baru dengan nama CategoryController.php


di dalam folder app/Http/Controllers/Admin. Sekarang kita akan ubah seluruh kode-
nya agar bisa digunakan untuk melakukan operasi CRUD. Silahkan buka file
app/Http/Controllers/CategoryController.php dan ubah semua kode-nya menjadi
seperti berikut ini :

<?php

namespace App\Http\Controllers\Admin;

use App\Models\Category;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;

class CategoryController extends Controller


{
/**
* index

145
*
* @return void
*/
public function index()
{
$categories = Category::latest()->when(request()->q,
function($categories) {
$categories = $categories->where('name', 'like', '%'.
request()->q . '%');
})->paginate(10);

return view('admin.category.index', compact('categories'));


}
/**
* create
*
* @return void
*/
public function create()
{
return view('admin.category.create');
}
/**
* store
*
* @param mixed $request
* @return void
*/
public function store(Request $request)
{
$this->validate($request, [
'image' => 'required|image|mimes:jpeg,jpg,png|max:2000',
'name' => 'required|unique:categories'
]);

//upload image
$image = $request->file('image');
$image->storeAs('public/categories', $image->hashName());

//save to DB
$category = Category::create([
'image' => $image->hashName(),
'name' => $request->name,
'slug' => Str::slug($request->name, '-')
]);

146
if($category){
//redirect dengan pesan sukses
return
redirect()->route('admin.category.index')->with(['success' => 'Data
Berhasil Disimpan!']);
}else{
//redirect dengan pesan error
return
redirect()->route('admin.category.index')->with(['error' => 'Data Gagal
Disimpan!']);
}
}
/**
* edit
*
* @param mixed $request
* @param mixed $category
* @return void
*/
public function edit(Category $category)
{
return view('admin.category.edit', compact('category'));
}
/**
* update
*
* @param mixed $request
* @param mixed $category
* @return void
*/
public function update(Request $request, Category $category)
{
$this->validate($request, [
'name' => 'required|unique:categories,name,'.$category->id
]);

//check jika image kosong


if($request->file('image') == '') {
//update data tanpa image
$category = Category::findOrFail($category->id);
$category->update([
'name' => $request->name,
'slug' => Str::slug($request->name, '-')
]);

} else {

147
//hapus image lama
Storage::disk('local')->delete('public/categories/'.basename($category->
image));

//upload image baru


$image = $request->file('image');
$image->storeAs('public/categories', $image->hashName());

//update dengan image baru


$category = Category::findOrFail($category->id);
$category->update([
'image' => $image->hashName(),
'name' => $request->name,
'slug' => Str::slug($request->name, '-')
]);
}

if($category){
//redirect dengan pesan sukses
return
redirect()->route('admin.category.index')->with(['success' => 'Data
Berhasil Diupdate!']);
}else{
//redirect dengan pesan error
return
redirect()->route('admin.category.index')->with(['error' => 'Data Gagal
Diupdate!']);
}
}
/**
* destroy
*
* @param mixed $id
* @return void
*/
public function destroy($id)
{
$category = Category::findOrFail($id);
Storage::disk('local')->delete('public/categories/'.basename($category->
image));
$category->delete();

if($category){
return response()->json([
'status' => 'success'
]);

148
}else{
return response()->json([
'status' => 'error'
]);
}
}
}

Dari penambahan kode di aatas, pertama kita melakukan import Model Category, karena
kita membutuhkan Model ini untuk melakukan beberapa aksi, seperti mendapatkan data
category, melakukan insert data, update sampai delete data category.

use App\Models\Category;

Selanjutnya, kita juga melakukan import untuk helpers string dari Laravel, ini akan kita
gunakan nantinya untuk membuat slug dari data category yang disimpan.

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;

Setelah itu kita import filesystem dari Laravel, ini bisa digunakan untuk menyimpan data ke
dalam local aplikasi maupun ke cloud seperti Amazon S3, Digital Ocean Spaces, dan lain-
lain.

use Illuminate\Support\Facades\Storage;

Dan di dalam controller category kita menambahkan 6 method, yaitu :

function index
function create
function store
function edit

149
function update
function destroy

function index

Method ini akan digunakan untuk mengambil atau mendapatkan data category di dalam
table categories melalui Model Category. Dan di dalam method ini kita akan urutkan
data-nya berdasarkan yang terakhir kali di masukkan ke dalam database.

Kemudian kita juga membuat sebuah kondisi di dalam Eloquent, yaitu menggunakan when.
Jadi ketika ada sebuah request dengan nama q, maka kita akan melakukan filter data
category berdasarkan value dari request tersebut. Dan kita berikan method paginate
untuk membatasi jumlah data yang akan di tampilkan per-halaman.

$categories = Category::latest()->when(request()->q,
function($categories) {
$categories = $categories->where('name', 'like', '%'. request()->q .
'%');
})->paginate(10);

Setelah itu, kita kirim/parsing variable $categories di atas ke sebuah view yang berada di
dalam folder admin.category.index menggunakan function compact dari PHP.

return view('admin.category.index', compact('categories'));

function create

Method ini akan digunakan untuk menampilkan halaman tambah data category dan di
dalam method ini akan melakukan return ke sebuah view yang berada di dalam folder
admin.category.create.

return view('admin.category.create');

function store

Method ini akan digunakan untuk melakukan proses insert data ke dalam database yang
dikirim melalui form. Di dalam method ini kita membuat sebuah kondisi validasi, jadi
sebelum data benar-benar di insert ke database, maka akan di lakukan pengecekan validasi
terlebih dahulu, mulai dari extensi image, ukuran, tidak boleh kosong, bersifat unique dan
lain-lain.

150
$this->validate($request, [
'image' => 'required|image|mimes:jpeg,jpg,png|max:2000',
'name' => 'required|unique:categories'
]);

Dari kode di atas, kita membuat validasi dengan 2 field, yaitu : image dan name. Dan untuk
keterangan dari validasi di atas kurang lebih seperti berikut ini :

KEY VALIDASI KETERANGAN

image required field wajib diisi.

image field harus memiliki extensi gambar.

extensi gambar yang boleh diupload adalah .jpeg, .jpg


mimes:jpeg,jpg,png
dan .png.

gambar yang boleh diupload maksimal memiliki ukuran


max:2000
2000 Kb / 2 Mb.

name required field wajib diisi.

field bersifat unik dan tidak boleh ada yang sama di


unique:categories
dalam table categories.

Setelah validasi di atas terpenuhi, maka akan menjalankan proses upload gambar ke dalam
storage Laravel. Dan untuk nama dari gambar yang diupload akan di ubah menjadi random
menggunakan function hashName.

//upload image
$image = $request->file('image');
$image->storeAs('public/categories', $image->hashName());

Di atas, kita akan letakkan gambar yang diupload ke dalam folder


storage/app/public/categories.

Setelah gambar berhasil diupload, sekarang kita akan lanjutkan untuk menyimpan data
category ke dalam database, kurang lebih seperti berikut ini :

151
//save to DB
$category = Category::create([
'image' => $image->hashName(),
'name' => $request->name,
'slug' => Str::slug($request->name, '-')
]);

Di atas, kita akan melakuakn insert data ke dalam table categories, yaitu :

ATTRIBUTE VALUE KETERANGAN

akan mengambil nama dari gambar yang


image $image->hashName()
diupload

akan mengambil data dari request yang


name $request->name
bernama name

akan diisi dengan request name dan di


masukkan ke dalam helper Str::slug
slug Str::slug($request->name, '-') untuk menghasilkan sebuah URL dari
nama category dan akan di tambahkan
notasi (-) untuk pemisah antar kata.

kemudian kita membuat sebuah kondisi untuk menentukan, apakah proses insert data
category di atas berhasil atau tidak.

if($category){
//redirect dengan pesan sukses
return redirect()->route('admin.category.index')->with(['success' =>
'Data Berhasil Disimpan!']);
}else{
//redirect dengan pesan error
return redirect()->route('admin.category.index')->with(['error' =>
'Data Gagal Disimpan!']);
}

function edit

Method ini digunakan untuk menampilkan data category yang akan diupdate ke dalam
sebuah form. Dan jika kita perhatikan di dalam parameter method ini kita melakukan model
injection, yaitu : Category $category. Setelah itu kita parsing variable $category
tersebut ke dalam view yang berada di dalam folder admin.category.edit
menggunakan function compact dari PHP.

152
public function edit(Category $category) // <-- model injection
{
return view('admin.category.edit', compact('category'));
}

function update

Method ini digunakan untuk melakukan proses update data category ke dalam database.
Disini sama seperti method store, kita buat sebuah validasi untuk melakukan proses
pengecekan data, apakah data tersebut sudah sesuai atau belum.

$this->validate($request, [
'name' => 'required|unique:categories,name,'.$category->id
]);

Dari kode validasi di atas, kurang lebih penjelasannya seperti berikut ini :

KEY VALIDASI KETERANGAN

name required field wajib diisi.

field bersifat unik dan tidak boleh ada


yang sama di dalam table
categories. Karena bersifat unik,
unique:categories,name,'.$category->id maka untuk proses update kita
tambahkan name,'.$category->id,
yang artinya kusus ID category ini
akan dikecualikan.

Kemudian, jika validasi tersebut terpenuhi, kita akan membuat kondisi untuk file gambar,
apakah ada isinya atau tidak, kurang lebih seperti berikut ini :

153
//check jika image kosong
if($request->file('image') == '') {

//update data tanpa gambar

}else{

//update data dengan gambar baru

Jika request file dengan nama image tidak memiliki value, maka otomatis kita akan
melakukan proses update data category tanpa gambar.

//update data tanpa image


$category = Category::findOrFail($category->id); // <-- mencari category
berdasarkan ID
$category->update([
'name' => $request->name,
'slug' => Str::slug($request->name, '-')
]);

Tapi, jika request file dengan nama image memiliki sebuah value, maka kita akan
melakukan proses update data category dengan gambar baru. Tapi sebelum gambar baru
diupload, maka kita harus melakukan hapus data gambar yang lama terlebih dahulu.

//hapus image lama


Storage::disk('local')->delete('public/categories/'.basename($category->
image));

Setelah gambar yang lama berhasil dihapus dari folder storage Laravel, sekarang kita
lanjutkan untuk melakukan proses upload gambar yang baru.

//upload image baru


$image = $request->file('image');
$image->storeAs('public/categories', $image->hashName());

154
Setelah gambar yang baru berhasil diupload, sekarang kita lanjutkan untuk melakukan
proses update data category dengan gambar yang terbaru.

//update dengan image baru


$category = Category::findOrFail($category->id); // <-- mencari category
berdasarkan ID
$category->update([
'image' => $image->hashName(),
'name' => $request->name,
'slug' => Str::slug($request->name, '-')
]);

Di atas, kita melakukan proses update data category berdasarkan ID, kurang lebih seperti
berikut ini :

ATTRIBUTE VALUE KETERANGAN

akan mengambil nama dari gambar baru


image $image->hashName()
yang diupload

akan mengambil data dari request yang


name $request->name
bernama name

akan diisi dengan request name dan di


masukkan ke dalam helper Str::slug
slug Str::slug($request->name, '-') untuk menghasilkan sebuah URL dari
nama category dan akan di tambahkan
notasi (-) untuk pemisah antar kata.

kemudian kita membuat sebuah kondisi untuk menentukan, apakah proses update data
category di atas berhasil atau tidak.

if($category){
//redirect dengan pesan sukses
return redirect()->route('admin.category.index')->with(['success'
=> 'Data Berhasil Diupdate!']);
}else{
//redirect dengan pesan error
return redirect()->route('admin.category.index')->with(['error' =>
'Data Gagal Diupdate!']);
}

function destroy

155
Method ini akan digunakan untuk menghapus data category dari database dan disini kita
juga akan menghapus gambar yang berkaitan dengan category.

$category = Category::findOrFail($id); // <-- mencari category


berdasarkan ID

//hapus gambar
Storage::disk('local')->delete('public/categories/'.basename($category->
image));

//hapus data category


$category->delete();

Jika proses hapus data berhasil, maka kita akan mengembalikan ke dalam format JSON
dengan status success.

return response()->json([
'status' => 'success'
]);

Dan jika proses hapus data gagal, maka akan mengembalikan ke dalam format JSON dengan
status error.

return response()->json([
'status' => 'error'
]);

Langkah 2 - Membuat Route Resource Category


Sekarang, kita akan menambahkan route untuk controller category, dan jenis route yang
akan kita gunakan adalah resource. Dengan menggunakan route resource maka kita
akan mendapatkan sebuah route yang komplit, mulai dari index, create, store, edit,
update dan destroy. Jadi kita tidak perlu mendefinisikan satu-satu route untuk proses
CRUD-nya.

Silahkan buka file routes/web.php kemudian tambahkan route berikut ini di dalam prefix
admin dan middleware auth dan tepat di bawahnya route dashboard.

156
//route resource categories
Route::resource('/category', CategoryController::class,['as' =>
'admin']);

Route di atas, kita tambahkan 'as' => 'admin', yang artinya, nama route-nya akan di
tambahkan kata admin di depannya. Contohnya jika di atas kita buat route-nya /category,
maka hasilnya nanti nama route tersebut akan menjadi seperti berikut ini :

dengan 'as' => 'admin' tanpa 'as' => admin

admin.category.index category.index

admin.category.show category.show

admin.category.create category.create

admin.category.store category.store

admin.category.edit category.edit

admin.category.update category.update

admin.category.destroy category.destroy

Kita bisa lihat untuk perbedaan di atas, jika menggunakan dan tanpa menggunakan 'as'
=> 'admin'. Yaitu setiap name dari route akan di tambahkan kataadmin. Kita juga bisa
melihatnya melalui perintah berikut ini di dalam terminal/CMD :

php artisan route:list

157
Setelah berhasil membuat route untuk category, sekarang kita lanjutkan untuk memanggil
route tersebut di dalam menu sidebar admin. Silahkan buka file
resources/views/layouts/app.blade.php dan cari kode berikut ini :

<a class="flex items-center mt-4 py-2 px-6 hover:bg-gray-700 hover:bg-


opacity-25 hover:text-gray-100 {{ Request::is('admin/category*') ? ' bg-
gray-700 bg-opacity-25 text-gray-100' : 'text-gray-500' }}" href="#">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0
24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-
width="2"
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2
0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path>
</svg>
<span class="mx-3">Kategori</span>
</a>

Di atas, bisa kita perhatikan, untuk URL dari menu category masih menggunakan
href="#", sekarang kita ubah kode di atas, menjadi seperti berikut ini :

158
<a class="flex items-center mt-4 py-2 px-6 hover:bg-gray-700 hover:bg-
opacity-25 hover:text-gray-100 {{ Request::is('admin/category*') ? ' bg-
gray-700 bg-opacity-25 text-gray-100' : 'text-gray-500' }}" href="{{
route('admin.category.index') }}">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0
24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-
width="2"
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2
0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path>
</svg>
<span class="mx-3">Kategori</span>
</a>

Di atas, kita ubah yang semula adalah href="#" menjadi href="{{


route('admin.category.index') }}".

Langkah 3 - Membuat View/Halaman Index Category


Setelah berhasil membuat controller dan route untuk category, maka sekarang kita
lanjutkan untuk membuat view untuk menampilkan halaman index category.

Silahkan buat folder baru dengan nama category di dalam folder


resources/views/admin dan di dalam folder category tersebut, silahkan buat file baru
dengan nama index.blade.php dan masukkan kode berikut ini :

@extends('layouts.app', ['title' => 'Kategori - Admin'])

@section('content')
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-300">
<div class="container mx-auto px-6 py-8">

<div class="flex items-center">


<button class="text-white focus:outline-none bg-gray-600
px-4 py-2 shadow-sm rounded-md">
<a href="{{ route('admin.category.create')
}}">TAMBAH</a>
</button>

<div class="relative mx-4">


<span class="absolute inset-y-0 left-0 pl-3 flex items-

159
center">
<svg class="h-5 w-5 text-gray-500" viewBox="0 0 24
24" fill="none">
<path
d="M21 21L15 15M17 10C17 13.866 13.866 17 10
17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401
17 10Z"
stroke="currentColor" stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</span>
<form action="{{ route('admin.category.index') }}"
method="GET">
<input class="form-input w-full rounded-lg pl-10
pr-4" type="text" name="q" value="{{ request()->query('q') }}"
placeholder="Search">
</form>
</div>
</div>

<div class="-mx-4 sm:-mx-8 px-4 sm:px-8 py-4 overflow-x-auto">


<div class="inline-block min-w-full shadow-sm rounded-lg
overflow-hidden">
<table class="min-w-full table-auto">
<thead class="justify-between">
<tr class="bg-gray-600 w-full">
<th class="px-16 py-2">
<span class="text-white">GAMBAR</span>
</th>
<th class="px-16 py-2 text-left">
<span class="text-white">NAMA
KATEGORI</span>
</th>
<th class="px-16 py-2">
<span class="text-white">AKSI</span>
</th>
</tr>
</thead>
<tbody class="bg-gray-200">
@forelse($categories as $category)
<tr class="border bg-white">
<td class="px-16 py-2 flex justify-
center">
<img src="{{ $category->image }}"
class="w-10 h-100 object-fit-cover rounded-full">

160
</td>
<td class="px-16 py-2">
{{ $category->name }}
</td>
<td class="px-10 py-2 text-center">
<a href="{{
route('admin.category.edit', $category->id) }}" class="bg-indigo-600
px-4 py-2 rounded shadow-sm text-xs text-white focus:outline-
none">EDIT</a>
<button onClick="destroy(this.id)"
id="{{ $category->id }}" class="bg-red-600 px-4 py-2 rounded shadow-sm
text-xs text-white focus:outline-none">HAPUS</button>
</td>
</tr>
@empty
<div class="bg-red-500 text-white text-
center p-3 rounded-sm shadow-md">
Data Belum Tersedia!
</div>
@endforelse
</tbody>
</table>
@if ($categories->hasPages())
<div class="bg-white p-3">
{{
$categories->links('vendor.pagination.tailwind') }}
</div>
@endif
</div>
</div>
</div>
</main>
<script>
//ajax delete
function destroy(id) {
var id = id;
var token = $("meta[name='csrf-token']").attr("content");

Swal.fire({
title: 'APAKAH KAMU YAKIN ?',
text: "INGIN MENGHAPUS DATA INI!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
cancelButtonText: 'BATAL',

161
confirmButtonText: 'YA, HAPUS!',
}).then((result) => {
if (result.isConfirmed) {
//ajax delete
jQuery.ajax({
url: `/admin/category/${id}`,
data: {
"id": id,
"_token": token
},
type: 'DELETE',
success: function (response) {
if (response.status == "success") {
Swal.fire({
icon: 'success',
title: 'BERHASIL!',
text: 'DATA BERHASIL DIHAPUS!',
showConfirmButton: false,
timer: 3000
}).then(function () {
location.reload();
});
} else {
Swal.fire({
icon: 'error',
title: 'GAGAL!',
text: 'DATA GAGAL DIHAPUS!',
showConfirmButton: false,
timer: 3000
}).then(function () {
location.reload();
});
}
}
});
}
})
}
</script>
@endsection

Di atas, pertama kita atur agar halaman ini menggunakan layout dari app dan di dalamnya
kita buat array yang bernama title dan memiliki value Kategori - Admin, array ini
akan di tampilkan di layout untuk title dari website.

162
@extends('layouts.app', ['title' => 'Kategori - Admin'])

kemudian kita menambahkan 1 button untuk tambah data category, button ini akan
mengarahkan kita ke dalam route yang bernama admin.category.create.

<button class="text-white focus:outline-none bg-gray-600 px-4 py-2


shadow-sm rounded-md">
<a href="{{ route('admin.category.create') }}">TAMBAH</a>
</button>

Dan untuk pencarian, pada form action kita arahkan ke dalam route yang bernama
admin.category.index, dengan menggunakan method GET.

<form action="{{ route('admin.category.index') }}" method="GET">


<input class="form-input w-full rounded-lg pl-10 pr-4" type="text"
name="q" value="{{ request()->query('q') }}" placeholder="Search">
</form>

Di dalam input di atas, kita berikan name-nya adalah q.

Kemudian untuk menampilkan data category, kita menggunakan sintaks dari blade yaitu
forelse. Kurang lebih seperti berikut ini :

@forelse($categories as $no => $category)


//kode untuk perulangan data
@empty

//kode untuk menampilkan pesan, jika data belum tersedia

@endforelse

Untuk edit data category, kita akan memanggil route yang bernama
admin.category.edit dan ditambahkan sebuah parameter berupa ID dari data category.

163
<a href="{{ route('admin.category.edit', $category->id) }}" class="bg-
indigo-600 px-4 py-2 rounded shadow-sm text-xs text-white focus:outline-
none">EDIT</a>

Dan untuk proses delete data category, kita akan menggunakan AJAX dengan
dikombinasikan Sweet Alert2. Kurang lebih seperti berikut ini :

<button onClick="destroy(this.id)" id="{{ $category->id }}" class="bg-


red-600 px-4 py-2 rounded shadow-sm text-xs text-white focus:outline-
none">HAPUS</button>

Pada button delete data di atas, kita memanggil sebuah function JavaScript yang bernama
destroy dan di dalamnya kita berikan sebuah parameter yang di ambil dari attribute
id="{{ $category->id }}", yang mana isinya adalah ID dari category.

Dan untuk function destroy di JavaScript kita kombinasikan dengan library Sweet Alert 2
agar bisa menampilkan sebuah popup konfirmasi.

function destroy(id) {

//ajax + sweet alert 2 confirmation

Pada bagian Ajax kita arahkan URL untuk menghapus datanya ke dalam :
/admin/category/${id}. Dimana $id berisi ID dari category. Dan untuk method yang
digunakan adalah DELETE.

jQuery.ajax({
url: `/admin/category/${id}`, // <-- URL/endpoint untuk delete
data: {
"id": id, // <-- ID category
"_token": token // < -- CSRF Token
},
type: 'DELETE', // < -- method DELETE
...

164
Dan untuk menampilkan data perhalaman, kita menggunakan pagination, di dalam Laravel
kita sudah disediakan template untuk pagination, baik itu Bootstrap, Semantic UI,
Tailwind CSS dan lain-lain. Oleh karena itu, kita cukup melakukan publish view/template
yang sudah disediakan tersebut.

Silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan vendor:publish --tag=laravel-pagination

Jika berhasil, maka kita akan mendapatkan beberapa file view/template untuk style
pagination di dalam folder /resources/views/vendor/pagination. Dan kali ini kita
akan menggunakan yang versi Tailwind CSS, dan kita cukup memanggilnya dengan kode
seperti berikut ini :

{{ $categories->links('vendor.pagination.tailwind') }}

Dari kode di atas, kita arahkan style pagination untuk data category ke dalam folder
resources/views/vendor/pagination/tailwind.blade.php.

Sekarang kita coba jalankan category-nya di http://localhost:8000/admin/category, Jika


berhasil, maka kurang lebih tampilannya seperti berikut ini :

Di atas muncul message Data Belum Tersedia! karena memang kita belum
memasukkan data apapun di dalam table categories.

165
Langkah 4 - Membuat View/Halaman Create Category
Setelah berhasil membuat view untuk menampilkan halaman index category, maka
sekarang kita lanjutkan untuk membuat view untuk menampilkan form untuk menambah
data category. Silahkan buat file baru dengan nama create.blade.php di dalam folder
resources/views/admin/category dan masukkan kode berikut ini :

@extends('layouts.app', ['title' => 'Tambah Kategori - Admin'])

@section('content')
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-300">
<div class="container mx-auto px-6 py-8">

<div class="p-6 bg-white rounded-md shadow-md">


<h2 class="text-lg text-gray-700 font-semibold
capitalize">TAMBAH KATEGORI</h2>
<hr class="mt-4">
<form action="{{ route('admin.category.store') }}"
method="POST" enctype="multipart/form-data">
@csrf
<div class="grid grid-cols-1 gap-6 mt-4">
<div>
<label class="text-gray-700"
for="image">GAMBAR</label>
<input class="form-input w-full mt-2 rounded-md
bg-gray-200 focus:bg-white p-3" type="file" name="image">
@error('image')
<div class="w-full bg-red-200 shadow-sm
rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>

<div>
<label class="text-gray-700" for="name">NAMA
KATEGORI</label>
<input class="form-input w-full mt-2 rounded-md
bg-gray-200 focus:bg-white" type="text" name="name" value="{{
old('name') }}" placeholder="Nama Kategori">
@error('name')

166
<div class="w-full bg-red-200 shadow-sm
rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>
</div>

<div class="flex justify-start mt-4">


<button type="submit" class="px-4 py-2 bg-gray-600
text-gray-200 rounded-md hover:bg-gray-700 focus:outline-none focus:bg-
gray-700">SIMPAN</button>
</div>
</form>
</div>
</div>
</main>
@endsection

Di atas, pertama kita atur agar halaman ini menggunakan layout dari app dan di dalamnya
kita buat array yang bernama title dan memiliki value Tambah Kategori - Admin,
array ini akan di tampilkan di layout untuk title dari website.

@extends('layouts.app', ['title' => 'Tambah Kategori - Admin'])

kemudian dibagian form action kita arahkan ke sebuah route yang bernama
admin.category.store, dimana jika kita perhatikan route ini akan mengarah ke dalam
method store yang ada di dalam CategoryController.

<form action="{{ route('admin.category.store') }}" method="POST"


enctype="multipart/form-data">

Dan jika kita melakukan upload file/gambar di dalam form, pastikan kita atur form tersebut
dengan attribute enctype="multipart/form-data". Jika tidak, maka form tersebut tidak
akan bisa melakukan proses upload file/gambar.

Sekarang, jika kita coba jalankan di http://localhost:8000/admin/category/create atau bisa

167
klik button TAMBAH, maka kurang lebih tampilannya seperti berikut ini :

Sekarang kita coba klik button SIMPAN tanpa mengisi input apapun, maka kita akan
mendapatkan sebuah error validasi kurang lebih seperti berikut ini :

GAMBAR CONTOH CATEGORY :


https://drive.google.com/file/d/12y7N4Nl8Z9eATCKpzKoOQszhOw4g1VK_/view?usp=sharing

Dan sekarang kita coba isi input yang ada, mulai dari gambar dan nama category, jika
berhasil maka kurang lebih hasilnya seperti berikut ini :

168
Langkah 5 - Membuat View/Halaman Edit Category
Setelah berhasil membuat view untuk menampilkan halaman tambah category, sekarang
kita lanjutkan untuk membuat view lagi untuk menampilkan form edit data category.

Silahkan buat file baru dengan nama edit.blade.php di dalam folder


resources/views/admin/category dan masukkan kode berikut ini :

@extends('layouts.app', ['title' => 'Edit Kategori - Admin'])

@section('content')
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-300">
<div class="container mx-auto px-6 py-8">

<div class="p-6 bg-white rounded-md shadow-md">


<h2 class="text-lg text-gray-700 font-semibold
capitalize">EDIT KATEGORI</h2>
<hr class="mt-4">
<form action="{{ route('admin.category.update',
$category->id) }}" method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
<div class="grid grid-cols-1 gap-6 mt-4">
<div>
<label class="text-gray-700"
for="image">GAMBAR</label>

169
<input class="form-input w-full mt-2 rounded-md
bg-gray-200 focus:bg-white p-3" type="file" name="image">
</div>

<div>
<label class="text-gray-700" for="name">NAMA
KATEGORI</label>
<input class="form-input w-full mt-2 rounded-md
bg-gray-200 focus:bg-white" type="text" name="name" value="{{
old('name', $category->name) }}" placeholder="Nama Kategori">
@error('name')
<div class="w-full bg-red-200 shadow-sm
rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>
</div>

<div class="flex justify-start mt-4">


<button type="submit" class="px-4 py-2 bg-gray-600
text-gray-200 rounded-md hover:bg-gray-700 focus:outline-none focus:bg-
gray-700">UPDATE</button>
</div>
</form>
</div>
</div>
</main>
@endsection

Di atas, pertama kita atur agar halaman ini menggunakan layout dari app dan di dalamnya
kita buat array yang bernama title dan memiliki value Edit Kategori - Admin, array
ini akan di tampilkan di layout untuk title dari website.

@extends('layouts.app', ['title' => 'Edit Kategori - Admin'])

Dan pada bagian form action kita arahkan ke dalam route yang bernama
admin.category.update dan di dalam route ini kita tambahkan parameter ID dari
category.

170
<form action="{{ route('admin.category.update', $category->id) }}"
method="POST" enctype="multipart/form-data">

Dan jangan lupa, untuk proses update data di dalam form kita tambahkan sebuah sintaks
berikut ini :

@method('PUT')

Sekarang, kita coba klik edit data category, jika berhasil maka kurang lebih tampilannya
seperti berikut ini :

Sekarang silahkan ubah gambar atau mengubah nama category dan jika berhasil maka
kurang lebih hasilnya seperti berikut ini :

171
Terakhir, kita akan uji coba untuk proses delete data category, silahkan klik button delete,
maka kita akan mendapatkan sebuah konfirmasi delete menggunakan Sweet Alert2,
kurang lebih seperti berikut ini :

172
Membuat CRUD Campaign

Sekarang kita bersama-sama akan belajar bagaimana cara membuat CRUD data campaing,
dimana kita juga akan menggunakan data category untuk membedakan satu campaign
dengan campaign yang lain.

Langkah 1 - Membuat Controller Campaign


Untuk membuat controller campaign, silahkan jalankan perintah berikut ini di dalam
terminal/CMD dan menjalankannya di dalam project Laravel :

php artisan make:controller Admin\\CampaignController

Jika perintah di atas berhasil di jalankan, maka kita akan mendapatkan 1 file controller baru
yang bernama CampaignController.php di dalam folder
app/Http/Controllers/Admin. Silahkan buka file controller tersebut dan ubah kode-nya
menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Admin;

use App\Models\Campaign;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Category;
use Illuminate\Support\Facades\Storage;

class CampaignController extends Controller


{
/**
* index
*
* @return void
*/
public function index()
{
$campaigns = Campaign::latest()->when(request()->q,
function($campaigns) {

173
$campaigns = $campaigns->where('title', 'like', '%'.
request()->q . '%');
})->paginate(10);

return view('admin.campaign.index', compact('campaigns'));


}
/**
* create
*
* @return void
*/
public function create()
{
$categories = Category::latest()->get();
return view('admin.campaign.create', compact('categories'));
}
/**
* store
*
* @param mixed $request
* @return void
*/
public function store(Request $request)
{
$this->validate($request, [
'image' => 'required|image|mimes:png,jpg,jpeg',
'title' => 'required',
'category_id' => 'required',
'target_donation' => 'required|numeric',
'max_date' => 'required',
'description' => 'required'
]);

//upload image
$image = $request->file('image');
$image->storeAs('public/campaigns', $image->hashName());

$campaign = Campaign::create([
'title' => $request->title,
'slug' => Str::slug($request->title, '-'),
'category_id' => $request->category_id,
'target_donation' => $request->target_donation,
'max_date' => $request->max_date,
'description' => $request->description,
'user_id' => auth()->user()->id,
'image' => $image->hashName()

174
]);

if($campaign){
//redirect dengan pesan sukses
return
redirect()->route('admin.campaign.index')->with(['success' => 'Data
Berhasil Disimpan!']);
}else{
//redirect dengan pesan error
return
redirect()->route('admin.campaign.index')->with(['error' => 'Data Gagal
Disimpan!']);
}
}
/**
* edit
*
* @param mixed $campaign
* @return void
*/
public function edit(Campaign $campaign)
{
$categories = Category::latest()->get();
return view('admin.campaign.edit', compact('campaign',
'categories'));
}
/**
* update
*
* @param mixed $request
* @param mixed $campaign
* @return void
*/
public function update(Request $request, Campaign $campaign)
{
$this->validate($request, [
'title' => 'required',
'category_id' => 'required',
'target_donation' => 'required|numeric',
'max_date' => 'required',
'description' => 'required'
]);

//check jika image kosong


if($request->file('image') == '') {
//update data tanpa image

175
$campaign = Campaign::findOrFail($campaign->id);
$campaign->update([
'title' => $request->title,
'slug' => Str::slug($request->title, '-'),
'category_id' => $request->category_id,
'target_donation' => $request->target_donation,
'max_date' => $request->max_date,
'description' => $request->description,
'user_id' => auth()->user()->id,
]);

} else {

//hapus image lama


Storage::disk('local')->delete('public/campaigns/'.basename($campaign->i
mage));

//upload image baru


$image = $request->file('image');
$image->storeAs('public/campaigns', $image->hashName());

//update dengan image baru


$campaign = Campaign::findOrFail($campaign->id);
$campaign->update([
'title' => $request->title,
'slug' => Str::slug($request->title, '-'),
'category_id' => $request->category_id,
'target_donation' => $request->target_donation,
'max_date' => $request->max_date,
'description' => $request->description,
'user_id' => auth()->user()->id,
'image' => $image->hashName()
]);
}

if($campaign){
//redirect dengan pesan sukses
return
redirect()->route('admin.campaign.index')->with(['success' => 'Data
Berhasil Diupdate!']);
}else{
//redirect dengan pesan error
return
redirect()->route('admin.campaign.index')->with(['error' => 'Data Gagal
Diupdate!']);
}

176
}
/**
* destroy
*
* @param mixed $id
* @return void
*/
public function destroy($id)
{
$campaign = Campaign::findOrFail($id);
Storage::disk('local')->delete('public/campaigns/'.basename($campaign->i
mage));
$campaign->delete();

if($campaign){
return response()->json([
'status' => 'success'
]);
}else{
return response()->json([
'status' => 'error'
]);
}
}
}

Dari penambahan kode di atas, pertama kita melakukan import Model Campaign, karena
kita membutuhkan Model ini untuk melakukan beberapa aksi, seperti mendapatkan data
campaign, melakukan insert data, update sampai delete data campaign.

use App\Models\Campaign;

Selanjutnya, kita juga melakukan import untuk helpers string dari Laravel, ini akan kita
gunakan nantinya untuk membuat slug dari data campaign yang disimpan.

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.

177
use Illuminate\Http\Request;

Setelah itu kita import filesystem dari Laravel, ini bisa digunakan untuk menyimpan data ke
dalam local aplikasi maupun ke cloud seperti Amazon S3, Digital Ocean Spaces, dan lain-
lain.

use Illuminate\Support\Facades\Storage;

Dan kita juga import Model Category, karena nanti kita akan menampilkan data category
saat akan menambahkan data campaign dan edit data campaign.

use App\Models\Category;

Di dalam controller CampaignController kita menambahkan 6 method baru, yaitu :

function index
function create
function store
function edit
function update
function destroy

function index

Method ini akan digunakan untuk mendapatkan data campaign yang diambil dari Model
Campaign atau table campaigns. Dan di dalam proses mendapatkan data tersebut, kita
membuat sebuah kondisi untuk melakukan filter atau pencarian menggunakan funtion
when.

Jika ada sebuah request dengan nama q, maka akan melakukan pencarian data campaign
berdasarkan judul yang terkait dengan isi dari request q tersebut. Dan setelah itu kita batasi
data yang di tampilkan menggunakan fitur pagination. Dengan menggunakan
pagination, maka data yang ditampilkan perhalaman akan dibatasi sesuai dengan
kebutuhan kita. Dalam contoh dibawah kita berikan value 10 data yang akan di tampilkaan
perhalaman.

178
$campaigns = Campaign::latest()->when(request()->q, function($campaigns)
{
$campaigns = $campaigns->where('title', 'like', '%'. request()->q .
'%');
})->paginate(10);

Setelah data berhasil di dapatkan dari Model Campaign atau table campaigns,
selanjutnya kita akan parsing variable $camapigns ke dalam sebuah view yang berada di
dalam folder admin.campaign.index menggunakan function bawaan dari PHP, yaitu
compact.

return view('admin.campaign.index', compact('campaigns'));

function create

Method ini akan digunakan untuk menampilkan sebuah halaman form untuk proses
menambah data campaign, disini kita akan memanggil data category untuk di tampilkan di
dalam halaman tambah data sebagai select option.

$categories = Category::latest()->get();

Dari penjelasan kode di atas yaitu akan mengambil semua data category dan diurutkan dari
yang paling terbaru menggunakan fitur latest dan selanjutnya data tersebut akan
diparsing ke dalam view yang berada di dalam folder admin.campaign.create
menggunakan function compact.

return view('admin.campaign.create', compact('categories'));

function store

Method ini digunakan untuk melakukan proses insert data campaign ke dalam database.
Dan di dalam method ini kita memiliki banyak kondisi, seperti melakukan validasi, upload
gambar dan melakukan insert ke database.

Yang pertama adalah validasi, dimana data yang dikirim melalui form akan dilakukan
validasi terlebih dahulu sebelum benar-benar di insert ke dalam database.

179
$this->validate($request, [
'image' => 'required|image|mimes:png,jpg,jpeg',
'title' => 'required',
'category_id' => 'required',
'target_donation' => 'required|numeric',
'max_date' => 'required',
'description' => 'required'
]);

Dari kode validasi di atas, kurang lebih penjelasannya seperti berikut ini :

KEY VALIDASI KETERANGAN

image required field wajib diisi.

image field harus memiliki extensi gambar.

extensi gambar yang boleh diupload adalah


mimes:png,jpg,jpeg
.jpeg, .jpg dan .png.

title required field wajib diisi.

category_id required field wajib diisi.

target_donation required field wajib diisi.

numeric field harus berupa numeric/angka

max_date required field wajib diisi.

description required field wajib diisi.

Jika validasi di atas terpenuhi, maka langkah selanjutnya adalah melakukan upload gambar
campaign ke dalam server.

//upload image
$image = $request->file('image');
$image->storeAs('public/campaigns', $image->hashName());

Dari kode di atas, pertama kita mengambil sebuah request dengan tipe file yang bernama
image yang dikirim melalui form. Setelah itu kita lakukan upload gambar tersebut ke dalam
folder storage laravel, yaitu storage/app/public/campaigns. Dan nama file gambar
yang diupload akan direname menjadi random menggunakan function hashName.

180
Setelah gambar berhasil diupload ke dalam sever, selanjutnya kita akan melakukan proses
insert data campaign ke dalam database.

$campaign = Campaign::create([
'title' => $request->title,
'slug' => Str::slug($request->title, '-'),
'category_id' => $request->category_id,
'target_donation' => $request->target_donation,
'max_date' => $request->max_date,
'description' => $request->description,
'user_id' => auth()->user()->id,
'image' => $image->hashName()
]);

Dari proses insert data di atas, kurang lebih seperti berikut ini penjelasannya :

ATTRIBUTE VALUE KETERANGAN

akan mengambil data dari request


title $request->title
yang bernama title

akan diisi dengan request title


dan di masukkan ke dalam helper
Str::slug untuk menghasilkan
slug Str::slug($request->title, '-')
sebuah URL dari title campaign dan
akan di tambahkan notasi (-) untuk
pemisah antar kata.

akan mengambil data dari request


category_id $request->category_id
yang bernama category_id

akan mengambil data dari request


target_donation request->target_donation
yang bernama target_donation

akan mengambil data dari request


max_date $request->max_date
yang bernama max_date

akan mengambil data dari request


description $request->description
yang bernama description

akan mengambil ID dari user yang


user_id auth()->user()->id
sedang login

akan mengambil nama dari gambar


image $image->hashName()
yang diupload

Setelah itu kita membuat sebuah kondisi untuk memastikan, apakah proses insert data

181
tersebut berhasil atau tidak. Kurang lebih seperti berikut ini :

if($campaign){
//redirect dengan pesan sukses
return redirect()->route('admin.campaign.index')->with(['success' =>
'Data Berhasil Disimpan!']);
}else{
//redirect dengan pesan error
return redirect()->route('admin.campaign.index')->with(['error' =>
'Data Gagal Disimpan!']);
}

function edit

Method ini digunakan untuk menampilkan data campaign yang akan di edit dan update, dan
disini kita masih memanggil data category untuk di tampilkan di dalam form edit.

$categories = Category::latest()->get();
return view('admin.campaign.edit', compact('campaign', 'categories'));

function update

Method ini digunakan untuk melakukan proses update data campaign ke dalam database.
Dan sama seperti method store, disini kita menggunakan sebuah validasi untuk memeriksa
apakah data yang akan di update sudah benar-benar sesuai.

$this->validate($request, [
'title' => 'required',
'category_id' => 'required',
'target_donation' => 'required|numeric',
'max_date' => 'required',
'description' => 'required'
]);

Dari kode validasi di atas, kurang lebih penjelasannya seperti berikut ini :

KEY VALIDASI KETERANGAN

title required field wajib diisi.

182
KEY VALIDASI KETERANGAN

category_id required field wajib diisi.

target_donation required field wajib diisi.

numeric field harus berupa numeric/angka

max_date required field wajib diisi.

description required field wajib diisi.

Setelah validasi di atas berhasil terpenuhi, maka selanjutnya kita akan membuat sebuah
kondisi lagi untuk memeriksa apakah gambar dari campaign juga ikut diubah atau tidak.

//check jika image kosong


if($request->file('image') == '') {

//update data tanpa gambar

}else{

//update data dengan gambar baru

Jika request dengan tipe file yang memiliki nama image bernilai kosong atau null, maka kita
akan melakukan update data campaign tanpa merubah nilai dari attribute image.

//update data tanpa image


$campaign = Campaign::findOrFail($campaign->id);
$campaign->update([
'title' => $request->title,
'slug' => Str::slug($request->title, '-'),
'category_id' => $request->category_id,
'target_donation' => $request->target_donation,
'max_date' => $request->max_date,
'description' => $request->description,
'user_id' => auth()->user()->id,
]);

Tapi, jika request file dengan nama image memiliki sebuah value, maka kita akan

183
melakukan proses update data campaign dengan gambar baru. Tapi sebelum gambar baru
diupload, maka kita harus melakukan hapus data gambar yang lama terlebih dahulu.

//hapus image lama


Storage::disk('local')->delete('public/campaigns/'.basename($campaign->i
mage));

Setelah gambar lama berhasil terhapus, selanjutnya kita melakukan upload untuk gambar
yang baru :

//upload image baru


$image = $request->file('image');
$image->storeAs('public/campaigns', $image->hashName());

Setelah gambar baru berhasil diupload, selanjutnya kita akan melakukan proses update data
campaign beserta mengubah nilai dari attribute image dengan nama dari file gambar yang
baru.

//update dengan image baru


$campaign = Campaign::findOrFail($campaign->id);
$campaign->update([
'title' => $request->title,
'slug' => Str::slug($request->title, '-'),
'category_id' => $request->category_id,
'target_donation' => $request->target_donation,
'max_date' => $request->max_date,
'description' => $request->description,
'user_id' => auth()->user()->id,
'image' => $image->hashName()
]);

kemudian kita membuat sebuah kondisi untuk menentukan, apakah proses update data
campaign di atas berhasil atau tidak.

184
if($campaign){
//redirect dengan pesan sukses
return redirect()->route('admin.campaign.index')->with(['success' =>
'Data Berhasil Diupdate!']);
}else{
//redirect dengan pesan error
return redirect()->route('admin.campaign.index')->with(['error' =>
'Data Gagal Diupdate!']);
}

function destroy

Method ini akan digunakan untuk menghapus data campaign dari database dan disini kita
juga akan menghapus gambar yang berkaitan dengan campaign.

$campaign = Campaign::findOrFail($id); // <-- mencari campaign


berdasarkan ID

//hapus gambar
Storage::disk('local')->delete('public/campaigns/'.basename($campaign->i
mage));

//hapus data campaign


$campaign->delete();

Jika proses hapus data berhasil, maka kita akan mengembalikan ke dalam format JSON
dengan status success.

return response()->json([
'status' => 'success'
]);

Dan jika proses hapus data gagal, maka akan mengembalikan ke dalam format JSON dengan
status error.

185
return response()->json([
'status' => 'error'
]);

Langkah 2 - Membuat Route Resource Campaign


Sekarang kita akan lanjutkan untuk membuat route campaign, disini kita akan
menggunakan route dengan jenis resource, dengan menggunakan jenis ini, maka kita
tidak perlu mendefinisikan route-nya satu persatu. Jadi semua route seperti index, show,
create, store, edit, update dan destroy akan otomatis digenerate oleh resource
tersebut.

Silahkan buka file routes/web.php kemudian tambahkan route berikut ini di dalam prefix
admin dan middleware auth dan tepat di bawahnya route category.

//route resource campaign


Route::resource('/campaign', CampaignController::class, ['as' =>
'admin']);

Route di atas, kita tambahkan 'as' => 'admin', yang artinya, nama route-nya akan di
tambahkan kata admin di depannya. Contohnya jika di atas kita buat route-nya /campaign,
maka hasilnya nanti nama route tersebut akan menjadi seperti berikut ini :

dengan 'as' => 'admin' tanpa 'as' => admin

admin.campaign.index campaign.index

admin.campaign.show campaign.show

admin.campaign.create campaign.create

admin.campaign.store campaign.store

admin.campaign.edit campaign.edit

admin.campaign.update campaign.update

admin.campaign.destroy campaign.destroy

Kita bisa lihat untuk perbedaan di atas, jika menggunakan dan tanpa menggunakan 'as'
=> 'admin'. Yaitu setiap name dari route akan di tambahkan kataadmin. Kita juga bisa
melihatnya melalui perintah berikut ini di dalam terminal/CMD :

186
php artisan route:list

Setelah menambahkan route untuk campaign, selanjutnya kita akan mengaktifkan menu
sidebar admin agar saat di klik akan menuju ke halaman campaign.

Silahkan buka file resources/views/layouts/app.blade.php dan cari kode berikut ini


:

<a class="flex items-center mt-4 py-2 px-6 hover:bg-gray-700 hover:bg-


opacity-25 hover:text-gray-100 {{ Request::is('admin/campaign*') ? ' bg-
gray-700 bg-opacity-25 text-gray-100' : 'text-gray-500' }}" href="#">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0
24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-
width="2"
d="M3 21v-4m0 0V5a2 2 0 012-2h6.5l1 1H21l-3
6 3 6h-8.5l-1-1H5a2 2 0 00-2 2zm9-13.5V9">
</path>
</svg>
<span class="mx-3">Campaigns</span>
</a>

Di atas, bisa kita perhatikan, untuk URL dari menu campaign masih menggunakan
href="#", sekarang kita ubah kode di atas, menjadi seperti berikut ini :

187
<a class="flex items-center mt-4 py-2 px-6 hover:bg-gray-700 hover:bg-
opacity-25 hover:text-gray-100 {{ Request::is('admin/campaign*') ? ' bg-
gray-700 bg-opacity-25 text-gray-100' : 'text-gray-500' }}" href="{{
route('admin.campaign.index') }}">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0
24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-
width="2"
d="M3 21v-4m0 0V5a2 2 0 012-2h6.5l1 1H21l-3
6 3 6h-8.5l-1-1H5a2 2 0 00-2 2zm9-13.5V9">
</path>
</svg>
<span class="mx-3">Campaigns</span>
</a>

Di atas, kita ubah yang semula adalah href="#" menjadi href="{{


route('admin.campaign.index') }}".

Langkah 3 - Membuat View/Halaman Index Campaign


Setelah berhasil membuat controller dan juga route, maka sekarang kita lanjutkan untuk
membuat sebuah view untuk menampilkan halaman index campaign. Silahkan buat folder
baru dengan nama campaign di dalam folder resources/views/admin dan di dalam
folder camapign sialahkan buat file baru dengan nama index.blade.php dan masukkan
kode berikut ini :

@extends('layouts.app', ['title' => 'Campaign - Admin'])

@section('content')
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-300">
<div class="container mx-auto px-6 py-8">

<div class="flex items-center">


<button class="text-white focus:outline-none bg-gray-600
px-4 py-2 shadow-sm rounded-md">
<a href="{{ route('admin.campaign.create')
}}">TAMBAH</a>
</button>

<div class="relative mx-4">


<span class="absolute inset-y-0 left-0 pl-3 flex items-

188
center">
<svg class="h-5 w-5 text-gray-500" viewBox="0 0 24
24" fill="none">
<path
d="M21 21L15 15M17 10C17 13.866 13.866 17 10
17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401
17 10Z"
stroke="currentColor" stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</span>
<form action="{{ route('admin.campaign.index') }}"
method="GET">
<input class="form-input w-full rounded-lg pl-10
pr-4" type="text" name="q" value="{{ request()->query('q') }}"
placeholder="Search">
</form>
</div>
</div>

<div class="-mx-4 sm:-mx-8 px-4 sm:px-8 py-4 overflow-x-auto">


<div class="inline-block min-w-full shadow-sm rounded-lg
overflow-hidden">
<table class="min-w-full table-auto">
<thead class="justify-between">
<tr class="bg-gray-600 w-full">
<th class="px-16 py-2" style="width: 40%">
<span class="text-white">JUDUL
CAMPAIGN</span>
</th>
<th class="px-16 py-2 text-left">
<span class="text-white">KATEGORI</span>
</th>
<th class="px-16 py-2 text-left">
<span class="text-white">TARGET
DONASI</span>
</th>
<th class="px-16 py-2 text-left">
<span class="text-white">TANGGAL
BERAKHIR</span>
</th>
<th class="px-16 py-2">
<span class="text-white">AKSI</span>
</th>
</tr>

189
</thead>
<tbody class="bg-gray-200">
@forelse($campaigns as $campaign)
<tr class="border bg-white">
<td class="px-5 py-2">
{{ $campaign->title }}
</td>
<td class="px-16 py-2">
{{ $campaign->category->name }}
</td>
<td class="px-16 py-2">
{{
moneyFormat($campaign->target_donation) }}
</td>
<td class="px-16 py-2">
{{ $campaign->max_date }}
</td>
<td class="px-10 py-2 text-center">
<a href="{{
route('admin.campaign.edit', $campaign->id) }}" class="bg-indigo-600
px-4 py-2 rounded shadow-sm text-xs text-white focus:outline-
none">EDIT</a>
<button onClick="destroy(this.id)"
id="{{ $campaign->id }}" class="bg-red-600 px-4 py-2 rounded shadow-sm
text-xs text-white focus:outline-none">HAPUS</button>
</td>
</tr>
@empty
<div class="bg-red-500 text-white text-
center p-3 rounded-sm shadow-md">
Data Belum Tersedia!
</div>
@endforelse
</tbody>
</table>
@if ($campaigns->hasPages())
<div class="bg-white p-3">
{{
$campaigns->links('vendor.pagination.tailwind') }}
</div>
@endif
</div>
</div>
</div>
</main>
<script>

190
//ajax delete
function destroy(id) {
var id = id;
var token = $("meta[name='csrf-token']").attr("content");

Swal.fire({
title: 'APAKAH KAMU YAKIN ?',
text: "INGIN MENGHAPUS DATA INI!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
cancelButtonText: 'BATAL',
confirmButtonText: 'YA, HAPUS!',
}).then((result) => {
if (result.isConfirmed) {
//ajax delete
jQuery.ajax({
url: `/admin/campaign/${id}`,
data: {
"id": id,
"_token": token
},
type: 'DELETE',
success: function (response) {
if (response.status == "success") {
Swal.fire({
icon: 'success',
title: 'BERHASIL!',
text: 'DATA BERHASIL DIHAPUS!',
showConfirmButton: false,
timer: 3000
}).then(function () {
location.reload();
});
} else {
Swal.fire({
icon: 'error',
title: 'GAGAL!',
text: 'DATA GAGAL DIHAPUS!',
showConfirmButton: false,
timer: 3000
}).then(function () {
location.reload();
});
}

191
}
});
}
})
}
</script>
@endsection

Di atas, pertama kita atur agar halaman ini menggunakan layout dari app dan di dalamnya
kita buat array yang bernama title dan memiliki value Campaign - Admin, array ini
akan di tampilkan di layout untuk title dari website.

@extends('layouts.app', ['title' => 'Campaign - Admin'])

Selanjutnya kita juga menambahkan 1 button yang digunakan untuk mengarahkan ke


halaman tambah campaign, dimana button ini kita berikan sebuah route yang bernama
admin.campaign.create.

<button class="text-white focus:outline-none bg-gray-600 px-4 py-2


shadow-sm rounded-md">
<a href="{{ route('admin.campaign.create') }}">TAMBAH</a>
</button>

Selanjutnya, untuk proses pencarian data, kita membuat sebuah form yang di arahkan ke
dalam sebuah route yang bernama admin.campaign.index dan form ini menggunakan
method GET. Di dalam form ini kita memiliki sebuah input yang menggunakan name q,
dimana input inilah yang akan di tangkap di dalam controller sebagai request dengan nama
q.

<form action="{{ route('admin.campaign.index') }}" method="GET">


<input class="form-input w-full rounded-lg pl-10 pr-4" type="text"
name="q" value="{{ request()->query('q') }}" placeholder="Search">
</form>

Dan untuk menampilkan data, kita menggunakan sintaks dari blade yaitu forelse. Kurang
lebih seperti berikut ini :

192
@forelse($campaigns as $no => $campaign)
//kode untuk perulangan data
@empty

//kode untuk menampilkan pesan, jika data belum tersedia

@endforelse

Untuk edit data campaign, kita akan memanggil route yang bernama
admin.campaign.edit dan ditambahkan sebuah parameter berupa ID dari data
campaign.

<a href="{{ route('admin.campaign.edit', $campaign->id) }}" class="bg-


indigo-600 px-4 py-2 rounded shadow-sm text-xs text-white focus:outline-
none">EDIT</a>

Dan untuk proses delete data campaign, kita akan menggunakan AJAX dengan
dikombinasikan Sweet Alert2. Kurang lebih seperti berikut ini :

<button onClick="destroy(this.id)" id="{{ $campaign->id }}" class="bg-


red-600 px-4 py-2 rounded shadow-sm text-xs text-white focus:outline-
none">HAPUS</button>

Pada button delete data di atas, kita memanggil sebuah function JavaScript yang bernama
destroy dan di dalamnya kita berikan sebuah parameter yang di ambil dari attribute
id="{{ $campaign->id }}", yang mana isinya adalah ID dari campaign.

Dan untuk function destroy di JavaScript kita kombinasikan dengan library Sweet Alert 2
agar bisa menampilkan sebuah popup konfirmasi.

function destroy(id) {

//ajax + sweet alert 2 confirmation

Pada bagian Ajax kita arahkan URL untuk menghapus datanya ke dalam :

193
/admin/campaign/${id}. Dimana $id berisi ID dari campaign. Dan untuk method yang
digunakan adalah DELETE.

jQuery.ajax({
url: `/admin/campaign/${id}`, // <-- URL/endpoint untuk delete
data: {
"id": id, // <-- ID campaign
"_token": token // < -- CSRF Token
},
type: 'DELETE', // < -- method DELETE
...

Dan untuk menampilkan pagination, kita menggunakan kode seperti berikut ini :

{{ $campaigns->links('vendor.pagination.tailwind') }}

Sekarang kita coba jalankan campaign-nya di http://localhost:8000/admin/campaign, Jika


berhasil, maka kurang lebih tampilannya seperti berikut ini :

Di atas muncul message Data Belum Tersedia! karena memang kita belum
memasukkan data apapun di dalam table campaigns.

194
Langkah 4 - Membuat View/Halaman Create Campaign
Setelah berhasil membuat view untuk menampilkan halaman index campaign, sekarang kita
lanjutkan untuk membuat view untuk menampilkan halaman tambah data campaign.
Silahkan buat file baru dengan nama create.blade.php di dalam folder
resources/views/admin/campaign dan masukkan kode berikut ini :

@extends('layouts.app', ['title' => 'Tambah Campaign - Admin'])

@section('content')
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-300">
<div class="container mx-auto px-6 py-8">

<div class="p-6 bg-white rounded-md shadow-md">


<h2 class="text-lg text-gray-700 font-semibold
capitalize">TAMBAH CAMPAIGN</h2>
<hr class="mt-4">
<form action="{{ route('admin.campaign.store') }}"
method="POST" enctype="multipart/form-data">
@csrf
<div class="grid grid-cols-1 gap-6 mt-4">
<div>
<label class="text-gray-700"
for="image">GAMBAR</label>
<input class="form-input w-full mt-2 rounded-md
bg-gray-200 focus:bg-white p-3" type="file" name="image">
@error('image')
<div class="w-full bg-red-200 shadow-sm
rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>

<div>
<label class="text-gray-700" for="name">JUDUL
CAMPAIGN</label>
<input class="form-input w-full mt-2 rounded-md
bg-gray-200 focus:bg-white" type="text" name="title" value="{{
old('title') }}" placeholder="Judul Campaign">
@error('title')

195
<div class="w-full bg-red-200 shadow-sm
rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>

<div>
<label class="text-gray-700"
for="name">KATEGORI</label>
<select class="w-full border bg-gray-200
focus:bg-white rounded px-3 py-2 outline-none" name="category_id">
@foreach($categories as $category)
<option class="py-1" value="{{
$category->id }}">{{ $category->name }}</option>
@endforeach
</select>
@error('category_id')
<div class="w-full bg-red-200 shadow-sm
rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>

<div>
<label class="text-gray-700"
for="name">DESKRIPSI</label>
<textarea name="description" rows="5">{{
old('description') }}</textarea>
@error('description')
<div class="w-full bg-red-200 shadow-sm
rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>

196
<div>
<label class="text-gray-700" for="name">TARGET
DONASI</label>
<input class="form-input w-full mt-2 rounded-md
bg-gray-200 focus:bg-white p-3" type="number" name="target_donation"
value="{{ old('target_donation') }}" placeholder="Target Donasi, Ex:
10000000">
@error('target_donation')
<div class="w-full bg-red-200 shadow-sm
rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>

<div>
<label class="text-gray-700" for="name">TANGGAL
BERAKHIR</label>
<input class="form-input w-full mt-2 rounded-md
bg-gray-200 focus:bg-white p-3" type="date" name="max_date" value="{{
old('max_date') }}">
@error('max_date')
<div class="w-full bg-red-200 shadow-sm
rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>

</div>

<div class="flex justify-start mt-4">


<button type="submit" class="px-4 py-2 bg-gray-600
text-gray-200 rounded-md hover:bg-gray-700 focus:outline-none focus:bg-
gray-700">SIMPAN</button>
</div>
</form>
</div>
</div>
</main>

197
<script
src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/5.7.0/tinymce.min.js
"></script>
<script>
tinymce.init({selector:'textarea'});
</script>
@endsection

Di atas, pertama kita atur agar halaman ini menggunakan layout dari app dan di dalamnya
kita buat array yang bernama title dan memiliki value Tambah Campaign - Admin,
array ini akan di tampilkan di layout untuk title dari website.

@extends('layouts.app', ['title' => 'Tambah Campaign - Admin'])

Kemudian untuk form action kita arahkan ke dalam sebuah route yang bernama
admin.campaign.store dan di dalam form ini kita menggunakan method POST.

<form action="{{ route('admin.campaign.store') }}" method="POST"


enctype="multipart/form-data">

Dan jangan lupa jika di dalam form tersebut kita melakukan sebuah upload file/image, maka
harus menambahkan attribute HTML enctype="multipart/form-data", jika tidak maka
file/image yang akan diupload tidak akan terbaca.

Kemudian pada input DESKRIPSI kita format menggunkan library tambahan, yaitu
TinyMCE. Kurang lebih seperti berikut ini :

<script
src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/5.7.0/tinymce.min.js
"></script>
<script>
tinymce.init({selector:'textarea'});
</script>

Kode di atas akan melakukan init library TinyMCE ke dalam tag HTML yaitu textarea dan
kebetulan untuk DESKRIPSI kita menggunakan jenis input textarea.

Sekarang jika kita klik button TAMBAH atau buka link berikut ini

198
http://localhost:8000/admin/campaign/create, maka kurang lebih tampilannya seperti ini :

Dan jika kita coba klik button SIMPAN tanpa mengisi data apapun, maka kita akan
mendapatkan sebuah error validasi kurang lebih seperti berikut ini :

GAMBAR CONTOH CAMPAIGN :


https://drive.google.com/file/d/13OV5kaBCckPgEdkRLp9VEg2duEEE4Nn6/view?usp=sharing

Sekarang, kita coba isi semua input yang ada dan jika berhasil maka kurang lebih seperti
berikut ini :

199
Langkah 5 - Membuat View/Halaman Edit Campaign
Setelah berhasil membuat view untuk menampilkan halaman tambah campaign, sekarang
kita lanjutkan untuk membuat view lagi untuk menampilkan form edit data campaign.

Silahkan buat file baru dengan nama edit.blade.php di dalam folder


resources/views/admin/campaign dan masukkan kode berikut ini :

@extends('layouts.app', ['title' => 'Edit Campaign - Admin'])

@section('content')
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-300">
<div class="container mx-auto px-6 py-8">

<div class="p-6 bg-white rounded-md shadow-md">


<h2 class="text-lg text-gray-700 font-semibold
capitalize">EDIT CAMPAIGN</h2>
<hr class="mt-4">
<form action="{{ route('admin.campaign.update',
$campaign->id) }}" method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
<div class="grid grid-cols-1 gap-6 mt-4">
<div>
<label class="text-gray-700"
for="image">GAMBAR</label>

200
<input class="form-input w-full mt-2 rounded-md
bg-gray-200 focus:bg-white p-3" type="file" name="image">
</div>

<div>
<label class="text-gray-700" for="name">JUDUL
CAMPAIGN</label>
<input class="form-input w-full mt-2 rounded-md
bg-gray-200 focus:bg-white" type="text" name="title" value="{{
old('title', $campaign->title) }}" placeholder="Judul Campaign">
@error('title')
<div class="w-full bg-red-200 shadow-sm
rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>

<div>
<label class="text-gray-700"
for="name">KATEGORI</label>
<select class="w-full border bg-gray-200
focus:bg-white rounded px-3 py-2 outline-none" name="category_id">
@foreach($categories as $category)
<option class="py-1" value="{{
$category->id }}" @if($campaign->category_id == $category->id) selected
@endif>{{ $category->name }}</option>
@endforeach
</select>
@error('category_id')
<div class="w-full bg-red-200 shadow-sm
rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>

<div>
<label class="text-gray-700"
for="name">DESKRIPSI</label>

201
<textarea name="description" rows="5">{{
old('description', $campaign->description) }}</textarea>
@error('description')
<div class="w-full bg-red-200 shadow-sm
rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>

<div>
<label class="text-gray-700" for="name">TARGET
DONASI</label>
<input class="form-input w-full mt-2 rounded-md
bg-gray-200 focus:bg-white p-3" type="number" name="target_donation"
value="{{ old('target_donation', $campaign->target_donation) }}"
placeholder="Target Donasi, Ex: 10000000">
@error('target_donation')
<div class="w-full bg-red-200 shadow-sm
rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>

<div>
<label class="text-gray-700" for="name">TANGGAL
BERAKHIR</label>
<input class="form-input w-full mt-2 rounded-md
bg-gray-200 focus:bg-white p-3" type="date" name="max_date" value="{{
old('max_date', $campaign->max_date) }}">
@error('max_date')
<div class="w-full bg-red-200 shadow-sm
rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror

202
</div>

</div>

<div class="flex justify-start mt-4">


<button type="submit" class="px-4 py-2 bg-gray-600
text-gray-200 rounded-md hover:bg-gray-700 focus:outline-none focus:bg-
gray-700">UPDATE</button>
</div>
</form>
</div>
</div>
</main>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/5.7.0/tinymce.min.js
"></script>
<script>
tinymce.init({selector:'textarea'});
</script>
@endsection

Di atas, pertama kita atur agar halaman ini menggunakan layout dari app dan di dalamnya
kita buat array yang bernama title dan memiliki value Edit Campaign - Admin, array
ini akan di tampilkan di layout untuk title dari website.

@extends('layouts.app', ['title' => 'Edit Campaign - Admin'])

Kemudian pada form action kita arahkan ke dalam sebuah route yang bernama
admin.campaign.update dan di dalam route tersebut kita tambahkan sebuah parameter
yaitu ID dari data campaign.

<form action="{{ route('admin.campaign.update', $campaign->id) }}"


method="POST" enctype="multipart/form-data">

Dan jangan lupa, ketika membuat form untuk proses edit dan update data, selalu
tambahkan sintaks berikut ini :

203
@method('PUT')

Sekarang kita coba untuk klik button edit dan jika berhasil maka kurang lebih tampilannya
seperti berikut ini :

Sekarang silahkan ubah title atau data apapun dan klik button UPDATE, jika berhasil maka
kurang lebih tampilannya seperti berikut ini :

Terakhir, kita akan uji coba untuk proses delete data campaign, silahkan klik button delete,

204
maka kita akan mendapatkan sebuah konfirmasi delete menggunakan Sweet Alert2,
kurang lebih seperti berikut ini :

205
Menampilkan Data Donatur

Pada tahap kali ini kita semua akan belajar bagaimana cara menampilkan data donatur,
disini kita hanya akan menampilkan data donatur saja tanpa membuat aksi apapun, karena
data ini nanti akan diambil dari donatur saat melakukan proses registrasi.

Langkah 1 - Membuat Controller Donatur


Sekarang silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:controller Admin\\DonaturController

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
yaitu DonaturController yang berada di dalam foler app/Http/Controllers/Admin.
Silahkan buka file tersebut dan masukkan kode berikut ini :

206
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Donatur;

class DonaturController extends Controller


{
/**
* index
*
* @return void
*/
public function index()
{
$donaturs = Donatur::latest()->when(request()->q,
function($donaturs) {
$donaturs = $donaturs->where('name', 'like', '%'.
request()->q . '%');
})->paginate(10);

return view('admin.donatur.index', compact('donaturs'));


}
}

Dari penambahan kode di atas, pertama kita melakukan import Model Donatur, karena
nanti kita akan memanfaatkan Model ini untuk mendapatkan data donatur dari database.

use App\Models\Donatur;

Selanjutnya, di dalam controller DonaturController kita menambahkan 1 method baru


yaitu function index, dimana di dalamnya kita akan melakukan proses get data donatur
dari database. Dan kita juga membuat sebuah kondisi untuk proses pencarian data
menggunakan function when.

Jika ada sebuah request dengan nama q, maka kita akan melakukan pencarian data donatur
berdasarkan nama yang di cocokan dengan value dari request yang bernama q tersebut.
Kemudian kita batasi untuk jumlah yang akan ditampilkan pada halaman menggunakan
function paginate. Dalam contoh kode di atas, kita atur untuk data yang ditampilkan
perhalaman adalah 10.

207
$donaturs = Donatur::latest()->when(request()->q, function($donaturs) {
$donaturs = $donaturs->where('name', 'like', '%'. request()->q .
'%');
})->paginate(10);

Setelah itu, kita akan lakukan parsing variable $donaturs di atas ke dalam sebuah view
yang berada di dalam folder admin.donatur.index menggunakan function compact.

return view('admin.donatur.index', compact('donaturs'));

Langkah 2 - Membuat Route Donatur


Setelah berhasil membuat controller, sekarang kita lanjutkan untuk menambahkan sebuah
route, Silahkan buka file routes/web.php kemudian tambahkan route berikut ini di dalam
prefix admin dan middleware auth dan tepat di bawahnya route campaign.

//route donatur
Route::get('/donatur', [DonaturController::class,
'index'])->name('admin.donatur.index');

Di atas kita menambahkan sebuah route baru untuk menampilkan halaman donatur, untuk
melihat route tersebut apakah sudah berhasil, kita bisa menjalankan perintah berikut ini di
dalam terminal/CMD :

php artisan route:list

![](/home/maulayyacyber/Pictures/Ebook - Website Donasi Online/route-list-donatur.png)

Setelah menambahkan route untuk donatur, selanjutnya kita akan mengaktifkan menu
sidebar admin agar saat di klik akan menuju ke halaman donatur.

Silahkan buka file resources/views/layouts/app.blade.php dan cari kode berikut ini


:

208
<a class="flex items-center mt-4 py-2 px-6 hover:bg-gray-700 hover:bg-
opacity-25 hover:text-gray-100 {{ Request::is('admin/donatur*') ? ' bg-
gray-700 bg-opacity-25 text-gray-100' : 'text-gray-500' }}" href="#">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0
24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-
width="2"
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6
0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z">
</path>
</svg>
<span class="mx-3">Donaturs</span>
</a>

Di atas, bisa kita perhatikan, untuk URL dari menu donatur masih menggunakan href="#",
sekarang kita ubah kode di atas, menjadi seperti berikut ini :

<a class="flex items-center mt-4 py-2 px-6 hover:bg-gray-700 hover:bg-


opacity-25 hover:text-gray-100 {{ Request::is('admin/donatur*') ? ' bg-
gray-700 bg-opacity-25 text-gray-100' : 'text-gray-500' }}" href="{{
route('admin.donatur.index') }}">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0
24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-
width="2"
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6
0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z">
</path>
</svg>
<span class="mx-3">Donaturs</span>
</a>

Di atas, kita ubah yang semula adalah href="#" menjadi href="{{


route('admin.donatur.index') }}".

Langkah 3 - Membuat View/Halaman Index Donatur


Sekarang kita lanjutkan untuk membuat view untuk menampilkan halaman index donatur.
Silahkan buat folder baru dengan nama donatur di dalam folder

209
resources/views/admin. Dan di dalam folder donatur silahkan buat file baru dengan
nama index.blade.php dan masukkan kode berikut ini :

@extends('layouts.app', ['title' => 'Donatur - Admin'])

@section('content')
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-300">
<div class="container mx-auto px-6 py-8">

<div class="flex items-center">


<div class="relative">
<span class="absolute inset-y-0 left-0 pl-3 flex items-
center">
<svg class="h-5 w-5 text-gray-500" viewBox="0 0 24
24" fill="none">
<path
d="M21 21L15 15M17 10C17 13.866 13.866 17 10
17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401
17 10Z"
stroke="currentColor" stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</span>
<form action="{{ route('admin.donatur.index') }}"
method="GET">
<input class="form-input w-full rounded-lg pl-10
pr-4" type="text" name="q" value="{{ request()->query('q') }}"
placeholder="Search">
</form>
</div>
</div>

<div class="-mx-4 sm:-mx-8 px-4 sm:px-8 py-4 overflow-x-auto">


<div class="inline-block min-w-full shadow-sm rounded-lg
overflow-hidden">
<table class="min-w-full table-auto">
<thead class="justify-between">
<tr class="bg-gray-600 w-full">
<th class="px-16 py-2">
<span class="text-white">NAMA
LENGKAP</span>
</th>
<th class="px-16 py-2 text-left">
<span class="text-white">EMAIL</span>

210
</th>
</tr>
</thead>
<tbody class="bg-gray-200">
@forelse($donaturs as $donatur)
<tr class="border bg-white">
<td class="px-5 py-2">
{{ $donatur->name }}
</td>

<td class="px-16 py-2">


{{ $donatur->email }}
</td>

</tr>
@empty
<div class="bg-red-500 text-white text-
center p-3 rounded-sm shadow-md">
Data Belum Tersedia!
</div>
@endforelse
</tbody>
</table>
@if ($donaturs->hasPages())
<div class="bg-white p-3">
{{
$donaturs->links('vendor.pagination.tailwind') }}
</div>
@endif
</div>
</div>
</div>
</main>
@endsection

Di atas, pertama kita atur agar halaman ini menggunakan layout dari app dan di dalamnya
kita buat array yang bernama title dan memiliki value Donatur - Admin, array ini akan
di tampilkan di layout untuk title dari website.

@extends('layouts.app', ['title' => 'Donatur - Admin'])

Kemudian untuk pencarian data, kita membuat sebuah form dengan method GET dan untuk
action kita arahkan ke dalam sebuah route yang bernama admin.donatur.index.

211
<form action="{{ route('admin.donatur.index') }}" method="GET">
<input class="form-input w-full rounded-lg pl-10 pr-4" type="text"
name="q" value="{{ request()->query('q') }}" placeholder="Search">
</form>

Kemudian untuk input kita berikan sebuah name q, dimana input inilah yang akan di cek di
dalam controller sebagai request dengan nama q.

Dan untuk menampilkan data, kita menggunakan sintaks dari blade yaitu forelse. Kurang
lebih seperti berikut ini :

@forelse($donaturs as $no => $donatur)


//kode untuk perulangan data
@empty

//kode untuk menampilkan pesan, jika data belum tersedia

@endforelse

Dan untuk pagination, kita menggunakan kode seperti berikut ini :

{{ $donaturs->links('vendor.pagination.tailwind') }}

Sekarang, kita bisa mencoba menjalankannya di http://localhost:8000/admin/donatur atau


bisa melalui menu sidebar dari halaman admin, jika berhasil maka kurang lebih hasilnya
seperti berikut ini :

212
Di atas muncul message Data Belum Tersedia! karena memang kita belum memiliki
data apapun di dalam table donaturs.

213
Menampilkan Data Donasi

Pada tahap kali ini kita semua akan belajar bagaimana cara menampilkan data donasi yang
masuk di dalam website kita. Disini kita juga akan membuat sebuah fitur untuk melakukan
filter data donasi berdasarkaan range tanggal.

Langkah 1 - Membuat Controller Donation


Silahkan jalankan perintah di bawah ini di dalam terminal/CMD untuk membuat controller
baru :

php artisan make:controller Admin\\DonationController

Jika perintah di atas berhasil, maka kita akan mendapatkan 1 file baru dengan nama
DonationController.php di dalam folder app/Http/Controllers/Admin. Silahkan
buka file tersebut dan ubah kode-nya menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Admin;

use App\Models\Donation;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class DonationController extends Controller


{
/**
* index
*
* @return void
*/
public function index()
{
return view('admin.donation.index');
}
/**
* filter
*
* @param mixed $request

214
* @return void
*/
public function filter(Request $request)
{
$this->validate($request, [
'date_from' => 'required',
'date_to' => 'required',
]);

$date_from = $request->date_from;
$date_to = $request->date_to;

//get data donation by range date


$donations = Donation::where('status',
'success')->whereDate('created_at', '>=',
$request->date_from)->whereDate('created_at', '<=',
$request->date_to)->get();

//get total donation by range date


$total = Donation::where('status',
'success')->whereDate('created_at', '>=',
$request->date_from)->whereDate('created_at', '<=',
$request->date_to)->sum('amount');
return view('admin.donation.index', compact('donations',
'total'));
}
}

Dari penambahan kode di atas, pertama kita melakukan import untuk Model Donation,
karena kita akan menggunakan Model tersebut untuk mendapatkan data melalui database.

use App\Models\Donation;

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;

Kemudian di dalam controller DonationController kita menambahkan 2 method, yaitu :

215
function index
function filter

function index

Method ini digunakan untuk menampilkan halaman index dari donasi, dan di halaman ini
kita akan membuat sebuah input berupa range tanggal, yang mana akan digunakan untuk
melakukan filter terhadap data donasi yang akan ditampilkan.

return view('admin.donation.index');

function filter

Method ini akan digunakan untuk mendapatkan data donasi dari database berdasarkan
range tanggal, disini kita juga akan melakukan validasi terlebih dahulu.

$this->validate($request, [
'date_from' => 'required',
'date_to' => 'required',
]);

Dari kode validasi di atas, kurang lebih penjelasannya seperti berikut ini :

KEY VALIDASI KETERANGAN

date_from required field wajib diisi

date_to required field wajib diisi

Setelah validasi di atas terpenuhi, sekarang kita buat 2 varaibel untuk menampung request
yang dikirim melalui form.

$date_from = $request->date_from;
$date_to = $request->date_to;

Setelah itu, kita akan melakukan pencarian data donasi berdasarkan range tanggal yang
dikirim melalui form tersebut.

216
//get data donation by range date
$donations = Donation::where('status',
'success')->whereDate('created_at', '>=',
$request->date_from)->whereDate('created_at', '<=',
$request->date_to)->get();

Di atas, kita melakukan pencarian data donasi dari 2 tanggal dan yang memiliki status
success. Kemudian kita juga akan melakukan sum atau menjumlahkan semua donasi
berdasarkan 2 tanggal tersebut.

//get total donation by range date


$total = Donation::where('status', 'success')->whereDate('created_at',
'>=', $request->date_from)->whereDate('created_at', '<=',
$request->date_to)->sum('amount');

Setelah data berhasil didapatkan, selanjutnya kita parsing kedua variable diatas, yaitu
$donations dan $total ke dalam sebuah view yang berada di dalam folder
admin.donation.index menggunakan unction* compact.

return view('admin.donation.index', compact('donations', 'total'));

Langkah 2 - Membuat Route Index dan Filter Donation


Sekarang kita lanjutkan untuk membuat sebuah route untuk menampilkan index dan proses
filter data donasi. Silahkan buka file routes/web.php kemudian tambahkan route berikut
ini di dalam prefix admin dan middleware auth dan tepat di bawahnya route donatur.

//route donation
Route::get('/donation', [DonationController::class,
'index'])->name('admin.donation.index');
Route::get('/donation/filter', [DonationController::class,
'filter'])->name('admin.donation.firter');

Di atas kita melakukan definisi dari 2 route, yaitu index dan juga filter. Untuk melihat route
tersebut apakah sudah berhasil, kita bisa menjalankan perintah berikut ini di dalam
terminal/CMD :

217
php artisan route:list

Setelah menambahkan route untuk donation, selanjutnya kita akan mengaktifkan menu
sidebar admin agar saat di klik akan menuju ke halaman donation.

Silahkan buka file resources/views/layouts/app.blade.php dan cari kode berikut ini


:

<a class="flex items-center mt-4 py-2 px-6 hover:bg-gray-700 hover:bg-


opacity-25 hover:text-gray-100 {{ Request::is('admin/donation*') ? ' bg-
gray-700 bg-opacity-25 text-gray-100' : 'text-gray-500' }}" href="#">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0
24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-
width="2"
d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path>
</svg>
<span class="mx-3">Donation</span>
</a>

Di atas, bisa kita perhatikan, untuk URL dari menu donation masih menggunakan
href="#", sekarang kita ubah kode di atas, menjadi seperti berikut ini :

218
<a class="flex items-center mt-4 py-2 px-6 hover:bg-gray-700 hover:bg-
opacity-25 hover:text-gray-100 {{ Request::is('admin/donation*') ? ' bg-
gray-700 bg-opacity-25 text-gray-100' : 'text-gray-500' }}" href="{{
route('admin.donation.index') }}">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0
24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-
width="2"
d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path>
</svg>
<span class="mx-3">Donation</span>
</a>

Di atas, kita ubah yang semula adalah href="#" menjadi href="{{


route('admin.donation.index') }}".

Langkah 3 - Membuat View/Halaman Index Donation


Setelah berhasil membuat controller dan route, selanjutnya kita akan belajar bagaimana
cara membuat view untuk menampilkan data donasi menggunakan range tanggal. Silahkan
buat folder baru dengan nama donation di dalam folder resources/views/admin dan di
dalam folder donation silahkan buat file baru dengan nama index.blade.php dan
masukkan kode berikut ini :

@extends('layouts.app', ['title' => 'Donation - Admin'])

@section('content')
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-300">
<div class="container mx-auto px-6 py-8">
<form action="{{ route('admin.donation.firter') }}"
method="GET">
<div class="flex gap-6">

<div class="flex-auto">
<label class="text-gray-700" for="name">TANGGAL
AWAL</label>
<input class="form-input w-full mt-2 rounded-md bg-
white p-3 shadow-md" type="date" name="date_from"
value="{{ old('date_form') ??
request()->query('date_from') }}">
@error('date_from')

219
<div class="w-full bg-red-200 shadow-sm rounded-
md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>

<div class="flex-auto">
<label class="text-gray-700" for="name">TANGGAL
AKHIR</label>
<input class="form-input w-full mt-2 rounded-md bg-
white p-3 shadow-md" type="date" name="date_to"
value="{{ old('date_to') ??
request()->query('date_to') }}">
@error('date_to')
<div class="w-full bg-red-200 shadow-sm rounded-
md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>

<div class="flex-1">
<button type="submit"
class="mt-8 w-full p-3 bg-gray-600 text-gray-200
rounded-md shadow-md hover:bg-gray-700 focus:outline-none focus:bg-
gray-700">FILTER</button>
</div>

</div>
</form>

@if($donations ?? '')

@if(count($donations) > 0)

<div class="-mx-4 sm:-mx-8 px-4 sm:px-8 py-4 overflow-x-


auto">
<div class="inline-block min-w-full shadow-sm

220
rounded-lg overflow-hidden">
<table class="min-w-full table-auto">
<thead class="justify-between">
<tr class="bg-gray-600 w-full">
<th class="px-16 py-2">
<span class="text-white">NAMA
DONATUR</span>
</th>
<th class="px-16 py-2 text-left">
<span class="text-
white">CAMPAIGN</span>
</th>
<th class="px-16 py-2 text-left">
<span class="text-
white">TANGGAL</span>
</th>
<th class="px-16 py-2 text-center">
<span class="text-white">JUMLAH
DONASI</span>
</th>
</tr>
</thead>
<tbody class="bg-gray-200">
@forelse($donations as $donation)
<tr class="border bg-white">
<td class="px-16 py-2 flex
justify-center">
{{ $donation->donatur->name
}}
</td>
<td class="px-16 py-2">
{{
$donation->campaign->title }}
</td>
<td class="px-16 py-2">
{{ $donation->created_at }}
</td>
<td class="px-5 py-2 text-
right">
{{
moneyFormat($donation->amount) }}
</td>
</tr>
@empty
<div class="bg-red-500 text-white
text-center p-3 rounded-sm shadow-md">

221
Data Belum Tersedia!
</div>
@endforelse
<tr class="border bg-gray-600 text-white
font-bold">
<td colspan="3" class="px-5 py-2
justify-center">
TOTAL DONASI
</td>
<td colspan="3" class="px-5 py-2
text-right">
{{ moneyFormat($total) }}
</td>
</tr>
</tbody>
</table>
</div>
</div>

@endif

@endif

</div>

</main>
@endsection

Di atas, pertama kita atur agar halaman ini menggunakan layout dari app dan di dalamnya
kita buat array yang bernama title dan memiliki value Donation - Admin, array ini
akan di tampilkan di layout untuk title dari website.

@extends('layouts.app', ['title' => 'Donation - Admin'])

Kemudian untuk filter data donasi kita membuat sebuah form yang menggunakan method
GET dan action kita arahkan ke sebuah route yang bernama admin.donation.filter.
Dan di dalamnya form ini kita memiliki 2 input, yaitu date_from dan date_to kedua input
inilah yang akan ditangkap oleh controller untuk di proses sebagaia parameter untuk
pencarian data.

222
<form action="{{ route('admin.donation.firter') }}" method="GET">

//...
</form

Kemduain kita juga menggunakan sebuah kondisi untuk menampilkan data donasi yang
telah difilter berdasarkan 2 tanggal di atas.

@if($donations ?? '')

@if(count($donations) > 0)
// data donasi
@endif

@endif

Jika nilai variable $donations di atas 0, maka artinya data donasi tersebut ada dan kita
akan menampilkan data tersebut menggunakan forelse.

223
@forelse($donations as $donation)
<tr class="border bg-white">
<td class="px-16 py-2 flex justify-center">
{{ $donation->donatur->name }}
</td>
<td class="px-16 py-2">
{{ $donation->campaign->title }}
</td>
<td class="px-16 py-2">
{{ $donation->created_at }}
</td>
<td class="px-5 py-2 text-right">
{{ moneyFormat($donation->amount) }}
</td>
</tr>
@empty

<div class="bg-red-500 text-white text-center p-3 rounded-sm


shadow-md">
Data Belum Tersedia!
</div>

@endforelse

Di atas, untuk menampilkan nama dari donatur, kita memanggil relasi yang sebelumnya
sudah kita definisikan di dalam Model Donation.

{{ $donation->donatur->name }}

donatur merupakan method yang kita buat di dalam Model Donation, dimana method
tersebut membuat sebuah relasi ke dalam Model Donatur.

kemudian, kita juga memanggil relasi data campaign, kurang lebih seperti berikut ini :

{{ $donation->campaign->title }}

campaign merupakan method yang kita buat di dalam Model Donation, dimana method
tersebut membuat sebuah relasi ke dalam Model Campaign.

Sekarang, kita bisa mencoba menjalankannya di http://localhost:8000/admin/donation atau

224
bisa melalui menu sidebar dari halaman admin, jika berhasil maka kurang lebih hasilnya
seperti berikut ini :

225
Profile User dan Two Factor Authentication

Pada tahap kali ini kita semua akan belajar bagaimana cara menampilkan profile user yang
sedang login, meng-update data profile, memperbarui password dan yang terakir adalah
mengaktifkan fitur two factor authentication.

Ketika mengaktifkan fitur two factor authentication, maka kita harus memindai QR Code
yang diberikan menggunakan aplikasi authenticator gratis seperti Google Authenticator.
Selain itu, kita juga harus menyimpan code recovery yang di sediakan, ini digunakan
untuk antisipasi jika kita kehilangan akses dari perangkat mobile.

CATATAN!: silahkan install Google Authenticator di Handphone masing-masing.

Dan pada tahap ini kita tidak perlu membuat beberapa method di dalam controller seperti
sebelumnya. Untuk proses update data profile, update password dan mengaktifkan two
factor authentication kita akan memanfaatkan fitur-fitur yang sudah disediakan oleh Laravel
Fortify.

Langkah 1 - Membuat Controller Profile


Sekarang, silahkan jalankan perintah berikut ini di dalam terminal/CMD untuk membuat
controller profile :

php artisan make:controller Admin\\ProfileController

Jika perintah di atas berhasil, maka kita akan mendapatkan controller baru dengan nama
ProfileController.php di dalam folder app/Http/Controllers/Admin. Silahkan
buka file tersebut dan ubah kode-nya menjadi seperti berikut ini :

226
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;

class ProfileController extends Controller


{
/**
* index
*
* @return void
*/
public function index()
{
return view('admin.profile.index');
}
}

Dari penambahan kode di atas, kita hanya menambahkan 1 method baru, yaitu function
index, dimana di dalam method ini kita melakukan sebuah return untuk mengembalikan ke
dalam view yang berada di dalam folder admin.profile.index.

Langkah 2 - Membuat Route Profile


Selanjutnya, kita akan belajar untuk menambahkan route baru untuk profile. Silahkan buka
file routes/web.php kemudian tambahkan route berikut ini di dalam prefix admin dan
middleware auth dan tepat di bawahnya route donation.

//route profile
Route::get('/profile', [ProfileController::class,
'index'])->name('admin.profile.index');

Untuk melihat route tersebut apakah sudah berhasil, kita bisa menjalankan perintah berikut
ini di dalam terminal/CMD :

php artisan route:list

227
Setelah menambahkan route untuk profile, selanjutnya kita akan mengaktifkan menu
sidebar admin agar saat di klik akan menuju ke halaman profile.

Silahkan buka file resources/views/layouts/app.blade.php dan cari kode berikut ini


:

<a class="flex items-center mt-4 py-2 px-6 hover:bg-gray-700 hover:bg-


opacity-25 hover:text-gray-100 {{ Request::is('admin/profile*') ? ' bg-
gray-700 bg-opacity-25 text-gray-100' : 'text-gray-500' }}" href="#">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0
24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-
width="2"
d="M5.121 17.804A13.937 13.937 0 0112 16c2.5
0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18
0 9 9 0 0118 0z">
</path>
</svg>
<span class="mx-3">Profil Saya</span>
</a>

Di atas, bisa kita perhatikan, untuk URL dari menu profile masih menggunakan href="#",
sekarang kita ubah kode di atas, menjadi seperti berikut ini :

228
<a class="flex items-center mt-4 py-2 px-6 hover:bg-gray-700 hover:bg-
opacity-25 hover:text-gray-100 {{ Request::is('admin/profile*') ? ' bg-
gray-700 bg-opacity-25 text-gray-100' : 'text-gray-500' }}" href="{{
route('admin.profile.index') }}">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0
24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-
width="2"
d="M5.121 17.804A13.937 13.937 0 0112 16c2.5
0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18
0 9 9 0 0118 0z">
</path>
</svg>
<span class="mx-3">Profil Saya</span>
</a>

Di atas, kita ubah yang semula adalah href="#" menjadi href="{{


route('admin.profile.index') }}".

Langkah 3 - Membuat View/Halaman Profile


Sekarang, kita lanjutkan untuk membuat view untuk menampilkan halaman profile, di dalam
halaman ini akan memuat beberapa fitur, yaitu, update profile, update password dan two
factor authentication.

Silahkan buat folder baru dengan nama profile di dalam folder


resources/views/admin dan di dalam folder profile silahkan buat file baru dengan
nama index.blade.php dan masukkan kode berikut ini :

@extends('layouts.app', ['title' => 'Profile - Admin'])

@section('content')
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-300">
<div class="container mx-auto px-6 py-8">

@if (session('status'))
<div class="bg-green-500 p-3 rounded-md shadow-sm mt-3">
@if (session('status')=='profile-information-updated')
Profile has been updated.
@endif
@if (session('status')=='password-updated')

229
Password has been updated.
@endif
@if (session('status')=='two-factor-authentication-
disabled')
Two factor authentication disabled.
@endif
@if (session('status')=='two-factor-authentication-enabled')
Two factor authentication enabled.
@endif
@if (session('status')=='recovery-codes-generated')
Recovery codes generated.
@endif
</div>
@endif

<div class="grid grid-cols-1 sm:grid-cols-2 gap-6 mt-4">

<div>
@if
(Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::twoFactorAu
thentication()))
<div class="p-6 bg-white rounded-md shadow-md">
<h2 class="text-lg text-gray-700 font-semibold
capitalize">WO-FACTOR AUTHENTICATION</h2>
<hr class="mt-4">

<div class="mt-4">
@if(! auth()->user()->two_factor_secret)
{{-- Enable 2FA --}}
<form method="POST" action="{{
url('user/two-factor-authentication') }}">
@csrf

<button type="submit"
class="px-4 py-2 bg-gray-600 text-
gray-200 rounded-md hover:bg-gray-700 focus:outline-none focus:bg-
gray-700">
Enable Two-Factor
</button>
</form>
@else
{{-- Disable 2FA --}}
<form method="POST" action="{{
url('user/two-factor-authentication') }}">
@csrf
@method('DELETE')

230
<button type="submit"
class="px-4 py-2 bg-red-600 text-
gray-200 rounded-md hover:bg-red-900 focus:outline-none focus:bg-
gray-700">
Disable Two-Factor
</button>
</form>

@if(session('status') == 'two-factor-
authentication-enabled')
{{-- Show SVG QR Code, After Enabling 2FA --
}}
<div class="mt-4">
Otentikasi dua faktor sekarang
diaktifkan. Pindai kode QR berikut menggunakan aplikasi
pengautentikasi ponsel Anda.
</div>

<div class="mb-3 mt-4">


{!! auth()->user()->twoFactorQrCodeSvg()
!!}
</div>
@endif

{{-- Show 2FA Recovery Codes --}}


<div class="mt-4">
Simpan recovery code ini dengan aman.
Ini dapat digunakan untuk memulihkan akses ke akun
Anda jika perangkat otentikasi dua
faktor Anda hilang.
</div>

<div style="background: rgb(44, 44,


44);color:white" class="rounded p-3 mb-2 mt-4">
@foreach
(json_decode(decrypt(auth()->user()->two_factor_recovery_codes), true)
as $code)
<div>{{ $code }}</div>
@endforeach
</div>

{{-- Regenerate 2FA Recovery Codes --}}


<form method="POST" action="{{
url('user/two-factor-recovery-codes') }}">
@csrf

231
<button type="submit"
class="mt-4 px-4 py-2 bg-gray-600
text-gray-200 rounded-md hover:bg-gray-700 focus:outline-none focus:bg-
gray-700">
Regenerate Recovery Codes
</button>
</form>
@endif
</div>

</div>
@endif
</div>

<div>
<div class="p-6 bg-white rounded-md shadow-md">
<h2 class="text-lg text-gray-700 font-semibold
capitalize">EDIT PROFILE</h2>
<hr class="mt-4">
<form class="mt-4" action="{{ route('user-profile-
information.update') }}" method="POST">
@csrf
@method('PUT')
<div>
<label class="block">
<span class="text-gray-700 text-sm">Nama
Lengkap</span>
<input type="text" name="name" value="{{
old('name') ?? auth()->user()->name }}"
class="form-input mt-1 block w-full
rounded-md" placeholder="Nama Lengkap">
@error('name')
<div
class="inline-flex max-w-sm w-full
bg-red-200 shadow-sm rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-
sm">{{ $message }}</p>
</div>
</div>
@enderror
</label>
</div>
<div class="mt-4">
<label class="block">
<span class="text-gray-700 text-

232
sm">Alamat Email</span>
<input type="email" name="email"
value="{{ old('email') ?? auth()->user()->email }}"
class="form-input mt-1 block w-full
rounded-md" placeholder="Alamat Email">
@error('email')
<div
class="inline-flex max-w-sm w-full
bg-red-200 shadow-sm rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-
sm">{{ $message }}</p>
</div>
</div>
@enderror
</label>
</div>
<div class="flex justify-start mt-4">
<button type="submit"
class="px-4 py-2 bg-gray-600 text-
gray-200 rounded-md hover:bg-gray-700 focus:outline-none focus:bg-
gray-700">UPDATE
PROFILE</button>
</div>
</form>
</div>

<div class="mt-6 p-6 bg-white rounded-md shadow-md">


<h2 class="text-lg text-gray-700 font-semibold
capitalize">UPDATE PASSWORD</h2>
<hr class="mt-4">
<form class="mt-4" action="{{ route('user-
password.update') }}" method="POST">
@csrf
@method('PUT')
<div>
<label class="block">
<span class="text-gray-700 text-
sm">Password Lama</span>
<input type="password"
name="current_password"
class="form-input mt-1 block w-full
rounded-md" placeholder="Password Lama">
</label>
</div>
<div class="mt-4">

233
<label class="block">
<span class="text-gray-700 text-
sm">Password Baru</span>
<input type="password" name="password"
class="form-input mt-1 block w-full rounded-md"
placeholder="Password Baru">
</label>
</div>
<div class="mt-4">
<label class="block">
<span class="text-gray-700 text-
sm">Konfirmasi Password Baru</span>
<input type="password"
name="password_confirmation"
class="form-input mt-1 block w-full
rounded-md"
placeholder="Konfirmasi Password
Baru">
</label>
</div>
<div class="flex justify-start mt-4">
<button type="submit"
class="px-4 py-2 bg-gray-600 text-
gray-200 rounded-md hover:bg-gray-700 focus:outline-none focus:bg-
gray-700">UPDATE
PASSWORD</button>
</div>
</form>
</div>
</div>

</div>

</div>
</main>
@endsection

Di atas, pertama kita atur agar halaman ini menggunakan layout dari app dan di dalamnya
kita buat array yang bernama title dan memiliki value Profile - Admin, array ini akan
di tampilkan di layout untuk title dari website.

234
@extends('layouts.app', ['title' => 'Profile - Admin'])

Di atas, kita membuat sebuah kondisi untuk menampilkan beberapa message dari Laravel
Fortify. Seperti berhasil meng-update profile, password dan mengaktifkan fitur two factor
authentication.

@if (session('status'))
<div class="bg-green-500 p-3 rounded-md shadow-sm mt-3">
@if (session('status')=='profile-information-updated')
Profile has been updated.
@endif
@if (session('status')=='password-updated')
Password has been updated.
@endif
@if (session('status')=='two-factor-authentication-disabled')
Two factor authentication disabled.
@endif
@if (session('status')=='two-factor-authentication-enabled')
Two factor authentication enabled.
@endif
@if (session('status')=='recovery-codes-generated')
Recovery codes generated.
@endif
</div>
@endif

Kemudian, disini kita membuat sebuah kode untuk mengecek apakah fitur two factow
authentication di aktifkan di dalam file config/fortify.php atau tidak.

@if(Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::twoFacto
rAuthentication()))

//kode untuk menampilkan konfigurasi two factor authentication


@endif

Kemudian, jika fitur two factor authentication di aktifkan di dalam file


config/fortify.php, maka sekarang kita buat kondisi lagi untuk cek apakah fitur
tersebut ini sudah di enable atau disable. Jika setatusnya masih disable, maka akan
menggunakan kode berikut ini untuk mengaktifkannya.

235
{{-- Enable 2FA --}}
<form method="POST" action="{{ url('user/two-factor-authentication')
}}">
@csrf
<button type="submit" class="px-4 py-2 bg-gray-600 text-gray-200
rounded-md hover:bg-gray-700 focus:outline-none focus:bg-gray-700">
Enable Two-Factor
</button>
</form>

Jika fitur tersebut sudah enable, maka untuk menon-aktifkannya kita menggunakan kode
berikut ini :

{{-- Disable 2FA --}}


<form method="POST" action="{{ url('user/two-factor-authentication')
}}">
@csrf
@method('DELETE')
<button type="submit" class="px-4 py-2 bg-red-600 text-gray-200
rounded-md hover:bg-red-900 focus:outline-none focus:bg-gray-700">
Disable Two-Factor
</button>
</form>

Dari kedua potongan kode di atas, kita sama-sama mengirimkan sebuah form ke dalam URL
user/two-factor-authentication. Dan jika kita periksa di dalam file
routes/web.php, maka URL tersebut tidak didefinisikan, itu karena kita menggunakan
fitur dari Laravel Fortify.

Jika kita ingin melihat route/URL tersebut di definisikan, silahkan cari di dalam folder
vendor/laravel/fortify/routes/routes.php, dengan catatan, kita tidak di
perbolehkan meng-edit kode di dalam folder vendor tersebut.

Kemudian, jika fitur two factor berhasil di enable, maka sekarang kita akan menampilkan
QR Code yang bisa kita scan menggunakan Google Authenticator. Ini akan kita gunakan
sebagai verifikasi 2 langkah setelah proses login.

236
@if(session('status') == 'two-factor-authentication-enabled')
{{-- Show SVG QR Code, After Enabling 2FA --}}
<div class="mt-4">
Otentikasi dua faktor sekarang diaktifkan. Pindai kode QR berikut
menggunakan aplikasi pengautentikasi ponsel Anda.
</div>

<div class="mb-3 mt-4">


{!! auth()->user()->twoFactorQrCodeSvg() !!}
</div>
@endif

Kemudian, kita juga akan menampilkan beberapa code recovery, ini digunakan untuk
antisipasi jika HP yang kita gunakan untuk scan QR Code hilang. Maka alternatifnya
menggunakan code recovery tersebut.

{{-- Show 2FA Recovery Codes --}}


<div class="mt-4">
Simpan recovery code ini dengan aman. Ini dapat digunakan untuk
memulihkan akses ke akun Anda jika perangkat otentikasi dua faktor Anda
hilang.
</div>

<div style="background: rgb(44, 44, 44);color:white" class="rounded p-3


mb-2 mt-4">
@foreach
(json_decode(decrypt(auth()->user()->two_factor_recovery_codes), true)
as $code)
<div>{{ $code }}</div>
@endforeach
</div>

Kita juga bisa melakukan regenerate code recovery. Untuk kode yang digunakan kurang
lebih seperti berikut ini :

237
{{-- Regenerate 2FA Recovery Codes --}}
<form method="POST" action="{{ url('user/two-factor-recovery-codes')
}}">
@csrf

<button type="submit" class="mt-4 px-4 py-2 bg-gray-600 text-gray-200


rounded-md hover:bg-gray-700 focus:outline-none focus:bg-gray-700">
Regenerate Recovery Codes
</button>
</form>

Kemudian, untuk update profile, form action akan kita arahkan ke sebuah route dengan
nama user-profile-information.update.

<form class="mt-4" action="{{ route('user-profile-information.update')


}}" method="POST">

Dan untuk form action yang digunakan untuk update password, kita arahkan ke route yang
bernama user-password.update.

<form method="POST" action="{{ route('user-password.update') }}">

Sekarang, kita bisa mencoba menjalankannya di http://localhost:8000/admin/profile atau


bisa melalui menu di sidebar admin, jika berhasil kurang lebih tampilannya seperti berikut
ini :

238
Sekarang bisa bisa coba mengaktifkan fitur two factor authentication dengan cara klik
button ENABLE TWO-FACTOR, maka kurang lebih kita akan mendpatkan error seperti
berikut ini :

Error tersebut muncul karena kita belum membuat view yang digunakan untuk proses
konfimasi password. Sekarang silahkan buat file baru dengan nama confirm-
password.blade.php di dalam folder resources/views/auth. Kemudian masukkan
kode berikut ini :

@extends('layouts.auth', ['title' => 'Confirm Password - Admin'])

239
@section('content')
<div class="flex justify-center items-center h-screen bg-gray-300 px-6">
<div class="p-6 max-w-sm w-full bg-white shadow-md rounded-md">
<div class="flex justify-center items-center">
<span class="text-gray-700 font-semibold text-2xl">CONFIRM
PASSWORD</span>
</div>
@if (session('status'))
<div class="bg-green-500 p-3 rounded-md shadow-sm mt-3">
{{ session('status') }}
</div>
@endif
<form class="mt-4" action="{{ route('password.confirm') }}"
method="POST">
@csrf

<label class="block mt-3">


<span class="text-gray-700 text-sm">Password</span>
<input type="password" name="password" class="form-input
mt-1 block w-full rounded-md focus:border-indigo-600"
placeholder="Password">
@error('password')
<div class="inline-flex max-w-sm w-full bg-red-200
shadow-sm rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{ $message
}}</p>
</div>
</div>
@enderror
</label>

<div class="mt-6">
<button type="submit"
class="py-2 px-4 text-center bg-indigo-600 rounded-
md w-full text-white text-sm hover:bg-indigo-500 focus:outline-none">
CONFIRM PASSWORD
</button>
</div>
</form>
</div>
</div>
@endsection

Sekarang, silahkan reload/refresh halaman error tersebut, jika berhasil maka kurang lebih

240
tampilannya seperti berikut ini :

Silahkan masukkan password sesuai dengan akun yang digunakan login dan klik CONFIRM
PASSSWORD, maka kita akan menemukan error lagi seperti berikut ini :

Di atas, karena kita belum menambahkan beberapa konfigurasi Laravel Fortify di dalam
model User. Silahkan buka file app/Models/User.php, kemudian import kode berikut ini :

241
use Laravel\Fortify\TwoFactorAuthenticatable;

Dan gunakan Traits tersebut di dalam Model User, kurang lebih seperti berikut ini :

class User extends Authenticatable


{
use HasFactory, Notifiable, TwoFactorAuthenticatable;

Sekarang kita coba lagi untuk enable two factor authentication. Jika berhasil maka kita
akan mendapatkan status untuk fitur two factor authentication telah enable dan kita
mendapatkan QR Code dan code recovery.

Silahkan scan QR Code tersebut dengan Google Authenticator.

Sekarang kita coba menguji fitur two factor authentication tersebut, silahkan logout dari
halaman admin dan silahkan login kembali. Maka kita akan mendapatkan error, kurang lebih
seperti berikut ini :

242
Error tersebut muncul karena kita belum membuat view untuk menampilkan halaman
verifikasi two factor authentication. Silahkan buat file baru dengan nama two-factor-
challenge.blade.php di dalam folder resources/views/auth dan masukkan kode
berikut ini :

@extends('layouts.auth', ['title' => 'Two Factor Challange - Admin'])

@section('content')
<div class="flex justify-center items-center h-screen bg-gray-300 px-6">
<div class="p-6 max-w-sm w-full bg-white shadow-md rounded-md">
<div class="flex justify-center items-center">
<span class="text-gray-700 font-semibold text-2xl">TWO
FACTOR CHALLANGE</span>
</div>
@if (session('status'))
<div class="bg-green-500 p-3 rounded-md shadow-sm mt-3">
{{ session('status') }}
</div>
@endif
<form class="mt-4" action="{{ url('/two-factor-challenge') }}"
method="POST">
@csrf
<label class="block">
<span class="text-gray-700 text-sm">Code</span>
<input type="text" name="code" value="{{ old('email')
}}"
class="form-input mt-1 block w-full rounded-md
focus:border-indigo-600" placeholder="Code">

243
@error('email')
<div class="inline-flex max-w-sm w-full bg-red-200
shadow-sm rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{ $message
}}</p>
</div>
</div>
@enderror
</label>

<p class="text-gray-600">
<i>Atau Anda dapat memasukkan salah satu recovery
code.</i>
</p>

<label class="block mt-3">


<span class="text-gray-700 text-sm">Recovery Code</span>
<input type="text" name="recovery_code" class="form-
input mt-1 block w-full rounded-md focus:border-indigo-600"
placeholder="Recovery Code">
@error('password')
<div class="inline-flex max-w-sm w-full bg-red-200
shadow-sm rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{ $message
}}</p>
</div>
</div>
@enderror
</label>

<div class="mt-6">
<button type="submit"
class="py-2 px-4 text-center bg-indigo-600 rounded-
md w-full text-white text-sm hover:bg-indigo-500 focus:outline-none">
LOGIN
</button>
</div>
</form>
</div>
</div>
@endsection

Kemudian silahkan refresh/reload halaman error tersebut, jika berhasil maka kurang lebih

244
tampilannya seperti berikut ini :

Silahkan masukkan kode yang di dapatkan dari Google Authenticator dan masukkan ke
dalam input Code atau bisa masukkan code recovery ke dalam input Recovery Code.
Jika berhasil maka teman-teman sudah bisa masuk ke dalam halaman dashboard.

245
Membuat CRUD Slider

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat CRUD untuk data
slider. Slider merupakan sebuah gambar yang ada di dalam sebuah website dan umumnya
digunakan untuk menampilkan informasi seputar promo, diskon, dan lain sebagainya.

Langkah 1 - Membuat Controller Slider


Sekarang, silahkan jalankan perintah di bawah ini di dalam terminal/CMD untuk membuat
controller baru :

php artisan make:controller Admin\\SliderController

Jika perintah di atas berhasil, maka kita akan mendapatkan 1 file controller baru dengan
nama SliderController.php yang berada di dalam folder
app/Http/Controllers/Admin. Silahkan buka controller tersebut dan ubah kode-nya
menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Admin;

use App\Models\Slider;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;

class SliderController extends Controller


{
/**
* index
*
* @return void
*/
public function index()
{
$sliders = Slider::latest()->paginate(5);
return view('admin.slider.index', compact('sliders'));
}
/**

246
* store
*
* @param mixed $request
* @return void
*/
public function store(Request $request)
{
$this->validate($request, [
'image' => 'required|image|mimes:jpeg,jpg,png|max:2000',
'link' => 'required'
]);

//upload image
$image = $request->file('image');
$image->storeAs('public/sliders', $image->hashName());

//save to DB
$slider = Slider::create([
'image' => $image->hashName(),
'link' => $request->link
]);

if($slider){
//redirect dengan pesan sukses
return
redirect()->route('admin.slider.index')->with(['success' => 'Data
Berhasil Disimpan!']);
}else{
//redirect dengan pesan error
return
redirect()->route('admin.slider.index')->with(['error' => 'Data Gagal
Disimpan!']);
}
}
/**
* destroy
*
* @param mixed $id
* @return void
*/
public function destroy($id)
{
$slider = Slider::findOrFail($id);
Storage::disk('local')->delete('public/sliders/'.basename($slider->image
));
$slider->delete();

247
if($slider){
return response()->json([
'status' => 'success'
]);
}else{
return response()->json([
'status' => 'error'
]);
}
}
}

Dari penambahan kode di atas, pertama kita melakukan import untuk Model Slider, karena
kita akan menggunakan Model ini untuk melakukan beberapa manipulasi data, seperti get,
insert, update sampai delete data.

use App\Models\Slider;

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;

Setelah itu kita import filesystem dari Laravel, ini bisa digunakan untuk menyimpan data ke
dalam local aplikasi maupun ke cloud seperti Amazon S3, Digital Ocean Spaces, dan lain-
lain.

use Illuminate\Support\Facades\Storage;

Dan di dalam controller SliderController kita menambahkan 3 method baru, yaitu :

function index
function store
function destroy

function index

Method ini akan digunakan untuk proses mendapatkan data slider dari database. Dan di

248
dalam method ini kita akan mengurutkan data slider yang ditampilkan berdasarkan yang
paling terbaru menggunakan function latest. Dan data yang akan ditampilkan perhalaman
akan dibatasi menggunakan function paginate.

$sliders = Slider::latest()->paginate(5);

Setelah itu, kita akan lakukan parsing variable $sliders di atas ke dalam sebuah view
yang berada di dalam folder admin.slider.index menggunakan function compact.

return view('admin.slider.index', compact('sliders'));

function store

Method ini akan digunakan untuk melakukan proses insert data slider ke dalam database, di
dalam method ini kita akan memberikan sebuah kondisi sebelum data benar-benar di insert
ke dalam database.

$this->validate($request, [
'image' => 'required|image|mimes:jpeg,jpg,png|max:2000',
'link' => 'required'
]);

Dari kode validasi di atas, kurang lebih penjelasannya seperti berikut ini :

KEY VALIDASI KETERANGAN

image required field wajib diisi

image field harus memiliki extensi gambar.

extensi gambar yang boleh diupload adalah


mimes:jpeg,jpg,png|max:2000
.jpeg, .jpg dan .png.

gambar yang boleh diupload maksimal


max:2000
memiliki ukuran 2000 Kb / 2 Mb.

link required field wajib diisi

Setelah validasi di atas terpenuhi, selanjutnya kita akan melakukan proses upload gambar
slider ke dalam server.

249
//upload image
$image = $request->file('image');
$image->storeAs('public/sliders', $image->hashName());

Di atas, pertama kita mengambil gambar dari sebuah request file yang bernama image dan
gambar-nya akan di simpan ke dalam server dan nama dari gambar tersebut akan dirandom
menggunakan function hashName.

Setelah gambar berhasil diupload ke server, selanjutnya kita melakukan proses inserta data
ke dalam database, kurang lebih seperti berikut ini :

//save to DB
$slider = Slider::create([
'image' => $image->hashName(),
'link' => $request->link
]);

Dari proses insert data di atas, kurang lebih seperti berikut ini penjelasannya :

ATTRIBUTE VALUE KETERANGAN

akan mengambil nama dari gambar yang


image $image->hashName()
diupload

akan mengambil data dari request yang bernama


link $request->link
link

Setelah itu, kita buat kondisi untuk memastikan apakah data yang di insert di atas berhasil
masuk atau tidak, kurang lebih seperti berikut ini :

if($slider){
//redirect dengan pesan sukses
return redirect()->route('admin.slider.index')->with(['success' =>
'Data Berhasil Disimpan!']);
}else{
//redirect dengan pesan error
return redirect()->route('admin.slider.index')->with(['error' =>
'Data Gagal Disimpan!']);
}

250
function destroy

Method ini digunakan untuk menghapus data slider dari database, selain itu kita juga akan
melakukan hapus data gambar slider yang terkait dengan data slider tersebut.

$slider = Slider::findOrFail($id); // <-- mencari slider berdasarkan ID

//hapus gambar
Storage::disk('local')->delete('public/sliders/'.basename($slider->image
));

//hapus slider
$slider->delete();

Jika proses hapus data berhasil, maka kita akan mengembalikan ke dalam format JSON
dengan status success.

return response()->json([
'status' => 'success'
]);

Dan jika proses hapus data gagal, maka akan mengembalikan ke dalam format JSON dengan
status error.

return response()->json([
'status' => 'error'
]);

Langkah 2 - Membuat Route Slider


Selanjutnya, kita akan menambahkan sebuah route untuk slider, disini kita akan
menggunakan route jenis resource, tapi kita tidak akan menggunakan semua fitur yang
ada di dalam route resource tersebut. Jadi nanti kita akan kecualikan beberapa route yang
tidak dibutuhkan menggunakan sintak except.

Silahkan buka file routes/web.php kemudian tambahkan route berikut ini di dalam prefix
admin dan middleware auth dan tepat di bawahnya route profile.

251
//route resource slider
Route::resource('/slider', SliderController::class, ['except' =>
['show', 'create', 'edit', 'update'], 'as' => 'admin']);

Bisa kita perhatikan dari penambahan route di atas, kita menambahkan except dengan
beberapa isi, diantaranya adalah :

show
create
edit
update

Route di atas merupakan route yang akan dikecualikan di dalam pendefinisian route
resource untuk slider. Jadi ke-empat route tersebut tidak akan ikut digenerate. Untuk
memastikannya, silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan route:list

Dari gambar di atas, bisa kita lihat bahwa kita hanya memiliki 3 route saja untuk halaman
slider, karena route yang lain sudah dikecualikan menggunakan except.

Setelah menambahkan route untuk slider, selanjutnya kita akan mengaktifkan menu sidebar
admin agar saat di klik akan menuju ke halaman slider.

Silahkan buka file resources/views/layouts/app.blade.php dan cari kode berikut ini


:

252
<a class="flex items-center mt-4 py-2 px-6 hover:bg-gray-700 hover:bg-
opacity-25 hover:text-gray-100 {{ Request::is('admin/slider*') ? ' bg-
gray-700 bg-opacity-25 text-gray-100' : 'text-gray-500' }}" href="#">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0
24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-
width="2"
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5
17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z">
</path>
</svg>
<span class="mx-3">Sliders</span>
</a>

Di atas, bisa kita perhatikan, untuk URL dari menu slider masih menggunakan href="#",
sekarang kita ubah kode di atas, menjadi seperti berikut ini :

<a class="flex items-center mt-4 py-2 px-6 hover:bg-gray-700 hover:bg-


opacity-25 hover:text-gray-100 {{ Request::is('admin/slider*') ? ' bg-
gray-700 bg-opacity-25 text-gray-100' : 'text-gray-500' }}" href="{{
route('admin.slider.index') }}">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0
24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-
width="2"
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5
17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z">
</path>
</svg>
<span class="mx-3">Sliders</span>
</a>

Di atas, kita ubah yang semula adalah href="#" menjadi href="{{


route('admin.slider.index') }}".

Langkah 3 - Membuat View/Halaman Slider


Sekarang kita lanjutkan untuk menambahkan sebuah view untuk menampilkan halaman
slider, disini kita akan membuat halaman dimana kita akan menampilkan data slider beserta

253
form untuk melakukan upload data slider.

Silahkan buat folder baru dengan nama slider di dalam folder resources/views/admin
dan di dalam folder slider silahkan buat file baru dengan nama index.blade.php dan
masukkan kode berikut ini :

@extends('layouts.app', ['title' => 'Slider - Admin'])

@section('content')
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-300">
<div class="container mx-auto px-6 py-8">

<div class="p-6 bg-white rounded-md shadow-md">


<h2 class="text-lg text-gray-700 font-semibold
capitalize">UPLOAD SLIDER</h2>
<hr class="mt-4">
<form action="{{ route('admin.slider.store') }}"
method="POST" enctype="multipart/form-data">
@csrf
<div class="grid grid-cols-1 gap-6 mt-4">
<div>
<label class="text-gray-700"
for="image">GAMBAR</label>
<input class="form-input w-full mt-2 rounded-md
bg-gray-200 focus:bg-white p-3" type="file" name="image">
@error('image')
<div class="w-full bg-red-200 shadow-sm
rounded-md overflow-hidden mt-2">
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>

<div>
<label class="text-gray-700" for="name">LINK
SLIDER</label>
<input class="form-input w-full mt-2 rounded-md
bg-gray-200 focus:bg-white" type="text" name="link" value="{{
old('link') }}" placeholder="Link Promo">
@error('link')
<div class="w-full bg-red-200 shadow-sm
rounded-md overflow-hidden mt-2">

254
<div class="px-4 py-2">
<p class="text-gray-600 text-sm">{{
$message }}</p>
</div>
</div>
@enderror
</div>

</div>

<div class="flex justify-start mt-4">


<button type="submit" class="px-4 py-2 bg-gray-600
text-gray-200 rounded-md hover:bg-gray-700 focus:outline-none focus:bg-
gray-700">UPLOAD</button>
</div>
</form>
</div>

<div class="-mx-4 sm:-mx-8 px-4 sm:px-8 py-4 overflow-x-auto">


<div class="inline-block min-w-full shadow-sm rounded-lg
overflow-hidden">
<table class="min-w-full table-auto">
<thead class="justify-between">
<tr class="bg-gray-600 w-full">
<th class="px-16 py-2">
<span class="text-white">GAMBAR</span>
</th>
<th class="px-16 py-2 text-left">
<span class="text-white">LINK
PROMO</span>
</th>
<th class="px-16 py-2">
<span class="text-white">AKSI</span>
</th>
</tr>
</thead>
<tbody class="bg-gray-200">
@forelse($sliders as $slider)
<tr class="border bg-white">
<td class="px-16 py-2 flex justify-
center">
<img src="{{ $slider->image }}"
class="object-fit-cover rounded" style="width: 35%">
</td>
<td class="px-16 py-2">
{{ $slider->link }}

255
</td>
<td class="px-10 py-2 text-center">
<button onClick="destroy(this.id)"
id="{{ $slider->id }}" class="bg-red-600 px-4 py-2 rounded shadow-sm
text-xs text-white focus:outline-none">HAPUS</button>
</td>
</tr>
@empty
<div class="bg-red-500 text-white text-
center p-3 rounded-sm shadow-md">
Data Belum Tersedia!
</div>
@endforelse
</tbody>
</table>
@if ($sliders->hasPages())
<div class="bg-white p-3">
{{ $sliders->links('vendor.pagination.tailwind')
}}
</div>
@endif
</div>
</div>
</div>
</main>
<script>
//ajax delete
function destroy(id) {
var id = id;
var token = $("meta[name='csrf-token']").attr("content");

Swal.fire({
title: 'APAKAH KAMU YAKIN ?',
text: "INGIN MENGHAPUS DATA INI!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
cancelButtonText: 'BATAL',
confirmButtonText: 'YA, HAPUS!',
}).then((result) => {
if (result.isConfirmed) {
//ajax delete
jQuery.ajax({
url: `/admin/slider/${id}`,
data: {

256
"id": id,
"_token": token
},
type: 'DELETE',
success: function (response) {
if (response.status == "success") {
Swal.fire({
icon: 'success',
title: 'BERHASIL!',
text: 'DATA BERHASIL DIHAPUS!',
showConfirmButton: false,
timer: 3000
}).then(function () {
location.reload();
});
} else {
Swal.fire({
icon: 'error',
title: 'GAGAL!',
text: 'DATA GAGAL DIHAPUS!',
showConfirmButton: false,
timer: 3000
}).then(function () {
location.reload();
});
}
}
});
}
})
}
</script>
@endsection

Di atas, pertama kita atur agar halaman ini menggunakan layout dari app dan di dalamnya
kita buat array yang bernama title dan memiliki value Slider - Admin, array ini akan
di tampilkan di layout untuk title dari website.

@extends('layouts.app', ['title' => 'Slider - Admin'])

Kemduain kita membuat sebuah form untuk melakukan upload data slider, dimana untuk
action akan kita arahkan ke dalam sebuah route yang bernama admin.slider.store.

257
<form action="{{ route('admin.slider.store') }}" method="POST"
enctype="multipart/form-data">

Kemudian untuk menampilkan data slider kita menggunakan sintaks blade, yaitu forelse,
kurang lebih seperti berikut ini :

@forelse($sliders as $slider)
//kode untuk perulangan data
@empty

//kode untuk menampilkan pesan, jika data belum tersedia

@endforelse

Pada button delete data, kita akan memanggil sebuah function yang bernama destroy di
dalam JavaScript menggunakan onClick. Dimana di dalam function tersebut kita masukkan
ID dari data slider tersebut.

<button onClick="destroy(this.id)" id="{{ $slider->id }}" class="bg-


red-600 px-4 py-2 rounded shadow-sm text-xs text-white focus:outline-
none">HAPUS</button>

Kemudian pada bagian JavaScript, kurang lebih seperti berikut ini :

//ajax delete
function destroy(id) {
// kode ajax delete + konfirmasi delete dengan sweet alert2
}

Di dalam function destroy di atas, kita menampilkan sebuah konfirmasi delete


menggunakan Sweet Alert2 dan jika bernilai true, maka akan melakukan delete data
menggunakan Ajax, kurang lebih seperti berikut ini :

258
//ajax delete
jQuery.ajax({
url: `/admin/slider/${id}`, // <-- URL untuk delete data
data: {
"id": id, // <-- ID slider
"_token": token // <-- CSRF Token
},
type: 'DELETE', // <-- method DELETE

Sekarang, kita bisa mencoba menjalankannya di [http://localhost:8000/admin/slider atau


bisa melalui menu sidebar dari halaman admin, jika berhasil maka kurang lebih hasilnya
seperti berikut ini :

Sekarang, jika kita coba klik button UPLOAD tanpa memasukkan data apapun, maka kita
akan mendapatkan sebuah error validasi kurang lebih seperti berikut ini :

259
GAMBAR CONTOH SLIDER :
https://drive.google.com/file/d/1rfMbvyNhcw8LidogUJHqv3U8rKL8NCh3/view?usp=sharing

Sekarang, silahkan masukkan gambar slider dan link untuk promo slider-nya, dan jika
berhasil maka kurang lebih hasilnya seperti berikut ini :

Terakhir, kita akan uji coba untuk proses delete data slider, silahkan klik button delete,
maka kita akan mendapatkan sebuah konfirmasi delete menggunakan Sweet Alert2,
kurang lebih seperti berikut ini :

260
261
RESTFUL API

262
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]

Apa itu RESTful API ?


RESTful API / REST API merupakan implementasi dari API (Application Programming
Interface). REST (Representional State Transfer) adalah suatu arsitektur metode komunikasi
yang menggunakan protokol HTTP untuk pertukaran data dan metode ini sering diterapkan
dalam pengembangan aplikasi. 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 :

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.

HTTP Verbs

Setiap request yang dilakukan terdapat metode yang dipakai agar server mengerti apa yang
sedang di request client

263
GET GET adalah metode HTTP Request yang paling simpel, metode ini digunakan
untuk membaca atau mendapatkan data dari sumber.
POST POST adalah metode HTTP Request yang digunakan untuk membuat data baru
dengan menyisipkan data dalam body saat request dilakukan.
PUT PUT adalah metode HTTP Request yang biasanya digunakan untuk melakukan
update data resource.
DELETE DELETE adalah metode HTTP Request yang digunakan untuk menghapus
suatu data pada resource.

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 :

2XX : adalah response code yang menampilkan bahwa request berhasil.


4XX : adalah response code yang menampilkan bahwa request mengalami kesalahan
pada sisi client.
5XX : adalah response code yang menampilkan bahwa request mengalami kesalahan
pada sisi server.

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 (Vue Js) dengan menggunakan API. Dan untuk pertukaran data tersebut
kita akan menggunakan data berupa JSON.

Postman
Untuk melakukan debug API, kita akan menggunakan aplikasi yang bernama Postman, jadi
silahkan teman-teman install terlebih dahulu melalui situs resminya di :
https://www.postman.com/downloads/

[1] : https://www.niagahoster.co.id/blog/api-adalah/#Apa_itu_API

[2] : https://jogjaweb.co.id/blog/pengertian-restfull-api

264
Install dan Konfigurasi Laravel Passport

Laravel merupakan salah satu framework yang dibilang sangat komplit, mulai dari fitur-fitur
yang ada di dalamnya sampai dengan library-library official yang disediakan. Salah satunya
adalah Laravel Passport, yaitu sebuah library yang digunakan untuk membuat proses
otentikasi berbasis Token dan menerapkan konsep OAuth2 di dalamnya.

Apa itu OAuth2 ?


OAuth adalah suatu protokol terbuka yang memungkinkan pengguna untuk berbagi sumber
pribadi mereka (mis. foto, video, daftar alamat) yang disimpan di suatu situs web dengan
situs lain tanpa perlu menyerahkan nama pengguna dan kata sandi mereka. Proses ini
dilakukan dengan memberikan token, bukan nama pengguna dan kata sandi, untuk data
mereka yang diinangi oleh suatu penyedia jasa tertentu.
(https://id.wikipedia.org/wiki/OAuth)
Contoh penerapan OAuth2 yaitu melakukan login via Facebook, Google, GitHUb, dan lain-
lain, dimana kita tidak perlu memasukkan data username atau password untuk login di situs
tertentu.

Di dalam buku ini kita akan menggunakan Laravel Passport sebagai proses otentikasi yang
akan digunakan oleh donatur di dalam Vue Js. Disini kita akan membuat sebuah API untuk
beberapa proses, yaitu : register dan login.

Langkah 1 - Installasi Laravel Passport


Pertama kita akan melakukan installasi library Laravel Passport menggunakan Composer,
silahkan jalankan perintah berikut ini di dalam terminaal/CMD dan di dalam project Laravel :

composer require laravel/passport:10.1

Jika perintah di atas berhasil, maka kita akan melihat tampilannya seperti gambar di bawah
ini :

265
Langkah 2 - Konfigurasi Laravel Passport
Setelah proses installasi selesai, sekarang kita lanjutkan untuk proses konfigurasi, silahkan
jalankan perintah di bawah ini untuk melakukan proses migration. Karena Laravel Passport
akan membuat beberapa table tambahan untuk menyimpan kredensial Token.

php artisan migrate

Jika berhasil, maka kurang lebih seperti berikut ini :

266
Selanjutnya, silahkan jalankan perintah berikut ini untuk membuat encryption key.

php artisan passport:install

Kita bisa melihat encryption key yang telah digenerate di dalam folder storag, kurang
lebih seperti berikut ini :

Setelah berhasil menjalankan perintah di atas, maka sekarang kita akan lanjutkan untuk

267
menambahkan Traits Laravel\Passport\HasApiTokens di dalam Model Donatur,
karena kita akan menggunakan Laravel Passport ini untuk proses otentikasi donatur.

Silahkan buka file app/Models/Donatur.php dan ubah kode-nya menjadi seperti berikut
ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Laravel\Passport\HasApiTokens;

class Donatur extends Model


{
use HasFactory, HasApiTokens;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password', 'avatar'
];
/**
* donations
*
* @return void
*/
public function donations()
{
return $this->hasMany(Donation::class);
}
/**
* getAvatarAttribute
*
* @param mixed $avatar
* @return void
*/
public function getAvatarAttribute($avatar)
{
if ($avatar != null) :
return asset('storage/donaturs/'.$avatar);

268
else :
return 'https://ui-avatars.com/api/?name=' . str_replace('
', '+', $this->name) . '&background=4e73df&color=ffffff&size=100';
endif;
}
}

Di atas, pertama kita import Traits HasApiTokens dari Laravel Passport.

use Laravel\Passport\HasApiTokens;

Kemudian kita gunakan Traits tersebut menggunakan use di dalam class Donatur.

use HasFactory, HasApiTokens;

Selanjutnya, kita harus memanggil method Passport::routes di dalam method boot di


AuthServiceProvider. Method ini akan digunakan untuk mendaftarkan beberapa route
yang diperlukan untuk proses access token, menghapus token, dan lain-lain.

Silahkan buka file app/Providers/AuthServiceProvider.php dan ubah kode-nya


menjadi seperti berikut ini :

269
<?php

namespace App\Providers;

use Laravel\Passport\Passport; // <-- import Laravel Passport


use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as
ServiceProvider;

class AuthServiceProvider extends ServiceProvider


{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
];

/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();

if (! $this->app->routesAreCached()) {
Passport::routes(); // <-- passport route
}
}
}

Terakhir, di file konfigurasi, Kita harus mengatur opsi driver dari guard authentication api
ke pasport. Silahkan buka file config/auth.php, kemudian cari kode berikut ini :

270
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],

'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],

Kemudian ubah menjadi seperti berikut ini :

'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],

'api' => [
'driver' => 'passport', // <-- set ke "passport"
'provider' => 'donaturs', // <-- set ke "donaturs"
'hash' => false,
],
],

Karena di atas untuk provider kita atur ke donaturs, maka sekarang kita akan membuat
provider tersebut, masih di file yang sama yaitu config/auth.php, kemudian cari kode
berikut ini :

271
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],

// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],

Kemudian kita ubah menjadi seperti berikut ini :

'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],

'donaturs' => [
// <-- nama provider "donaturs"
'driver' => 'eloquent', // <
-- driver "eloquent"
'model' => AppModelsDonatur::class, // <-- model
'app/Models/Donatur.php'
],
],

Langkah 3 - Konfigurasi Model Donatur


Secara default Model akan di atur dengan extend dari class Model, sekarang kita akan
ubah extend pada Model Donatur agar bisa digunakan untuk proses otentikasi.

Silahkan buka file app/Models/Donatur.php kemudian ubah kode-nya menjadi seperti


berikut ini :

272
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use IlluminateFoundationAuthUser as Authenticatable; // <-- import Auth
Laravel
use Laravel\Passport\HasApiTokens;

class Donatur extends Authenticatable // <-- set ke Authenticatable


{
//...

Dari perubahan kode di atas, pertama kita import Auth dari Laravel.

use Illuminate\Foundation\Auth\User as Authenticatable;

Kemudian kita ubah default extend class dari Model menjadi Authenticatable.

class Donatur extends Authenticatable


{
// ...
}

273
Membuat Restful API Register

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat sebuah API untuk
proses register. Didalam proses register kita juga akan melakukan generate token dari
Laravel Passport.

Langkah 1 - Membuat Controller API Register


Silahkan jalankan perintah berikut ini di dalam terminal/CMD untuk membuat controller API
baru.

php artisan make:controller Api\\RegisterController

Perintah di atas digunakan untuk melakukan generate controller baru dengan nama
RegisterController.php di dalam folder app/Http/Controllers/Api. Silahkan buka
file tersebut dan ubah kode-nya menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api;

use App\Models\Donatur;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;

class RegisterController extends Controller


{
/**
* register
*
* @param mixed $request
* @return void
*/
public function register(Request $request)
{
//set validasi
$validator = Validator::make($request->all(), [
'name' => 'required',

274
'email' => 'required|email|unique:donaturs',
'password' => 'required|min:8|confirmed'
]);

if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}

//create donatur
$donatur = Donatur::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password)
]);
//return JSON
return response()->json([
'success' => true,
'message' => 'Register Berhasil!',
'data' => $donatur,
'token' => $donatur->createToken('authToken')->accessToken
], 201);
}
}

Dari penambahan kode di atas, pertama kita melakukan import Model Donatur. Karena kita
akan menggunakan Model ini untuk melakukan proses insert data ke dalam table
donaturs.

use App\Models\Donatur

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;

Selanjutnya kita juga import Facades Hash, ini akan kita gunakan untuk melakukan hashing
sebuah password sebelum password tersebut di insert ke dalam database.

275
use Illuminate\Support\Facades\Hash;

Dan kita juga akan membuat sebuah validasi dengan manul, oleh sebab itu kita harus
melakukan import untuk Facades Validator.

use Illuminate\Support\Facades\Validator;

Di dalam controller RegisterController, kita menambahkan 1 method baru dengan


nama register, method inilah yang akan digunakan untuk melakukan proses registrasi. Di
dalamnya, pertama kita melakukan sebuah validasi terlebih dahulu untuk memastikan data
yang dikirim benar-benar valid.

//set validasi
$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|email|unique:donaturs',
'password' => 'required|min:8|confirmed'
]);

Dari penambahan validasi di atas, kurang lebih penjelasannya seperti berikut ini :

KEY VALIDASI KETERANGAN

name required field wajib diisi

email required field wajib diisi

email field harus menggunakan format email

field bersifat unik dan tidak boleh ada yang sama di


unique:donaturs
dalam table donaturs

password required field wajib diisi

min:8 field minimal 8 karakter

confirmed field harus sama dengan field pertama

Jika validasi di atas tidak terpenuhi, maka akan mengembalikan sebuah response berupa
message validasi dengan status code 400.

276
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}

Tapi, jika validasi terpenuhi dan artinya data sudah sesuai dengan yang diharapkaan, maka
akan melakukan insert data tersebut ke dalam database.

//create donatur
$donatur = Donatur::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password)
]);

Setelah data berhasil disimpan ke dalam database, maka kita akan mengembalikan
response data success dan memberikan informasi dari data yang diinput tersebut dalam
format JSON.

//return JSON
return response()->json([
'success' => true,
'message' => 'Register Berhasil!',
'data' => $donatur,
'token' => $donatur->createToken('authToken')->accessToken
], 201);

Di atas, kita juga melakukan generate token untuk data yang baru saja diregister tersebut,
yaitu kurang lebih seperti berikut ini :

$donatur->createToken('authToken')->accessToken

Langkah 2 - Membuat Route API Register


Sekarang kita lanjutkan untuk menambahkan route API untuk register, silahkan buka file
routes/api.php kemudian ubah kode-nya menjadi seperti berikut ini :

277
<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::middleware('auth:api')->get('/user', function (Request $request)


{
return $request->user();
});

/**
* Api Register
*/
Route::post('/register', [RegisterController::class, 'register']);

Di atas, pertama kita arahkan namespace ke dalam folder app/Http/Controllers/Api.


Selanjutnya, kita menambahkan 1 route baru, yaitu :

/**
* Api Register
*/
Route::post('/register', [RegisterController::class, 'register']);

Langkah 3 - Uji Coba API Register


Sekarang kita akan lakukan uji coba menggunakan Postman. Silahkan buka aplikasi Postman
dan masukkan link berikut ini http://localhost:8000/api/register di dalam URL dan
menggunakan method POST. Kemudian klik Send, maka kita akan mendapatkan sebuah
response validasi kurang lebih seperti berikut ini :

278
Dari gambar di atas kita bisa melihat, bahwa kita mendapatkan sebuah response berupa
validasi, karena kita tidak memasukkan data apapun di dalam input body.

Sekarang, silahkan klik tab Body dan pilih form-data dan masukkan kredensial berikut ini :

KEY VALUE

name Kurnia Andi Nugroho

email kurnia@gmail.com

password password

password_confirmation password

Untuk value, silahkan disesuaikan dengan kebutuhan kita, dalam contoh di atas, saya
membuat data sample dengan nama kurnia Andi Nugroho. Sekarang, silahkan klik Send
lagi dan jika berhasil, maka kita akan mendapatkan sebuah success response kurang lebih
seperti berikut ini :

279
Bisa kita lihat, di atas kita mendapatkan sebuah response berupa data donatur yang register
dan mendapatkan token yang di generate oleh Laravel Passport.

280
Membuat Restful API Login

Pada tahap ini kita semua akan belajar bagaimana cara membuat proses login dengan
Laravel Passport. Konsepnya hampir sama dengan proses register, karena sama-sama akan
melakukan sebuah generate token, perbedaanya kalau login hanya akan mencocokan data
yang di input dengan data yang ada di dalam database.

Langkah 1 - Membuat Controller API Login


Sekarang, silahkan jalankan perintah di bawah ini di dalam terminal/CMD untuk membuat
controller baru :

php artisan make:controller Api\\LoginController

Jika perintah di atas berhasil, maka kita akan dibuatkan 1 file baru dengan nama
LoginController.php di dalam folder app/Http/Controllers/Api. Silahkan buka file
tersebut dan ubah kode-nya menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api;

use App\Models\Donatur;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;

class LoginController extends Controller


{
/**
* login
*
* @param mixed $request
* @return void
*/
public function login(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|email',

281
'password' => 'required'
]);

if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}

$donatur = Donatur::where('email', $request->email)->first();

if (!$donatur || !Hash::check($request->password,
$donatur->password)) {
return response()->json([
'success' => false,
'message' => 'Login Failed!',
], 401);
}

return response()->json([
'success' => true,
'message' => 'Login Berhasil!',
'data' => $donatur,
'token' => $donatur->createToken('authToken')->accessToken
], 200);
}
/**
* logout
*
* @param mixed $request
* @return void
*/
public function logout(Request $request)
{
$removeToken = $request->user()->tokens()->delete();

if($removeToken) {
return response()->json([
'success' => true,
'message' => 'Logout Berhasil!',
]);
}
}
}

Dari penambahan kode di atas, pertama kita melakukan import Model Donatur. Karena kita
akan menggunakan Model ini untuk melakukan proses login ke dalam aplikasi.

282
use App\Models\Donatur

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;

Selanjutnya kita juga import Facades Hash, ini akan kita gunakan untuk melakukan hashing
sebuah password sebelum password tersebut di insert ke dalam database.

use Illuminate\Support\Facades\Hash;

Dan kita juga akan membuat sebuah validasi dengan manul, oleh sebab itu kita harus
melakukan import untuk Facades Validator.

use Illuminate\Support\Facades\Validator;

Dan di dalam controller LoginController kita menambahkan 2 method baru, yaitu :

login
logout

function login

Method ini digunakan untuk proses login di dalam aplikasi kita, disini kita menambahkan
sebuah validasi terlebih dahulu untuk memastikan bahwa data yang di input benar-benar
sesuai.

$validator = Validator::make($request->all(), [
'email' => 'required|email',
'password' => 'required'
]);

Dari validasi di atas, kurang lebih penjelasannya seperti berikut ini :

283
KEY VALIDASI KETERANGAN

email required field wajib diisi

email field harus menggunakan format email

password required field wajib diisi

Jika validasi di atas tidak terpenuhi, maka akan mengembalikan sebuah response berupa
message validasi dengan status code 400.

if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}

Tapi, jika validasi sudah terpenuhi maka kita akan melakukan pengecekan data donatur
berdasarkan email, kurang lebih seperti berikut ini :

$donatur = Donatur::where('email', $request->email)->first();

Selanjutnya kita akan membuat 2 kondisi, yaitu apakah variable $donatur bernilai false
atau variable $donatur bernilai true tapi password-nya tidak sesuai dengan yang ada di
dalam database.

if (!$donatur || !Hash::check($request->password, $donatur->password)) {

//response login failed

Di atas, jika salah satu dari 2 kondisi bernilai false, maka akan mengembalikan sebuah
response failed dengan status code 401.

284
return response()->json([
'success' => false,
'message' => 'Login Failed!',
], 401);

Kemudian, jika variable $donatur bernilai true dan password-nya sesuai yang ada di
dalam database, maka kita akan mengembalikan sebuah response success dengan
menampilkan data donatur yang login dan sekaligus melakukan generate token.

return response()->json([
'success' => true,
'message' => 'Login Berhasil!',
'data' => $donatur,
'token' => $donatur->createToken('authToken')->accessToken
], 200);

function logout

Method ini akan kita gunakan untuk proses hapus data token dari user yang sedang login di
dalam aplikasi, kurang lebih seperti berikut ini :

$removeToken = $request->user()->tokens()->delete();

Kemudian, kita membuat sebuah kondisi apabila proses di atas berhasil, maka akan
mengembalikan sebuah response success. Kurang lebih seperti berikut ini :

if($removeToken) {
return response()->json([
'success' => true,
'message' => 'Logout Berhasil!',
]);
}

Langkah 2 - Membuat Route API Login


Selanjutnya, kita akan menambahkan sebuah route agar URL/endpoint API login dapat

285
diakses. Silahkan buka file routes/api.php, kemudian tambahkan route berikut ini di
bawah route register.

/**
* Api Login
*/
Route::post('/login', [LoginController::class, 'login']);

Langkah 3 - Uji Coba API Login


Sekarang, kita akan lanjutkan untuk melakukan proses uji coba API yang sudah kita buat di
atas, silahkan buka aplikasi Postman kemudian masukkan URL ini
http://localhost:8000/api/login dan pada bagian method silahkan pilih POST. Dan silahkan
klik Send, maka kita akan mendapatkan sebuah response failed yang berisi validasi, kurang
lebih seperti berikut ini :

Sekarang, kita akan uji coba login menggunakan kredensial yang sebelumnya sudah pernah
kita gunakan untuk register. Silahkan klik tab Body dan pilih form-data kemudian isi key
dan value seperti berikut ini :

KEY VALUE

email kurnia@gmail.com

286
KEY VALUE

password password
CATATAN! : Silahkan disesuaikan dengan kredensial masing-masing.

Setelah itu, silahkan klik Send lagi dan jika berhasil maka kita akan mendapatkan sebuah
response success kurang lebih seperti berikut ini :

Di atas, kita mendapatkan sebuah response success dan di dalamnya terdapat data donatur
yang login beserta API token.

287
Membuat Restful API Category

Pada tahap kali ini kita semua akan belajar membuat sebuah API yang digunakan untuk
menampilkan data category, API yang akan kita buat disini bersifat global, artinya dapat
diakses tanpa harus menyertakan sebuah token.

Disini kita akan membuat 3 fungsi, yang pertama akan digunakan untuk menampilkan data
category dan yang kedua akan kita gunakan untuk menampilkan detail category, dimana di
dalam detail category ini akan berupa data campaign yang memiliki ID category tersebut.
Dan yang terakhir kita akan menampilkan data category yang nantinya akan kita tampilkan
di halaman frontend (Vue Js), dimana data yang akan kita tampilkan adalah 3.

Langkah 1 - Membuat Controller API Category


Sekarang kita akan lanjutkan untuk membuat controller baru, silahkan jalankan perintah
berikut ini di dalam terminal/CMD :

php artisan make:controller Api\\CategoryController

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama CategoryController.php di dalam folder app/Http/Controllers/Api.
Silahkan buka file tersebut dan ubah semua kode-nya menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api;

use App\Models\Category;
use App\Http\Controllers\Controller;

class CategoryController extends Controller


{
/**
* index
*
* @return void
*/
public function index()
{
//get data categories

288
$categories = Category::latest()->paginate(12);

//return with response JSON


return response()->json([
'success' => true,
'message' => 'List Data Categories',
'data' => $categories,
], 200);
}
/**
* show
*
* @param mixed $slug
* @return void
*/
public function show($slug)
{
//get detail data category with campaign
$category = Category::with('campaigns.user',
'campaigns.sumDonation')->where('slug', $slug)->first();

if($category) {

//return with response JSON


return response()->json([
'success' => true,
'message' => 'List Data Campaign Berdasarkan Category :
'. $category->name,
'data' => $category,
], 200);
}

//return with response JSON


return response()->json([
'success' => false,
'message' => 'Data Category Tidak Ditemukan!',
], 404);
}
/**
* categoryHome
*
* @return void
*/
public function categoryHome()
{
//get data categories

289
$categories = Category::latest()->take(3)->get();

//return with response JSON


return response()->json([
'success' => true,
'message' => 'List Data Category Home',
'data' => $categories,
], 200);
}
}

Dari penambahan kode di atas, pertama kita import Model Category terlebih dahulu,
karena kita akan memanfatkan Model ini untuk mendapatkan data category dari database.

use App\Models\Category;

Kemudian di dalam controller CategoryController, kita menambahkan 3 method baru,


yaitu :

function index
function show
function categoryHome

function index

Method ini akan kita gunakan untuk menampilkan data category yang akan diurutkan
berdasarkan data yang paling baru menggunakan function latest. Dan akan di batasi
jumlah data yang akan ditampilkan perhalaman adalah 12 menggunakan function
paginate.

//get data categories


$categories = Category::latest()->paginate(12);

Setelah itu, kita akan parsing variable $categories di atas ke dalam sebuah response
dengan format JSON, kurang lebih seperti berikut ini :

290
//return with response JSON
return response()->json([
'success' => true,
'message' => 'List Data Categories',
'data' => $categories, // <-- data
category
], 200);

function show

Method ini akan kita gunakan untuk menampilkan detail data category berdasarkan slug
yang diambil dari parameter URL, dimana di dalamnya akan kita gunakan untuk
menampilkan data-data campaign yang sesuai dengan category tersebut.

//get detail data category with campaign


$category = Category::with('campaigns.user',
'campaigns.sumDonation')->where('slug', $slug)->first();

Di atas, kita menambahkan method with yang isinya ada 2 parameter, yaitu :

campaigns.user - artinya kita akan memanggil relasi campaigns yang sudah kita
definisikan di dalam Model Category. Dan dari relasi campaigns kita akan
memanggil method yang bernama user, dimana method tersebut akan melakukan
relasi dengan Model User.
campaigns.sumDonation - artinya kita akan memanggil relasi campaigns yang
sudah kita definisikan di dalam Model Category. Dan dari relasi campaigns kita
memanggil sebuah method yang bernama sumDonation yang berada di dalam
Model Campaign.

Setelah itu, kita membuat sebuah kondisi jika variable $category di atas bernilai true
yang artinya datanya ditemukan, maka kita akan membuat sebuah response success kurang
lebih seperti berikut ini :

291
if($category) {

//return with response JSON


return response()->json([
'success' => true,
'message' => 'List Data Campaign Berdasarkan Category : '.
$category->name,
'data' => $category, // <-- data "campaign" berdasarkan
"category"
], 200);

Tapi, jika nilai dari variable $category bernilai false artinya data category tidak
ditemukan, maka akan mengembalikan sebuah response 404 atau data tidak ditemukan.
Kurang lebih seperti berikut ini :

//return with response JSON


return response()->json([
'success' => false,
'message' => 'Data Category Tidak Ditemukan!',
], 404);

function categoryHome

Method ini sama seperti method index, yang membedakan disini kita hanya akan
menampilkan jumlah data yaitu 3, karena API ini akan kita gunakan pada halaman home di
frontend nantinya.

//get data categories


$categories = Category::latest()->take(3)->get();

Di atas, kita menggunakan function take untuk mengambil data dengan jumlah tertentu.
Kemudian kita parsing variable $categories di atas ke dalam response JSON, kurang lebih
seperti berikut ini :

292
//return with response JSON
return response()->json([
'success' => true,
'message' => 'List Data Category Home',
'data' => $categories, // <-- data
category
], 200);

Langkah 2 - Membuat Route API Category


Sekarang kita akan lenjutkan untuk menambahkan route untuk API category, silahkan buka
file routes/api/php dan tambahkan route berikut ini tepat di bawah route login.

/**
* APi Category
*/
Route::get('/category', [CategoryController::class, 'index']);
Route::get('/category/{slug}', [CategoryController::class, 'show']);
Route::get('/categoryHome', [CategoryController::class,
'categoryHome']);

Langkah 3 - Uji Coba API Category


Sekarang kita akan lakukan uji coba untuk API category index, silahkan buka aplikasi
Postman dan masukkan URL berikut ini http://localhost:8000/api/category dan untuk
method-nya adalah GET. Kemudian klik Send. Jika berhasil maka kita akan mendapatkan
response kurang lebeih seperti berikut ini :

293
Sekarang kita akan uji coba lagi untuk menampilkan detail category, yang dimana di
dalanya berisi data campaign yang sesuai dengan category tersebut. Silahkan buka aplikasi
Postman dan masukkan URL berikut ini http://localhost:8000/api/category/bencana-alam dan
method-nya adalah GET.

CATATAN! : silahkan ganti bencana-alam dengan data slug dari category yang kalian
miliki, dalam contoh di atas saya menggunakan contoh bencana-alam.

Setelah itu, silahkan klik Send dan jika berhasil maka kita akan mendapatkan response
success yang berisi data campaign, kurang lebih seperti berikut ini :

294
Terakhir, kita akan lakukan uji coba untuk menampilkan data categoryHome, silahkan
buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/categoryHome dan untuk method-nya silahkan pilih GET.
Kemudian klik Send dan jika berhasil, maka kita akan mendapatkan response success
kurang lebih seperti berikut ini :

295
Membuat Restful API Campaign

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat sebuah API untuk
menampilkan data campaign, disini kita akan membuat 2 fungsi, yang pertama akan kita
gunakan untuk menampilkan list/daftar dari data campaign dan yang kedua akan kita
gunakan untuk menampilkan detail data camapign. Di dalam detail campaign kita juga akan
menampilkan jumlah donasi dan donatur.

Langkah 1 - Membuat Controller API Campaign


Sekarang kita lanjutkan untuk proses membuat controller baru, silahkan jalankan perintah di
bawah ini di dalam terminal/CMD :

php artisan make:controller Api\\CampaignController

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama CampaignController.php di dalam folder app/Http/Controllers/Api.
Silahkan buka file tersebut dan ubah semua kode-nya menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api;

use App\Models\Campaign;
use App\Models\Donation;
use App\Http\Controllers\Controller;

class CampaignController extends Controller


{
/**
* index
*
* @return void
*/
public function index()
{
//get data campaigns
$campaigns =
Campaign::with('user')->with('sumDonation')->when(request()->q,
function($campaigns) {

296
$campaigns = $campaigns->where('title', 'like', '%'.
request()->q . '%');
})->latest()->paginate(5);

//return with response JSON


return response()->json([
'success' => true,
'message' => 'List Data Campaigns',
'data' => $campaigns,
], 200);
}
/**
* show
*
* @param mixed $slug
* @return void
*/
public function show($slug)
{
//get detail data campaign
$campaign =
Campaign::with('user')->with('sumDonation')->where('slug',
$slug)->first();

//get data donation by campaign


$donations = Donation::with('donatur')->where('campaign_id',
$campaign->id)->where('status', 'success')->latest()->get();

if($campaign) {

//return with response JSON


return response()->json([
'success' => true,
'message' => 'Detail Data Campaign : '.
$campaign->title,
'data' => $campaign,
'donations' => $donations
], 200);
}

//return with response JSON


return response()->json([
'success' => false,
'message' => 'Data Campaign Tidak Ditemukan!',
], 404);
}

297
}

Dari penambahan kode di atas, pertama kita import Model Campaign terlebih dahulu,
karena kita akan menggunakan Model ini untuk melakukan get data campaign dari
database.

use App\Models\Campaign;

kemudian kita juga import Model Donation, kita akan menggunakan Model ini untuk
melakukan get data donasi di dalam detail campaign nanti.

use App\Models\Donation;

Dan di dalam controller CampaignController, kita menambahkan 2 method baru, yaitu :

function index
function show

function index

Method ini akan kita gunakan untuk menampilkan data campaign, dimana datanya akan
diurutkan dari yang paling terbaru menggunakan function latest. Dan disini kita juga
melakukan relasi ke dalam user dan sumDonation menggunakan function with, dimana
kedua method tersebut sudah kita buat di dalam Model Campaign.

Dan kita juga membuat kondisi untuk proses pencarian, yaitu menggunakan function when,
jadi jika ada sebuah request dengan nama q, maka akan mencari data campaign
berdasarkan title yang sesuai dengan isi dari request q tersebut.

//get data campaigns


$campaigns =
Campaign::with('user')->with('sumDonation')->when(request()->q,
function($campaigns) {
$campaigns = $campaigns->where('title', 'like', '%'. request()->q .
'%');
})->latest()->paginate(5);

Kemudian jika data sudah di dapatkan, maka data yang akan ditampilkan dibatas

298
perhalaman adalah 5 dengan menggunakan function paginate. Kemudian kita tampilkan
ke dalam sebuah response dengan format JSON. Kurang lebih seperti berikut ini :

//return with response JSON


return response()->json([
'success' => true,
'message' => 'List Data Campaigns',
'data' => $campaigns, // < data Campaign
], 200);

function show

Method ini akan digunakan untuk menampilkan detail data camapign, dimana untuk
parameter-nya akan diambil dari slug. Kemudian kita jadikan parameter slug tersebut
untuk proses mendari data campaign.

//get detail data campaign


$campaign = Campaign::with('user')->with('sumDonation')->where('slug',
$slug)->first();

Di atas kita mencari data campaign berdasarkan data slug menggunakan where dan di atas
kita juga memanggil sebuah relasi menggunakan function with.

Setelah itu, kita juga akan mencari data donation yang memiliki status success
berdasarkan data campaign di atas, kurang lebih seperti berikut ini :

//get data donation by campaign


$donations = Donation::with('donatur')->where('campaign_id',
$campaign->id)->where('status', 'success')->latest()->get();

Kemudian kita membuat sebuah kondisi untuk memeriksa apakah variable $campaign di
atas bernilai true yang artinya data camapign ditemukan di dalam database.

299
if($campaign) {

//return with response JSON


return response()->json([
'success' => true,
'message' => 'Detail Data Campaign : '. $campaign->title,
'data' => $campaign, // <-- data detail campaign
'donations' => $donations // <-- data donation berdasarkan
campaign
], 200);
}

Tapi, jika variable $campaign bernailai false artinya data campaign tidak ditemukan di
dalam database, maka akan mengembalikan sebuah response failed atau 404.

//return with response JSON


return response()->json([
'success' => false,
'message' => 'Data Campaign Tidak Ditemukan!',
], 404);

Langkah 2 - Membuat Route API Campaign


Sekarang kita lanjutkan untuk menambahkan route API campaign, silahkan buka file
routes/api.php kemudian tambahkan route berikut ini tepat di bawah route category.

/**
* Api Campaign
*/
Route::get('/campaign', [CampaignController::class, 'index']);
Route::get('/campaign/{slug}', [CampaignController::class, 'show']);

Langkah 3 - Uji Coba API Campaign


Sekarang kita akan lakukan uji coba untuk mendapatkan data camapign, silahkan buka
aplikasi Postman dan masukkan URL berikut ini http://localhost:8000/api/campaign dan
untuk method-nya silahkan pilih GET. Sekarang klik Send dan jika berhasil maka kita akan

300
mendapatkan response success kurang lebih seperti berikut ini :

Sekarang, kita akan lakukan uji coba untuk menampilkan detail data camapign, silahkan
buka aplikasi Postman dan massukkan URL berikut ini
http://localhost:8000/api/campaign/pahala-tak-terputus-bangun-masjid-terkena-gempa dan
untuk method-nya silahkan pilih GET.

CATATAN! : silahkan ubah nilai slug dari pahala-tak-terputus-bangun-masjid-


terkena-gempa dengan data slug yang kalian miliki di dalam database. Dalam contoh di
atas saya menggunakan slug tersebut, jadi disesuaikan dengan data masing-masing.

Silahkan klik Send dan jika berhasil maka kurang lebih hasilnya seperti berikut ini :

301
302
Membuat Restful API Slider

Pada tahap kali ini, kita semua akan belajar bagaimana cara membuat API untuk
menampilkan data slider, disini kita hanya akan membuat 1 method saja. Jadi di dalam
controller slider ini bisa dibilang sangat sederhana.

Langkah 1 - Membuat Controller API Slider


Sekarang kita akan belajar membuat controller baru untuk slider, silahkan jalankan perintah
berikut ini di dalam terminal/CMD :

php artisan make:controller Api\\SliderController

Jika perintah di atas berhasil, maka kita akan mendapatkan 1 file baru dengan nama
SliderController.php di dalam folder app/Http/Controllers/Api. Silahkan buka
file tersebut dan ubah semua kode-nya menjadi seperti berikut ini :

303
<?php

namespace App\Http\Controllers\Api;

use App\Models\Slider;
use App\Http\Controllers\Controller;

class SliderController extends Controller


{
/**
* index
*
* @return void
*/
public function index()
{
//get data sliders
$sliders = Slider::latest()->get();

//return with response JSON


return response()->json([
'success' => true,
'message' => 'List Data Sliders',
'data' => $sliders,
], 200);
}
}

Dari penambahan kode di atas, pertama kita melakukan import Model Slider, ini akan kita
gunakan untuk mendapatkan data slider dari database.

use App\Models\Slider;

Kemudian di dalam controller SliderController kita menambahkan 1 method baru yaitu


index, dimana di dalamnya kita melakukan get data slider dari database yang akan
diurutkan berdasarkan data yang paling terbaru.

304
//get data sliders
$sliders = Slider::latest()->get();

Setelah itu, kita parsing variable $sliders di atas ke dalam sebuah response dengan
format JSON, kurang lebih seperti berikut ini :

//return with response JSON


return response()->json([
'success' => true,
'message' => 'List Data Sliders',
'data' => $sliders,
], 200);

Langkah 2 - Membuat Route API Slider


Sekarang kita akan lanjutkan untuk menambahkan route untuk API slider. Silahkan buka file
routes/api.php kemudian tambahkan route berikut ini tepat di bawah route campaign.

/**
* Api Slider
*/
Route::get('/slider', [SliderController::class, 'index']);

Langkah 3 - Uji Coba API Slider


Sekarang kita akan lakukan uji coba untuk API slider, silahkan buka aplikasi Postman
kemudian masukkan URL berikut ini http://localhost:8000/api/slider dan untuk method-nya
silahkan pilih GET. Kemudian klik Send dan jika berhasil maka kurang lebih hasil response-
nya seperti berikut ini :

305
306
Membuat Restful API Profile

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat API untuk profile,
dimana di dalamnya kita akan membuat 3 fungsi, yang pertama untuk menampilkan data
profile, yang kedua untuk melakukan update profile dan yang terakhir untuk melakukan
update password.

Langkah 1 - Membuat Controller API Profile


Sekarang kita akan belajar bagaimana cara membuat controller baru untuk data profile,
silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:controller Api\\ProfileController

Jika perintah di atas berhasil, maka kita akan mendapatkan 1 file controller baru dengan
nama ProfileController.php di dalam folder app/Http/Controllers/Api. Silahkan
buka file tersebut dan ubah semua kode-nya menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api;

use App\Models\Donatur;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;

class ProfileController extends Controller


{
/**
* index
*
* @return void
*/
public function index()
{
//return with response JSON
return response()->json([

307
'success' => true,
'message' => 'Data Profile',
'data' => auth()->guard('api')->user(),
], 200);
}
/**
* update
*
* @param mixed $request
* @return void
*/
public function update(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required'
]);

if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}

//get data profile


$donatur =
Donatur::whereId(auth()->guard('api')->user()->id)->first();

//update with upload avatar


if($request->file('avatar')) {

//hapus image lama


Storage::disk('local')->delete('public/donaturs/'.basename($donatur->ima
ge));

//upload image baru


$image = $request->file('avatar');
$image->storeAs('public/donaturs', $image->hashName());

$donatur->update([
'name' => $request->name,
'avatar' => $image->hashName()
]);

//update without avatar


$donatur->update([
'name' => $request->name,

308
]);

//return with response JSON


return response()->json([
'success' => true,
'message' => 'Data Profile Berhasil Diupdate!',
'data' => $donatur,
], 201);

}
/**
* updatePassword
*
* @param mixed $request
* @return void
*/
public function updatePassword(Request $request)
{
$validator = Validator::make($request->all(), [
'password' => 'required|confirmed'
]);

if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}

$donatur =
Donatur::whereId(auth()->guard('api')->user()->id)->first();
$donatur->update([
'password' => Hash::make($request->password)
]);

//return with response JSON


return response()->json([
'success' => true,
'message' => 'Password Berhasil Diupdate!',
'data' => $donatur,
], 201);
}
}

Dari penambahan kode di atas, pertama kita melakukan import Model Donatur, karena kita
akan menggunakan Model ini untuk melakukan manipulasi data.

309
use App\Models\Donatur;

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;

Selanjutnya kita juga import Facades Hash, ini akan kita gunakan untuk melakukan hashing
sebuah password sebelum password tersebut di insert ke dalam database.

use Illuminate\Support\Facades\Hash;

Setelah itu kita import filesystem dari Laravel, ini bisa digunakan untuk menyimpan data ke
dalam local aplikasi maupun ke cloud seperti Amazon S3, Digital Ocean Spaces, dan lain-
lain.

use Illuminate\Support\Facades\Storage;

Dan kita juga akan membuat sebuah validasi dengan manul, oleh sebab itu kita harus
melakukan import untuk Facades Validator.

use Illuminate\Support\Facades\Validator;

Dan di dalam controller ProfileController kita menambahkan 3 method baru, yaitu :

function index
function update
function updatePassword

function index

Method ini digunakan untuk menampilkan data profile yang sedang login dan kita tampilkan
dalam sebuah response dengan format JSON.

310
//return with response JSON
return response()->json([
'success' => true,
'message' => 'Data Profile',
'data' => auth()->guard('api')->user(), // <-- data
profile
], 200);

Di atas, untuk mendapatkan data profile yang sedang login, kita tambahkan method
guard('api'), karena profile yang login tersebut menggunakan guard ini.

auth()->guard('api')->user()

function update

Method ini akan kita gunakan untuk melakukan update data profile, dan disini kita juga akan
membuat sebuah kondisi untuk validasi.

$validator = Validator::make($request->all(), [
'name' => 'required'
]);

Dari pendefinisian validasi di atas, kita atur agar field name menggunakan jenis validasi
required, yang artinya field tersebut wajib diisi.

Jika validasi di atas tidak terpenuhi, maka akan mengembalikan sebuah response berupa
message validasi dengan status code 400.

if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}

Setelah itu kita mencari data donatur/profile yang sedang login, kurang lebih seperti berikut
ini :

311
//get data profile
$donatur = Donatur::whereId(auth()->guard('api')->user()->id)->first();

kemudian kita membuat sebuah kondisi untuk mengecek apakah ada sebuah request file
dengan nama avatar, jika ada, maka kita akan melakukan update data profile dan
sekaligus melakukan upload foto profile.

//update with upload avatar


if($request->file('avatar')) {

//hapus image lama


Storage::disk('local')->delete('public/donaturs/'.basename($donatur->ima
ge));

//upload image baru


$image = $request->file('avatar');
$image->storeAs('public/donaturs', $image->hashName());

$donatur->update([
'name' => $request->name,
'avatar' => $image->hashName()
]);

Di atas, sebelum kita melakukan upload foto terbaru, kita harus melakukan hapus data foto
yang lama terlebih dahulu.

Dan jika tidak ada request file dengan nama avatar, maka kita hanya akan mengupdate
nama donatur saja tanpa merubah foto, kurang lebih seperti berikut ini :

//update without avatar


$donatur->update([
'name' => $request->name,
]);

Dan terakhir, kita akan melakukan return berupa format JSON dengan membawa informasi
data profile yang sudah kita update di atas.

312
//return with response JSON
return response()->json([
'success' => true,
'message' => 'Data Profile Berhasil Diupdate!',
'data' => $donatur, // <-- data profile
], 201);

function updatePassword

Method ini akan kita gunakan untuk melakukan update password, disini kita juga membuat
sebuah kondisi validasi. Kurang lebih seperti berikut ini :

$validator = Validator::make($request->all(), [
'password' => 'required|confirmed'
]);

Dari definisi validasi di atas, kurang lebih seperti berikut ini penjelesannya :

KEY VALIDASI KETERANGAN

password required field wajib diisi

confirmed field harus sama dengan field pertama

Jika validasi di atas tidak terpenuhi, maka akan mengembalikan sebuah response error
validasi dengan status code 400.

if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}

Jika validasi terpenuhi, maka akan melakukan proses update password. Kurang lebih seperti
berikut ini :

313
$donatur = Donatur::whereId(auth()->guard('api')->user()->id)->first();
$donatur->update([
'password' => Hash::make($request->password)
]);

Dan terakhir, kita akan mengembalikan ke dalam sebuah response dengan format JSON,
kurang lebih seperti berikut ini :

//return with response JSON


return response()->json([
'success' => true,
'message' => 'Password Berhasil Diupdate!',
'data' => $donatur,
], 201);

Langkah 2 - Membuat Route API Profile


Sekarang, kita akan lanjutkan untuk membuat route untuk API profile. Silahkan buka file
routes/api.php kemudian tambahkan route berikut ini di bawah route slider.

/**
* Api Profile
*/
Route::get('/profile', [ProfileController::class,
'index'])->middleware('auth:api');
Route::post('/profile', [ProfileController::class,
'update'])->middleware('auth:api');
Route::post('/profile/password', [ProfileController::class,
'updatePassword'])->middleware('auth:api');

Dari penambahan kode route profile di atas, bisa kita perhatikan, disini ditambahkan
middleware dengan isi auth:api yang artinya route ini hanya bisa diakses jika
user/donatur sudah memiliki akses otentikasi API terlebih dahulu.

Langkah 3 - Uji Coba API Profile


Sekarang, kita akan lakukan uji coba untuk menampilkan data profile. Silahkan buka aplikasi

314
Postman dan masukkan URL berikut ini http://localhost:8000/api/profile dan untuk method-
nya pilih GET. Kemudian klik tab Headers dan masukkan beberapa key dan value berikut
ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer Token


CATATAN! : silahkan ganti Token dengan token hasil generate dari proses login.

Kurang lebih seperti berikut ini :

Sekarang klik Send dan jika berhasil kurang lebih hasilnya seperti berikut ini :

Sekarang kita akan coba untuk melakukan update data profile dengan upload avatar baru.

315
Silahkan buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/profile dan untuk method-nya silahkan pilih POST. Kemudian klik
tab Headers dan masukkan beberapa key dan value berikut ini :

masukkan beberapa key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer Token

Selanjutnya, silahkan klik tab Body dan pilih form-data dan masukkan beberaya key dan
value berikut ini :

KEY TYPE VALUE

name Text Kurnia Andi Nugroho

avatar File Pilih Foto dari Komputer

Untuk avatar silahkan di set ke type File dan berkas-nya bisa pilih di dalam komputer
masing-masing. Jika sudah sekarang klik Send dan jika berhasil maka kurang lebih
response-nya seperti berikut ini :

316
Terakhir, kita akan lakukan uji coba untuk update password. Silahkan buka aplikasi Postman
dan masukkan URL berikut ini http://localhost:8000/api/profile/password dan untuk method-
nya silahkan pilih POST. Kemudian klik tab Headers dan masukkan beberapa key dan
value berikut ini :

masukkan beberapa key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer Token

Selanjutnya, silahkan klik tab Body dan pilih form-data dan masukkan beberaya key dan
value berikut ini :

KEY VALUE

password 12345678

password_confirmation 12345678

Di atas, untuk value silahkan disesuaikan dengan password yang diinginkan. Sekarang klik
Send dan jika berhasil maka kurang lebih akan mendapatkan response success update
password.

317
318
Installasi dan Konfigurasi Midtrans

Pada tahap kali ini kita semua akan belajar bagaimana cara menginstall dan konfigurasi
package/library Midtrans di dalam project Laravel yang kita bangun. Midtrans merupakan
salah satu layanan payment gateway yang mudah untuk diintegrasikan di dalam aplikasi,
dengan menggunakan payment gateway maka untuk proses pembayaran yang dilakukan di
dalam website akan berjalan secara otomatis tanpa harus melakukan konfirmasi
pembayaran secara manual.

Langkah 1 - Daftar Akun di Midtrans


Pertama, kita akan melakukan pendaftaran akun di dalam website Midtrans, ini bertujuan
agar kita nanti mendapatkan Client Key dan Server Key yang digunakan untuk
integrasi.

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.

Di dalam halaman dashboard Midtrans terdapat 2 environment, yaitu Sandbox dan


Production. Disini pastikan kita menggunakan yang jenis Sandbox, karena masih dalam
proses testing. Nanti, jika sudah benar-benar fix dan digunakan secara online, maka kita
bisa menggantinya dengan Production.

319
Setelah itu, sekarang klik menu SETTINGS > ACCESS KEYS, maka sekarang kita sudah
mendapatkan kredensial Merchant ID, Client Key dan Server Key.

Langkah 2 - Installassi Package Midtrans


Sekarang, kita lanjutkan untuk proses installasi package Midtrans di dalam project Laravel
yang kita kembangkan. SIlahkan jalankan perintah di bawah ini di dalam terminal/CMD dan
pastikan menjalankannya di dalam project Laravel.

320
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.

REPOSITORY MIDTRANS : https://github.com/Midtrans/midtrans-php

Langkah 3 - Konfigurasi Package Midtrans


Sekarang, kita lanjutkan untuk proses konfigurasi Midtrans di dalam project Laravel.
Silahkan buka file .env kemudian tambahkan kode berikut ini :

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.

Silahkan buka file config/services.php, kemudian tambahkan array berikut ini :

//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

321
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'),
],

//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),
]

322
];

Sekarang, kita sudah bisa menggunakan Midtrans di dalam project Laravel yang kita
kembangkan.

323
Membuat Restful API Donation

Pada tahap kali ini, kita semua akan belajar bagaimana cara membuat API untuk proses
pembayaran/checkout. Dimana disini kita akan mengimplementasikan payment gateway
dari Midtrans. Konsepnya saat melakukan pembayaran/checkout kita akan melakukan
request ke Midtrans untuk mendapatkan SNAP_TOKEN, dimana SNAP_TOKEN inilah yang
akan kita gunakan untuk proses pembayaran menggunakan Midtrans. Untuk lebih jelasnya
bisa perhatikan gambar di bawah ini.

CATATAN:

Merchant Backend adalah aplikasi Laravel yang sedang kita buat.

324
Snap Backend adalah backend service dari Midtrans.

Dari ilustrasi gambar alur kerja payment gatewat (Midtrans) di atas, kurang lebih detail
konsepnya seperti berikut ini :

1. Pengguna melakukan request checkout ke dalam Merchant Backend.


2. Merchant Backend membuat permintaan api ke Snap Backend untuk
mendapatkan SNAP_TOKEN.
3. Snap Backend menanggapi permintaan dan mengembalikan response ke Merchant
Backend dengan SNAP_TOKEN.
4. Merchant Backend membuat halaman HTML dan mengirimkannya kembali ke
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 tentang status
biaya.

Langkah 1 - Membuat Controller API Donation


Sekarang kita lanjutkan untuk membuat controller donation, di dalam controller ini nanti
akan kita buat beberapa fungsi yaitu :

1. menampilkan data donasi yang pernah dibuat.


2. membuat donasi baru (checkout).
3. membuat notifikasi handler, ini digunakan untuk meng-handle konfirmasi pembayaran
secara otomatis.

Silahkan jalankan perintah di bawah ini di dalam terminal/CMD untuk membuat controller
baru :

php artisan make:controller Api\\DonationController

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama DonationController.php di dalam folder app/Http/Controllers/Api.
Silahkan buka file tersebut dan ubah kode-nya menjadi seperti berikut ini :

<?php

325
namespace App\Http\Controllers\Api;

use Midtrans\Snap;
use App\Models\Campaign;
use App\Models\Donation;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;

class DonationController extends Controller


{
/**
* __construct
*
* @return void
*/
public function __construct()
{
// Set midtrans configuration
\Midtrans\Config::$serverKey =
config('services.midtrans.serverKey');
\Midtrans\Config::$isProduction =
config('services.midtrans.isProduction');
\Midtrans\Config::$isSanitized =
config('services.midtrans.isSanitized');
\Midtrans\Config::$is3ds =
config('services.midtrans.is3ds');
}

/**
* index
*
* @return void
*/
public function index()
{
//get data donations
$donations = Donation::with('campaign')->where('donatur_id',
auth()->guard('api')->user()->id)->latest()->paginate(5);

//return with response JSON


return response()->json([
'success' => true,
'message' => 'List Data Donations : '.
auth()->guard('api')->user()->name,

326
'data' => $donations,
], 200);
}

/**
* store
*
* @param mixed $request
* @return void
*/
public function store(Request $request)
{
DB::transaction(function() use ($request) {

/**
* algorithm create no invoice
*/
$length = 10;
$random = '';
for ($i = 0; $i < $length; $i++) {
$random .= rand(0, 1) ? rand(0, 9) : chr(rand(ord('a'),
ord('z')));
}

$no_invoice = 'TRX-'.Str::upper($random);

//get data campaign


$campaign = Campaign::where('slug',
$request->campaignSlug)->first();

$donation = Donation::create([
'invoice' => $no_invoice,
'campaign_id' => $campaign->id,
'donatur_id' => auth()->guard('api')->user()->id,
'amount' => $request->amount,
'pray' => $request->pray,
'status' => 'pending',
]);

// Buat transaksi ke midtrans kemudian save snap tokennya.


$payload = [
'transaction_details' => [
'order_id' => $donation->invoice,
'gross_amount' => $donation->amount,
],
'customer_details' => [

327
'first_name' =>
auth()->guard('api')->user()->name,
'email' =>
auth()->guard('api')->user()->email,
]
];

//create snap token


$snapToken = Snap::getSnapToken($payload);
$donation->snap_token = $snapToken;
$donation->save();

$this->response['snap_token'] = $snapToken;

});

return response()->json([
'success' => true,
'message' => 'Donasi Berhasil Dibuat!',
$this->response
]);
}
/**
* notificationHandler
*
* @param mixed $request
* @return void
*/
public function notificationHandler(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;
$fraud = $notification->fraud_status;

328
//data donation
$data_donation = Donation::where('invoice', $orderId)->first();

if ($transaction == 'capture') {

// For credit card transaction, we need to check whether


transaction is challenge by FDS or not
if ($type == 'credit_card') {

if($fraud == 'challenge') {
/**
* update invoice to pending
*/
$data_donation->update([
'status' => 'pending'
]);

} else {
/**
* update invoice to success
*/
$data_donation->update([
'status' => 'success'
]);

} elseif ($transaction == 'settlement') {

/**
* update invoice to success
*/
$data_donation->update([
'status' => 'success'
]);

} elseif($transaction == 'pending'){

/**
* update invoice to pending
*/
$data_donation->update([
'status' => 'pending'

329
]);

} elseif ($transaction == 'deny') {

/**
* update invoice to failed
*/
$data_donation->update([
'status' => 'failed'
]);

} elseif ($transaction == 'expire') {

/**
* update invoice to expired
*/
$data_donation->update([
'status' => 'expired'
]);

} elseif ($transaction == 'cancel') {

/**
* update invoice to failed
*/
$data_donation->update([
'status' => 'failed'
]);

}
}

Di atas, pertama kita import Snap dari Midtrans, ini akan kita gunakan untuk proses
generate SNAP_TOKEN.

use Midtrans\Snap;

330
Kemudian kita juga import Model Campaign dan Donation, karena kita akan menggunakan
Model ini untuk melakukan insert data ke dalam database.

use App\Models\Campaign;
use App\Models\Donation;

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;

Di dalam controller DonationController kita menambahkan 4 method baru, yaitu :

function __construct
function index
function store
function notificationHandler

function __construct

Method ini akan dijalankan pertama kali saat class diakses, disiini akan kita manfaatkan
untuk mengambil beberapa data dari konfigurasi Midtrans, seperti serverKey,
isProduction dan lain-lai, konfig tersebut kita panggil dari file config/services.php
yang sebelumnya sudah kita definisikan.

331
// Set midtrans configuration
\Midtrans\Config::$serverKey = config('services.midtrans.serverKey');
\Midtrans\Config::$isProduction =
config('services.midtrans.isProduction');
\Midtrans\Config::$isSanitized =
config('services.midtrans.isSanitized');
\Midtrans\Config::$is3ds = config('services.midtrans.is3ds');

function index

Method ini akan kita gunakan untuk menampilkan data donasi berdasarkan donatur yang
sedang login. Kurang lebih seperti berikut ini :

//get data donations


$donations = Donation::with('campaign')->where('donatur_id',
auth()->guard('api')->user()->id)->latest()->paginate(5);

Di atas, kita mengambil data donasi sekaligus memanggil relasi ke campaign dan untuk
menentukan data yang diambil, kita pakai function where dimana untuk parameternya
adalah ID dari user/donatur yang sedang login.

Setelah itu, kita tampilkan ke dalam response dengan format JSON, kurang lebih seperti
berikut ini :

//return with response JSON


return response()->json([
'success' => true,
'message' => 'List Data Donations : '.
auth()->guard('api')->user()->name,
'data' => $donations,
], 200);

function store

Method ini akan kita gunakan untuk melakukan proses checkout donasi, 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.

332
DB::transaction(function() use ($request) {

//...

Di dalam Database Transaction, pertama kita melakukan generate nomor invoice terlebih
dahulu. Kurang lebih seperti berikut ini :

/**
* algorithm create no invoice
*/
$length = 10;
$random = '';
for ($i = 0; $i < $length; $i++) {
$random .= rand(0, 1) ? rand(0, 9) : chr(rand(ord('a'), ord('z')));
}

$no_invoice = 'TRX-'.Str::upper($random);

Setelah itu, kita mencari data campaign berdasarkan slug yang dikirim oleh sebuah
request bernama campaignSlug.

//get data campaign


$campaign = Campaign::where('slug', $request->campaignSlug)->first();

Dan sekarang, kita lanjutkan untuk melakukan proses insert data donasi ke dalam database.

$donation = Donation::create([
'invoice' => $no_invoice,
'campaign_id' => $campaign->id,
'donatur_id' => auth()->guard('api')->user()->id,
'amount' => $request->amount,
'pray' => $request->pray,
'status' => 'pending',
]);

333
Dari proses insert data donasi di atas, kurang lebih penjelasannya seperti berikut ini :

ATTRIBUTE VALUE KETERANGAN

mengambil dari variable


invoice $no_invoice $no_invoice yang isinya adalah
hasil generate kode random

mengambil ID dari variable


campaign_id $campaign->id $campaign, yang mana isinya
adalah ID campaign

mengambil ID user/donatur yang


donatur_id auth()->guard('api')->user()->id
sedang login

mengambil isi dari request yang


amount $request->amount
bernama amount

mengambil isi dari request yang


pray $request->pray
bernama pray

kita set secara default dengan


status pending
pending

Setelah data berhasil disimpan, 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 :

// Buat transaksi ke midtrans kemudian save snap tokennya.


$payload = [
'transaction_details' => [
'order_id' => $donation->invoice,
'gross_amount' => $donation->amount,
],
'customer_details' => [
'first_name' => auth()->guard('api')->user()->name,
'email' => auth()->guard('api')->user()->email,
]
];

Di atas, kita memiliki 2 array di dalam object payload, yaitu :

transaction_details
customer_details

transaction_details

334
Akan berisi data order_id yang isinya adalah nomor invoice, kemudian untuk
gross_amount kita isi dengan data dari donasi yang amount.

customer_details

Akan berisi data first_name yang akan diisi dengan name user/donatur yang sedang login.
Dan untuk email akan diisi dengan email dari user/donatur 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 :

//create snap token


$snapToken = Snap::getSnapToken($payload);

Setelah itu, kita akan melakukan update attribute snap_token dari data donasi yang
diinsert di atas dengan isi SNAP_TOKEN dari Midtrans.

$donation->snap_token = $snapToken;
$donation->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.

return response()->json([
'success' => true,
'message' => 'Donasi Berhasil Dibuat!',
$this->response // <-- data SNAP_TOKEN dari Midtrans
]);

function notificationHandler

Method ini akan digunakan untuk menerima response data berupa status pembayaran yang

335
dikirim dari Midtrans. 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.

$validSignatureKey = hash("sha512", $notification->order_id .


$notification->status_code . $notification->gross_amount .
config('services.midtrans.serverKey'));

Jika nilai dari $notification dan $validSignaturKey tidak sama, maka akan
melakukan return dengan status code 403.

if ($notification->signature_key != $validSignatureKey) {
return response(['message' => 'Invalid signature'], 403);
}

Jika data signatur ditemukan, maka kita akan melakukan query ke dalam table
donations berdasarkan data order ID/invoice yang dikirim dari Midtrans.

$transaction = $notification->transaction_status;
$type = $notification->payment_type;
$orderId = $notification->order_id;
$fraud = $notification->fraud_status;

//data donation
$data_donation = Donation::where('invoice', $orderId)->first();

Setelah itu, kita akan melakukan pengecekan kondisi pembayaran, kurang lebih kode-nya
seperti berikut ini :

336
if ($transaction == 'capture') {

if ($type == 'credit_card') {
if($fraud == 'challenge') {
//pembayaran via credit cart "pending"
} else {
//pembayaran via credit card "success"
}
}

} elseif ($transaction == 'settlement') {

//pembayaran selesai "success"

} elseif($transaction == 'pending'){

//pembayaran pending "pending"

} elseif ($transaction == 'deny') {


//pembayaran gagal "failed"

} elseif ($transaction == 'expire') {

//pembayaran kadaluwarsa "expired"

} elseif ($transaction == 'cancel') {

//pembaran di batalkan "cancel"

Di atas, jika $transaction yang dikirim dari Midtrans bernilai capture, maka akan
menjalankan beberapa kondisi untuk melakukan update field status yang ada di dalam
table donations sesuai dengan status yang di dapatkan.

Langkah 2 - Membuat Route API Donation


Sekarang kita akan menambahkan sebuah route untuk API donation. SIlahkan buka file
routes/api.php kemudian tambahkan route berikut ini di bawah route profile.

337
/**
* Api Donation
*/
Route::get('/donation', [DonationController::class,
'index'])->middleware('auth:api');
Route::post('/donation', [DonationController::class,
'store'])->middleware('auth:api');
Route::post('/donation/notification', [DonationController::class,
'notificationHandler']);

Di atas, untuk route index dan store kita berikan middleware('auth:api'), karena
kedua route tersebut hanya boleh diakses jika user/donatur sudah melakukan proses
otentikasi/login.

Sedangkan untuk route notification kita set menjadi global, karena endpoint API ini
akan menerima sebuah request yang dikirim dari website Midtrans untuk mengubah status
pembayaran.

Langkah 3 - Uji Coba API Donation


Sekarang kita akan lakukan uji coba untuk menampilkan data donasi berdasarkan
user/donatur yang sedang login. Silahkan buka aplikasi Postman dan masukkan URL berikut
ini http://localhost:8000/api/donation dan untuk method-nya silahkan pilih GET. Kemudian
klik tab Headers dan masukkan beberapa key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer Token


CATATAN! : silahkan ganti Token dengan token hasil generate dari proses login.

Sekarang silahkan klik Send dan jika berhasil maka kita akan menampilkan data donasi
berdasarkan user/donatur yang sedang login.

338
Untuk proses uji coba method store dan notification akan kita lakukan menggunakan
Vue Js nantinya.

339
PENGENALAN VUE JS & VUEX

340
Berkenalan Dengan Vue Js

Apa itu Vue Js ?


Vue (diucapkan / vjuː /, atau view) adalah kerangka kerja progresif untuk membangun
antarmuka pengguna. Tidak seperti kerangka kerja monolitik lainnya, Vue dirancang dari
bawah ke atas agar dapat diadopsi secara bertahap.

Vue dibangun untuk difokuskan pada sisi tampilan dan mudah digunakan dan diintegrasikan
dengan libarary lain atau proyek yang sudah ada. Selain itu Vue juga sangat bagus
digunakan untuk membuat aplikasi SPA atau Single Page Application ketika di gabungkan
dengan library pendukung, seperti Vue Router, Vuex dan yang lainnya. Berikut link library
pendukung secara lengkap : https://github.com/vuejs/awesome-vue#components--libraries

Sejarah Vue Js
Vue Js dibuat oleh Evan You pada tahun 2013 yaitu seorang mantan software engineer di
Google yang sebelumnya ikut dalam pengembangan Angular Js. Evan You sebelum fulltime
dengan Vue Js beliau juga pernah bekerja dalam pengembangan Meteor Js.

Untuk versi pertama (0.6) dari Vue Js di rilis pada tanggal 8 Desember 2013 yang kemudian
berlanjut ke versi berikutnya (0.7) pada tanggal 24 Desember 2013. Berikut daftar lengkap
versi rilis dari Vue Js.

VERSI TANGGAL JUDUL

3.0 18 September 2020 One Piece

2.6 4 Februari 2019 Macross

2.5 13 Oktober 2017 Level E

2.4 13 Juli 2017 Kill la Kill

341
VERSI TANGGAL JUDUL

2.3 27 April 2017 JoJo's Bizarre Adventure

2.2 26 Februari 2017 Initial D

2.1 22 November 2016 Hunter X Hunter

2.0 30 September 2016 Ghost in the Shell

1.0 27 Oktober 2015 Evangelion

0.12 12 Juni 2015 Dragon Ball

0.11 7 November 2014 Cowboy Bebop

0.10 23 Maret 2014 Blade Runner

0.9 25 Februari 2014 Animatrix

0.8 27 Januari 2014 N/A

0.7 24 Desember 2013 N/A

0.6 8 Desember 2013 N/A

Membuat Aplikasi Pertama di Vue Js


Sekarang, kita akan belajar membuat aplikasi sederhana menggunakan Vue Js. Silahkan
buat file dengan nama index.html dan masukkan kode berikut ini :

342
<!DOCTYPE html>
<html>
<head>
<title>Belajar Vue Js</title>
</head>
<body>

<div id="app">
{{ message }}
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
Vue.createApp({

data() {
return {
message: 'Hello World!'
}
}

}).mount('#app')
</script>
</body>
</html>

Di atas, pertama kita panggil Vue Js 3 dari CDN atau online.

<script src="https://unpkg.com/vue@next"></script>

Setelah itu kita membuat element kontainer pada HTML yang akan digunakan oleh Vue Js
untuk menginisialisasi template ke dalam Vue. Di atas kita menggunakan element <div>
dengan menambahkan attribute id="app".

<div id="app"> <!-- ini adalah inisialisasi vue ke attribute


HTML id "app" -->
{{ message }}
</div>

Kemudian kita membuat object dari class Vue.

343
Vue.createApp({
// ...
})

Di dalamnya kita mendefinisikan properti/state data dengan nama message.

data() {
return {
message: 'Hello World!'
}
}

Pada Vuejs, kita menggunakan tanda kurung {{ ... }} untuk menampilkan isi variabel di
dalam template.

state/properti message di atas akan di render di dalam template menggunakan {{


message }}. Terakhir, kita mount atau set aplikasi Vue Js kita ke dalam sebuah attribute
HTML yang memiliki ID app.

.mount('#app')

Jika di jalankan, kurang lebih hasilnya seperti berikut ini :

344
Two-way Data Binding di Vue Js
Vue Js dikenal sangat bisa diandalkan untuk membuat data secara realtime/reactive. Disini
kita mencoba belajar tentang Two-way data binding di dalam Vue Js. Silahkan buat file
dengan nama two-way-data-binding.html dan masukkan kode berikut ini :

345
<!DOCTYPE html>
<html>
<head>
<title>Two-way Data Binding</title>
</head>
<body>

<div id="app">
<div>
{{ message }}
</div>

<input type="text" v-model="message">

</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
Vue.createApp({

data() {
return {
message: 'Hello World!'
}
}

}).mount('#app')
</script>
</body>
</html>

Di atas, kita menambahkan sebuah input dengan directive v-model, dimana directive ini
akan memperbarui nilai yang ada di dalam properti/state message.

346
Jika di jalankan maka hasilnya seperti berikut ini :

347
Composition API dan Reactivity API

Composition API
Composition API adalah fitur baru di dalam Vue Js 3, ini merupakan fitur tamban yang
akan mempermudah kita dalam mengembangkan aplikasi dengan sekala besar. Dengan
Composition API kita bisa menggunakan ulang (reusable) kode-kode yang kita buat di
dalam component ke component yang lainnya. Di Vue Js 3 kita masih bisa menggunakan
Option API. Dan Composition API ini bersifat tidak wajib digunakan, tapi jika kita
menggunakan ini kode yang ditulis akan menjadi semakin rapi dan mudah di maintenance.

Kenapa Harus Menggunakan Composition API ?


Berikut ini beberapa alasan kenapa kita harus menggunakan Composition API,
diantaranya adalah :

1. Limitasi/batasan di dalam Vue Js 2


Ketika aplikasi yang dikembangkan semakin besar, maka kode yang ada di
dalam component semakin banyak dan sulit di pahami dan terlihat buruk.
kurang fleksibel dan sulitanya menggunakan ulang (reusable) logic antar
component.
Kurangnya dukungan TypeScript
2. Lebih Fleksibel dalam membuat kode di dalam component.
3. lebih mudah menggunakan ulang (reusable) logic di dalam component maupun luar
component.
4. Kode yang ditulis lebih mudah di pahami dan di kembangkan.

Berikut ini gambaran baris kode jika kita menggunakan Composition API di bandingkan
Option API.

348
Dari gambar di atas, bisa kita lihat dengan menggunakan Composition API maka penulisan
kode menjadi lebih baik dan terstruktur. Dan memiliki lebih sedikit block kode, yang artinya
akan semakin mudah di dalam proses maintenance

Contoh Penulisan Option API dan Composition API


Jika teman-teman masih bingung perbedaan antara Option API dan Compotion API, maka
berikut ini saya berikan contoh kode-nya.

349
export default {

data() {
return() {
//define user state
email: '',
password: ''
}
},
methods: {
//method login
login() {
//define varibale and get from state user
let email = this.email
let password = this.password

//logic action login


.....
}
}
}

Di atas merupakan contoh kode Vue Js yang ditulis menggunakan Option API dan cara
penulisan kode seperti berikut ini sudah umum digunakan oleh kebanyakan orang saat ini.

Composition API
Berikut ini contoh penulisan kode yang sama dari yang di atas, tapi akan kita tulis
menggunakan Composition API.

350
import { reactive } from 'vue'

export default {

setup() {

//define user state


const user = reactive({
email: '',
password: ''
})
//method login
function login() {
//define varibale and get from state user
let email = user.email
let password = user.password
//logic action login
.....
}
return {
user, // return state user
login // return method login
}
}
}

Jika kita perhatikan dari penulisan kode menggunakan Compostion API, kita sudah tidak
menambahkan seperti data, method, dan lain-lain, karena konsepnya akan diganti
menggunakan function setup. Dengan konsep seperti ini maka akan memungkinkan kita
menggunakan kode yang sama di dalam banyak component.

Reactivity API
Di dalam Vue Js 3 kita mendapatkan fitur baru yaitu Reactivity API, fitur ini digunakan
untuk membuat sebuah variable atau state menjadi reaktif. Disini kita akan membahas
tentang reactive dan ref. Dimana keduanya sama-sama bisa membuat varibale manjadi
reaktif. Kurang lebih penjelasannya seperti berikut ini :

Reactive

Reactive digunakan untuk mengambil sebuah object dan mengembalikan nilainya


secara reaktif ke object aslinya.

351
Ref

ref digunakan saat penggunaan data primitif seperti Boolean, String, Integer. Ketika
kita menggunakan ref di dalam function setup, maka untuk menge-set dan menge-
get sebuah data, kita bisa menggunakan single object .value.

Reactivity API Reactive

Berikut ini contoh penggunaan Reactivity API reactive.

import { reactive } from 'vue'

setup () {
//define varibale dengan reactive
const user = reactive({
name: 'Fika Ridaul Maulayya',
gender: 'male'
})
return {
user // return state user
}
}

Di atas, kita mendefinisikan sebuah state user dengan jenis reactive dan di dalamnya
kita mendefinisikan 2 object baru yang bernama name dan gender.

//define varibale dengan reactive


const user = reactive({
name: 'Fika Ridaul Maulayya',
gender: 'male'
})

Setelah itu, agar state user tersebut dapat digunakan di template HTML, kita harus
melakukan return terlebih dahulu.

return {
user // return state user
}

352
kemudian, untuk menampilkannya di template, kita bisa menggunakan kode seperti berikut
ini :

<div>
Name: {{ user.name }}
Gender: {{ user.gender }}
</div>

Di atas, kita panggil dulu nama state-nya yaitu user kemudian diberikan notasi (.) dan
diikuti nama object-nya.

Reactivity API Ref

Sekarang, kita akan belajar tentang Reactivity API ref, kurang lebih seperti berikut ini :

import { ref } from 'vue'

setup () {
//define varibale dengan ref
const count = ref(0)
// set count
count.value = 100
// get count
console.log(count.value) // output : 100
return {
count
}
}

Di atas, pertama kita definisikan sebuah state baru dengan nama count, dimana state ini
menggunakan jenis ref dan memiliki default value 0.

Selanjutnya, kita akan mencoba memberikan/mengubah nilai state tersebut, dan untuk
mengubah nilai state dengan jenis ref, maka kita harus menggunakan single object
.value.

// set count
count.value = 100

Di atas, kita ubah nilai state count menjadi 100, terus bagaimana cara menampilkan dan

353
mendapatkan nilai dari state count tersebut ?. Kita bisa mencoba menampilkannya kurang
lebih seperti berikut ini :

// get count
console.log(count.value)

Di atas, kita juga menggunakan single object .value untuk mendapatkan nilai dari state
count tersebut. Dan output-nya adalah aangka 100.

Dan agar bisa ditampilkan di template HTML, kita harus melakukan return terlebih dahulu.

return {
count
}

Kemudian, untuk menampilkannya di template HTML kita tidak perlu lagi menggunakan
single object .value, jadi kita bisa langsung seperti berikut ini :

<div>
{{ count }}
</div>

354
Lifecycle Hooks

Lifecycle Hooks di Vue Js merupakan seraingkain kode yang di eksekusi secara berurutan
ketika sebuah instance di buat. Misalnya, perlu menyiapkan data, render template,
memasang instance ke DOM, dan memperbarui DOM ketika data berubah. Disini
memungkinkan pengguna menambahkan kode pada tahap tertentu.

Lifecycle Diagram
Di bawah ini merupakan gambaran alur lifecycle yang ada di dalam Vue Js.

355
356
Lifecycle di Vue Js 2 dan Vue Js 3 bekerja dengan sangat mirip, disini kita masih memiliki
akses ke hook yang sama dan kita masih menggunakannya untuk kasus penggunaan yang
sama. Namun, dengan diperkenalkannya Composition API, maka sekarang untuk
mengakses hook tersebut telah berubah.

Seperti yang sudah kita bahas sebelumnya, di dalam Composition API kita menggunakan
function setup, dimana di dalam function tersebut bisa digunakan untuk menangani banyak
fungsi, seperti reaktifitas, lifecycle dan lain-lain.

Singkatnya, Composition API memungkinkan kita mengatur kode dengan lebih baik
menjadi fitur yang lebih modular daripada memisahkan berdasarkan jenis properti seperti
method, computed, data dan lain-lain.

Berikut ini perbedaan penggunaan hook di dalam Option API dan Composition API.

OPTION API COMPOSITION API (di dalam setup)

beforeCreate Tidak dibutuhkan*

created Tidak dibutuhkan*

beforeMount onBeforeMount

mounted onMounted

beforeUpdate onBeforeUpdate

updated onUpdated

beforeUnmount onBeforeUnmount

unmounted onUnmounted

errorCaptured onErrorCaptured

renderTracked onRenderTracked

renderTriggered onRenderTriggered

Di atas bisa kita lihat bersama, ketika kita menggunakan hook di dalam Composition API,
maka kita hanya perlu menambahkan on di depannya.

Ada 13 lifecycle Vue menurut dokumentasi Resmi Vue terbaru. Setiap instance Vue yang
dibuat akan melalui lifecycle. Disini kita akan belajar untuk menerapkan beberapa lifecycle
menggunakan Option API. Berikut ini adalah contoh penggunaan beberapa lifecycle hooks
di Vue Js.

357
Creation
Creation Hooks merupakan lifecycle pertama kali di jalankan pada suatu component. Ini
memungkinkan kita menambahkan beberapa kode sebelum dan sesudah component meng-
update sebuah DOM. Tidak seperti Hooks yang lainnya, Creation Hooks juga tetap akan
dijalankan selama rendering di sisi server. Di dalam Creation Hooks terdapat 2 lifecycle
yaitu :

beforeCreate
created

beforeCreate

Lifecycle beforeCreate akan di jalankan pertama kali di dalam component saat sebelum
semuanya di-inisialisasi oleh Vue. Contohnya seperti berikut ini :

358
<!DOCTYPE html>
<html>
<head>
<title>beforeCreate - Option API</title>
</head>
<body>

<div id="app">
{{ message }}
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
Vue.createApp({
data () {
return {
message: 'Hello World!'
}
},

beforeCreate () {
//outputnya undefined, karena properti/state message belum
diinisialisasi oleh Vue
console.log(this.message)
}

}).mount('#app')
</script>
</body>
</html>

Di atas, di dalam hook beforeCreate kita memanggil properti/state message dengan


value Hello World!. Saat file dijalankan maka akan menghasilkan undifened, karena
hook beforeCreate dijalankan sebelum properti/state message di inisialisasi.

359
created

Lifecycle Hooks created akan dijalankan setelah instace dibuat oleh Vue, umunya
lifecycle hooks created dimanfaatkan untuk mendapatkan data dari Rest API kemudian
meng-assign-nya pada sebuah properti/state data. Contoh sederhanya seperti berikut ini :

360
<!DOCTYPE html>
<html>
<head>
<title>created - Option API</title>
</head>
<body>

<div id="app">
{{ message }}
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
Vue.createApp({
data () {
return {
message: 'Hello World!'
}
},

created () {
//outputnya Hello World!, karena properti/state message
sudah diinisialisasi oleh Vue
console.log(this.message)
}

}).mount('#app')
</script>
</body>
</html>

Di atas, di dalam hook created kita memanggil properti/state message dengan value
Hello World!. Saat file ini dijalankan, maka akan menghasilkan Hello World!, karena
properti/state message sudah diinisialisasi oleh Vue sebelum hook created dijalankan.

361
Mounting (Insert DOM)
Lifecycle ini merupakan jenis lifecycle yang ada pada Vue yang di fungsikan untuk dapat
mengakses DOM persis sebelum dan sesudah template di render. Mounting memiliki 2
jenis lifecycle hooks yaitu :

beforeMount
mounted

beforeMount

Lifecycle Hooks beforeMount akan dijalankan sebelum template pada suatu component
di render, contoh sederhanya seperti berikut ini :

362
<!DOCTYPE html>
<html>
<head>
<title>beforeMount - Option API</title>
</head>
<body>

<div id="app">
</div>

<script src="https://unpkg.com/vue@next"></script>
<script>
Vue.createApp({
template: '<div id="template">{{ data }}</div>',
data () {
return {
data: 'Hello World!'
}
},

beforeMount () {
// hasilnya adalah null, karena template dengan attribute
id="template" belum di render
console.log(document.getElementById('template'))
}

}).mount('#app')
</script>
</body>
</html>

Di atas, di dalam hook beforeMount kita memanggil element HTML dengan attribute id
"template". Dan saat file ini di jalankan, maka akan menghasilkan null, karena template
tersebut belum di render sebelum hook beforeMount dijalankan.

363
mounted

Lifecycle Hook mounted akan dijalankan setelah template berhasil di render. Hooks ini
sering digunakan untuk memodifikasi sebuah DOM template dan bisa dengan mudah
mendapatkan akses di seluruh component, data dan lain-lain, Contoh sederhanya seperti
berikut ini :

364
<!DOCTYPE html>
<html>
<head>
<title>mounted - Option API</title>
</head>
<body>

<div id="app">
</div>

<script src="https://unpkg.com/vue@next"></script>
<script>
Vue.createApp({
template: '<div id="template">{{ data }}</div>',
data () {
return {
data: 'Hello World!'
}
},

mounted () {
// hasilnya adalah : <div id="template">Hello World!</div>,
karena template sudah di render.
console.log(document.getElementById('template'))
}

}).mount('#app')
</script>
</body>
</html>

Di atas, di dalam hook mounted kita memanggil element HTML dengan attribute id
"template". Dan saat file ini di jalankan, maka akan menghasilkan <div
id="template">Hello World!</div>, karena template tersebut sudah di render
sebelum hook mounted dijalankan.

365
Updating (Re-render)
Lifecycle Hooks ini akan dijalankan setiap ada perubahan atau manipulasi pada properties
data di dalam component. Atau sesuatu lain yang menyebabkan di render. Jangan gunakan
jenis hook ini jika tidak tahu kapan properti secara reaktif pada component berubah, sebagai
gantinya gunakanlah computed atau watched.

Computed merupakan sebuah properti yang digunakan untuk melakukan pemrosesan data
lain. Nilai dari Computed hanya akan diperbarui jika ada data baru di dalam sumber data.

Sedangkan untuk Watched merupakan sebuah properti yang mirip dengan Computed yaitu
sama-sama akan dijalankan ketika sebuah data ada yang berubah di dalam Instance Vue
dan melakukan beberapa action. Di dalam lifecycle ini terdapat 2 hooks yaitu :

beforeUpdate
updated

beforeUpdate

Lifecycle beforeUpdate ini akan dijalankan ketika sebuah properti data pada component
diupdate sebelum DOM melakukan render ulang template dengan data yang diperbarui.
Contoh sederhananya seperti berikut ini :

366
<!DOCTYPE html>
<html>
<head>
<title>beforeUpdate - Option API</title>
</head>
<body>

<div id="app">
<h1 ref="h1-element">{{ message }}</h1>
<button @click="message = 'Message is updated';">Ubah
Message</button>
</div>

<script src="https://unpkg.com/vue@next"></script>
<script>
Vue.createApp({

data () {
return {
message: 'Hello World!'
}
},

beforeUpdate() {
//ketika klik button Ubah Message hasilnya adalah: Hello World!
console.log(this.$refs["h1-element"].textContent)
}

}).mount('#app')
</script>
</body>
</html>

Di atas, di dalam hook beforeUpdate kita melakukan perubahan pada properti/state


message, saat button Ubah Message di klik. Dan saat itu nilai properti/state message yang
berisi value Hello World! akan dirender lebih dulu sebelum nilai dari properti/state
berubah menjadi Message is ipdated.

367
updated

Lifecycle Hooks updated akan dijalankan ketika sebuah data berhasil di update dan DOM
berhasil melakukan render ulang dengan data yang sudah diperbarui. Jika kita
membutuhkan akses DOM setelah properti dirubah, maka hooks ini tepat untuk digunakan.
Contoh sederhanya seperti berikut ini :

368
<!DOCTYPE html>
<html>
<head>
<title>updated - Option API</title>
</head>
<body>

<div id="app">
<h1 ref="h1-element">{{ message }}</h1>
<button @click="message = 'Message is updated';">Ubah
Message</button>
</div>

<script src="https://unpkg.com/vue@next"></script>
<script>
Vue.createApp({

data () {
return {
message: 'Hello World!'
}
},

updated() {
//ketika klik button Ubah Message hasilnya adalah: Message is
updated, karena data pada properties diperbaru setelah template berhasil
di render ulang dengan data yang terbaru.
console.log(this.$refs["h1-element"].textContent)
}

}).mount('#app')
</script>
</body>
</html>

Di atas, di dalam hook updated kita melakukan perubahan pada properti/state message.
Saat melakukan klik button Ubah Message, maka nilai dari properti/state message akan
berubah menjadi Message is updated, setelah template dirender ulang.

369
Unmounting
Di Vue Js 3 beforeDestroy() juga bisa ditulis sebagai beforeUnmount(). Dan
destroyed() bisa ditulis sebagai unmounted(). Kata Evan You tentang perubahan ini,
beliau menyebutkan itu hanya konvensi penamaan yang lebih baik, karena Vue memasang
(mounts) dan melepas (unmounts) komponen.

beforeUnmount

Hooks ini akan dipanggil sebelum instance dari Vue di lepas. Instance dan semua fungsi
masih tetap berjalan di dalam hooks ini. Disini umunya digunakan untuk menghapus sebuah
variable, menghapus component dan lain-lain.

unmounted

Dipanggil setelah instance component dilepas. Ketika hook ini dipanggil, semua directive
dari instance component telah dilepas, semua event listener telah dihapus, dan semua child
component juga telah dilepas. Disini properti datanya masih ada tetapi tidak dapat diakses.

370
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 ?

Apa itu Vuex ?


Menurut situs resminya di https://next.vuex.vuejs.org/, kurang lebih seperti berikut ini :

Vuex is a state management pattern + library for Vue.js applications. It serves as a


centralized store for all the components in an application, with rules ensuring that the state
can only be mutated in a predictable fashion.

Jadi secara sederhana, Vuex merupakan state manajemen pattern dan library untuk Vue Js
yang digunakan untuk membuat store secara centralized untuk semua component yang ada
di dalam aplikasi.

Kapan Saya Harus Menggunakannya?


Vuex digunakan ketika aplikasi yang kita kembangkan menjadi semakin besar dan sulitnya
mengelola data, dengan demikian menggunakan Vuex merupakan cara terbaik untuk
masalah ini. Tapi jika aplikasi yang kita kembangkan masih bersekala kecil, maka kita tidak
perlu menggunakan Vuex, karena masih bisa ditangani oleh beberapa props dan emit.

Konsep di dalam Vuex

371
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 object yang digunakan untuk mendefinisikan dan menyimpan
semua data yang ada di dalam aplikasi secara reaktif. Berikut ini contoh menggunakan state
untuk menyimpan sebuah data :

372
import { createStore } from 'vuex'

const store = createStore({


//state
state: {
name: 'Fika Ridaul Maulayya',
gender: 'male'
}
})

export default store

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" -->

<h1>Jenis kelamin: {{ gender }}</h1> <!-- output "male" -->

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 :

373
import { createStore } from 'vuex'

const store = createStore({


//state
state: {
todos: [
{
title: 'Task 1',
completed: false
},
{
title: 'Task 2',
completed: true
},
{
title: 'Task 3',
completed: false
}
]
},
//getters
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
},
}
})

export default store

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 :

374
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 :

<h1>Done: {{ done }}</h1> <!-- output "1" -->

<h1>Pending: {{ pending }}</h1> <!-- output "2" -->

Mutations
Mutations merupakan salah satu store yang digunakan untuk melakukan perubahan nilai
padat state secara reaktif. Jadi mutation ini bisa dibilang mirip event yang ada di dalam
store. Mutation terdiiri dari sebuah string nama dan handler-nya.

Kurang lebih contohnya seperti berikut ini :

375
import { createStore } from 'vuex'

const store = createStore({


//state
state: {
count: 1
},
//mutations
mutations: {
increment (state) {
// mutate state
state.count++
}
}
})

export default store

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 :

action -> mutation -> state

KETERANGAN: pengguna melakukan sebuah action, kemudian action memanggil mutation


untuk melakukan perubahan data di dalam state.

376
import { createStore } from 'vuex'

const store = createStore({


//state
state: {
count: 0
},
//mutations
mutations: {
SET_COUNT(state) {
state.count++
}
},
//actions
actions: {
increment({ commit }) {
//commit ke mutation SET_COUNT
commit.commit('SET_COUNT')
}
}
})

export default store

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.

Dan untuk menggunakan/memanggil action di dalam global component, kita bisa


menggunakan kode seperti berikut ini :

this.$store.dispatch('increment')

Modules
Meskipun sudah menggunakan Vuex, jika aplikasi yang dikembangkan semakin besar,
maka untuk proses maintenance di dalam Vuex juga akan kesulitan. Di dalam Vuex state
merupakan sebuah single tree json, yang artinya semua data akan di lettakn jadi satu di
dalam state tersebut.

Jadi ketika kita mempunyai banyak state yang harus di handle, maka datanya akan

377
membengkak dan susah untuk di maintenance. Disinilah peran modules, yaitu digunakan
untuk memecah/membagi beberapa state ke dalam module-module kecil. Menariknya
masing-masing module ini selain berisi state, juga bisa berisi mutation, getter, dan action.

Contohnya seperti kode berikut ini :

import { createStore } from 'vuex'

//define module auth


const auth = {
state: {
isLoggedIn: false
},
mutations: { ... },
actions: { ... },
getters: { ... }
}

//define module user


const user = {
state: {
name: 'Fika Ridaul Maulayya'
},
mutations: { ... },
actions: { ... }
}

//register module ke Vuex store


const store = createStore({
modules: {
auth, // <-- module auth
user // <-- module user
}
})
export default store

Di atas, kita membuat 2 module yaitu auth dan user, kemudian kita malkukan register
module tersebut di dalam store Vuex. Kita juga bisa menaruh module-module tersebut di
dalam file yang terpisah.

Untuk memanggil module di dalam global component, kita bisa menggunakan kode seperti
berikut ini :

378
//get state "isLoggedIn" from module "auth"
this.$store.state.auth.isLoggedIn
//output: false

//get state "name" from module "user"


this.$store.state.user.name
//output: Fika Ridaul Maulayya

TAMBAHAN
Karena nanti kita akan menggunakan Composition API, maka ada cara kusus untuk
pemanggilan Vuex. Ketika kita menggunakan Composition API, maka kita tidak akan bisa
menggunakan global Vuex dengan this.$store lagi. Disini kita akan menggunakan hook
yang bernama useStore. Kurang lebih contohnya seperti berikut ini :

import { createStore } from 'vuex'

const store = createStore({


//state
state: {
name: 'Fika Ridaul Maulayya',
gender: 'male'
}
})

export default store

Kemudian di dalam component yang menggunakan Composition API, kita memanggilnya


seperti berikut ini :

379
//import useStore from vuex
import { useStore } from 'vuex'

export default {
//inisialisasi Composition API
setup() {
//define store dari hook useStore
const store = useStore()
//get state "name"
console.log(store.state.name) // <-- output: "Fika Ridaul
Maulayya"

}
}

380
Membuat Aplikasi Pertama Dengan Vuex

Sebelum kita lanjutkan belajar Vue Js untuk membangun halaman frontend dari website
yang kita bangun, maka sekarang kita akan belajar terlebih dahulu untuk membuat aplikasi
sederhana menggunakan Vue Js dan Vuex. Dimana untuk Vuex akan kita gunakan untuk
melakukan centralized data atau data yang terpusat dan bersifat global dan bisa digunakan
di dalam semua component.

Sekarang, kita bersama-sama akan belajar membuat aplikasi todo list sederhana
menggunakan Vuex. Dan kita akan menggunakan Vue Js 3 dan Vuex 4 dengan CDN, disini
kita akan pecah-pecah perbagian supaya mudah untuk di pahami. Seperti menampilkan
todo, input todo, complete todo dan delete todo.

Langkah 1 - Menampilkan Data Menggunakan State


Silahkan teman-teman buat folder baru dengan nama learning-vuex , untuk tempatnya
bebas. Kemudian silahkan buat file baru di dalam folder tersebut dengan nama vuex-
state.html dan masukkan kode berikut ini :

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-
scale=1.0">
<title>Belajar Vuex - State</title>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.mi
n.css">
</head>

<body style="background: lightgray;">

<div id="app">

<div class="container mt-5">


<div class="row">
<div class="col-md-12">
<ul class="list-group border-0 shadow">
<li v-for="(todo, index) in todos" :key="index"
class="list-group-item">
{{ todo.title }}

381
</li>
</ul>
</div>
</div>
</div>

</div>

<script src="https://unpkg.com/vue@next"></script>
<script
src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>

<script>
//create store vuex
const store = Vuex.createStore({

//state
state: {
todos: [{
title: 'Task 1',
completed: false
},
{
title: 'Task 2',
completed: true
},
{
title: 'Task 3',
completed: false
}
]
}

})

//vue Create App


const app = Vue.createApp({

computed: {
todos() {
//get todos from state
return this.$store.state.todos
}
}

382
})
//use vuex
app.use(store)
//mount to attribute ID app
app.mount('#app')
</script>

</body>
</html>

Di atas, kita membuat 2 block kode, yang pertama untuk mendeklarasikan sebuah Vuex
dan yang kedua sebuah Vue Instance.

Pertama, kita membuat sebuah variable yang bernama store dimana di dalamnya kita
membuat sebuah obejct dari Vuex yang isinya adalah sebuah state yang bernama todos.
Dimana di dalam state todos kita memiliki beberapa data object array, yaitu ada key
title dan completed.

//create store vuex


const store = Vuex.createStore({

//state
state: {
todos: [{
title: 'Task 1',
completed: false
},
{
title: 'Task 2',
completed: true
},
{
title: 'Task 3',
completed: false
}
]
}

})

Selanjutnya, kita membuat variable dengan nama app yang isinya adalah sebuah deklarasi
Vue Instance. Dan di dalamnya kita menambahkan hook yang bernama computed dan di
dalam hook tersebut kita membuat method yang bernama todos() dan di dalamnya

383
memanggil sebuah state yang bernama todos dari Vuex.

todos() {
//get todos from state
return this.$store.state.todos // <-- get data from state "todos"
Vuex
}

Kemudian, agar Vuex dapat digunakan di dalam Vue Instance, maka kita gunakan plugin
use untuk meng-import Vuex Store di dalam aplikasi Vue.

//use vuex
app.use(store)

Selanjutnya, kita mount aplikasi Vue kita ke dalam sebuah element HTML dengan attribute
ID app.

//mount to attribute ID app


app.mount('#app')

Kode di atas, akan mencari sebuah element HTML yang memiliki attribute ID app.

<div id="app">

//...

</div>

Semua kode yang di tulis di dalam div akan menjadi bagian dari Vue Js. Kemudian untuk
menampilkan data, kita menggunakan directive v-for kurang lebih seperti berikut ini :

384
<ul class="list-group border-0 shadow">
<li v-for="(todo, index) in todos" :key="index" class="list-group-
item">
{{ todo.title }}
</li>
</ul>

Di atas, kita melakukan perulangan data todos yang diambil dari hook computed
menggunakan directive v-for. Dan kita menampilkan title dari data todo tersebut.

{{ todo.title }}

Sekarang jika kita coba jalankan, maka akan menghasilkan output kurang lebih seperti
berikut ini :

Di atas, kita menampilkan data yang diambil dari state todos di dalam Vuex.

Langkah 2 - Menampilkan Data Menggunakan Getters


Sekarang, kita akan belajar menampilkan jumlah todo list yang sudah selesai dan pending,
disini kita akan memanfaatkan Getters di Vuex. Silahkan buat file baru dengan nama
vuex-getters.html dan masukkan kode berikut ini :

385
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-
scale=1.0">
<title>Belajar Vuex</title>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.mi
n.css">
</head>

<body style="background: lightgray;">

<div id="app">

<div class="container mt-5">


<div class="row">
<div class="col-md-12">
<ul class="list-group border-0 shadow">
<li v-for="(todo, index) in todos" :key="index"
class="list-group-item">
{{ todo.title }}
</li>
</ul>
<div class="status mt-3">
<h5>DONE: <strong>{{ done }}</strong></h5>
<h5>PENDING: <strong>{{ pending }}</strong>
</h5>
</div>
</div>
</div>
</div>

</div>

<script src="https://unpkg.com/vue@next"></script>
<script
src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>

<script>
//create store vuex
const store = Vuex.createStore({

386
//state
state: {
todos: [{
title: 'Task 1',
completed: false
},
{
title: 'Task 2',
completed: true
},
{
title: 'Task 3',
completed: false
}
]
},

//getters
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
},

})

//vue Create App


const app = Vue.createApp({

computed: {

todos() {
//get todos from state
return this.$store.state.todos

387
},

done() {
//get todos completed from getters vuex
return this.$store.getters.doneTodos
},

pending() {
//get todos pending from getters vuex
return this.$store.getters.pendingTodos
}
}

})
//use vuex
app.use(store)
//mount to attribute ID app
app.mount('#app')
</script>

</body>
</html>

Di atas, kita menambahkan getters di dalam Vuex Store, di dalam di dalam getters
tersebut kita membuat 2 method yaitu, doneTodos dan pendingTodos.

Untuk method doneTodo di dalamnya kita melakukan filter state todos yang memiliki
status completed yang true dan kita count dengan .length. Tujuannya agar
mengembalikan sebuah jumlah data. Begitu juga dengan pendingTodos yaitu kebalikan
dari doneTodos.

388
//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
},

Selanjutnya, di dalam Vue dan pada hook computed kita menambahkan 2 method juga
yang bernama done dan pending. Dimana di dalam kedua method tersebut memanggil
sebuah getters dari Vuex yang bernama doneTodos dan pendingTodos.

done() {
//get todos completed from getters vuex
return this.$store.getters.doneTodos
},

pending() {
//get todos pending from getters vuex
return this.$store.getters.pendingTodos
}

Dan untuk menampilkan di template, kita tinggal panggil nama method di atas. Kurang lebih
seperti berikut ini :

<div class="status mt-3">


<h5>DONE: <strong>{{ done }}</strong></h5>
<h5>PENDING: <strong>{{ pending }}</strong> </h5>
</div>

Jika kita coba jalankan, maka kurang lebih hasilnya seperti berikut ini :

389
Langkah 3 - Input Data Menggunakan Actions dan Mutations
Sekarang, kita akan belajar membuat input data todo menggunakan Actions dan
Mutations. Silahkan buat file baru dengan nama vuex-input-todo.html dan masukkan
kode berikut ini :

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-
scale=1.0">
<title>Belajar Vuex</title>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.mi
n.css">
</head>

<body style="background: lightgray;">

<div id="app">

<div class="container mt-5">


<div class="row">
<div class="col-md-12">

390
<div class="mb-3">
<input type="text" class="form-control" v-
model="todo" @keyup.enter="addTodo" placeholder="masukkan todo list dan
enter">
</div>
<ul class="list-group border-0 shadow">
<li v-for="(todo, index) in todos" :key="index"
class="list-group-item">
{{ todo.title }}
</li>
</ul>
<div class="status mt-3">
<h5>DONE: <strong>{{ done }}</strong></h5>
<h5>PENDING: <strong>{{ pending }}</strong>
</h5>
</div>
</div>
</div>
</div>

</div>

<script src="https://unpkg.com/vue@next"></script>
<script
src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>

<script>
//create store vuex
const store = Vuex.createStore({

//state
state: {
todos: [{
title: 'Task 1',
completed: false
},
{
title: 'Task 2',
completed: true
},
{
title: 'Task 3',
completed: false
}
]
},

391
//mutations
mutations: {

//change todos
ADD_TODO(state, data) {
//push payload to todo
state.todos.push({
title: data,
completed: false
})
}

},

//actions
actions: {

//action addTodo
addTodo({ commit }, data) {
//commit to mutations "ADD_TODO"
commit('ADD_TODO', data)

},

//getters
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
},

})

392
//vue Create App
const app = Vue.createApp({

data() {
return {
todo: ''
}
},

computed: {

todos() {
//get todos from state
return this.$store.state.todos
},

done() {
//get todos completed from getters vuex
return this.$store.getters.doneTodos
},

pending() {
//get todos pending from getters vuex
return this.$store.getters.pendingTodos
}
},

methods: {

addTodo() {
//call action "addTodo" on vuex
this.$store.dispatch('addTodo', this.todo)

//empty todo state


this.todo = ''

}
},

})

//use vuex
app.use(store)
//mount to attribute ID app
app.mount('#app')
</script>

393
</body>
</html>

Dari penambahan kode di atas, pertama kita buat sebuah input, kurang lebih seperti berikut
ini :

<input type="text" class="form-control" v-model="todo"


@keyup.enter="addTodo" placeholder="masukkan todo list dan enter">

Dari input di atas terdapat directive v-model='todo', yang mana akan mengubah isi dari
nilai properti/state todo.

data() {
return {
//properti/state todo
todo: ''
}
}

Kemudian di dalam input kita buat sebuah event @keyup.enter="addTodo", yang artinya
jika di tekan tombol enter, maka akan menjalankan sebuah method yang bernama
AddTodo.

methods: {

addTodo() {
//call action "addTodo" on vuex
this.$store.dispatch('addTodo', this.todo)
//empty todo state
this.todo = ''
}
}

Di atas, di dalam method addTodo kita melakukan dispatch/memanggil sebuah actions di


dalam Vuex dengan nama addTodo dengan parameter data properti/state todo.

394
this.$store.dispatch('addTodo', this.todo) // <-- dispatch to "addTodo"
with value "this.todo"

Kemudian di dalam action addTodo di Vuex. Kurang lebih seperti ini :

//actions
actions: {

//action addTodo
addTodo({ commit }, data) {
//commit to mutations "ADD_TODO"
commit('ADD_TODO', data)
}

Di atas, di dalam actions addTodo kita melakukan commit ke dalam mutations ADD_TODO
dengan parameter data.

//change todos
ADD_TODO(state, data) {
//push payload to todo
state.todos.push({
title: data,
completed: false
})
}

Di dalam mutations inilah nilai state akan diperbarui, yaitu dengan melakukan push ke
dalam state todos dengan data yang di dapatkan melalui parameter.

Sekarang, jika kita coba jalankan dengan klik 2 kali file vuex-input-todo.html, maka
hasilnya kurang lebih seperti berikut ini :

395
Langkah 4 - Mengubah Status Todo Menggunakan Actions
dan Mutations
Sekarang, kita lanjutkan untuk membuat fungsi untuk mengubah status todo, yaitu dari
pending ke selesai atau sebaliknya. Silahkan buat file baru dengan nama vuex-status-
todo.html, kemudian masukkan kode berikut ini :

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-
scale=1.0">
<title>Belajar Vuex</title>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.mi
n.css">
<style>
.completed {
text-decoration: line-through;
}
</style>
</head>

<body style="background: lightgray;">

396
<div id="app">

<div class="container mt-5">


<div class="row">
<div class="col-md-12">
<div class="mb-3">
<input type="text" class="form-control" v-
model="todo" @keyup.enter="addTodo" placeholder="masukkan todo list dan
enter">
</div>
<ul class="list-group border-0 shadow">
<li v-for="(todo, index) in todos" :key="index"
class="list-group-item" @click="changeTodo(todo)">
<span :class="{completed:
todo.completed}">{{ todo.title }}</span>
</li>
</ul>
<div class="status mt-3">
<h5>DONE: <strong>{{ done }}</strong></h5>
<h5>PENDING: <strong>{{ pending }}</strong>
</h5>
</div>
</div>
</div>
</div>

</div>

<script src="https://unpkg.com/vue@next"></script>
<script
src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>

<script>
//create store vuex
const store = Vuex.createStore({

//state
state: {
todos: [{
title: 'Task 1',
completed: false
},
{
title: 'Task 2',
completed: true
},

397
{
title: 'Task 3',
completed: false
}
]
},

//mutations
mutations: {

//add todo
ADD_TODO(state, data) {
//push payload to todo
state.todos.push({
title: data,
completed: false
})
},

//status todo
STATUS_TODO(state, data) {
data.completed = !data.completed
}

},

//actions
actions: {

//action addTodo
addTodo({ commit }, data) {

//commit to mutations "ADD_TODO"


commit('ADD_TODO', data)

},

//action changeTodo
changeTodo({ commit }, data) {

//commit to mutations "STATUS_TODO"


commit('STATUS_TODO', data)

},

398
//getters
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
},

})

//vue Create App


const app = Vue.createApp({

data() {
return {
todo: ''
}
},

computed: {

todos() {
//get todos from state
return this.$store.state.todos
},

done() {
//get todos completed from getters vuex
return this.$store.getters.doneTodos
},

pending() {
//get todos pending from getters vuex
return this.$store.getters.pendingTodos
}

399
},

methods: {

addTodo() {
//call action "addTodo" on vuex
this.$store.dispatch('addTodo', this.todo)

//empty todo state


this.todo = ''

},

changeTodo(todo) {
//call action "changeTodo" on vuex
this.$store.dispatch('changeTodo', todo)

}
},

})

//use vuex
app.use(store)
//mount to attribute ID app
app.mount('#app')
</script>

</body>
</html>

Di atas, pertama kita buat CSS tambahan untuk membuat line jika todo sudah dalam status
complete.

<style>
.completed {
text-decoration: line-through;
}
</style>

Setelah itu, kita menambahkan beberapa kode di dalam perulangan data todos, yaitu
menambahkan event @click. Kurang lebih seperti berikut ini :

400
<li v-for="(todo, index) in todos" :key="index" class="list-group-item"
@click="changeTodo(todo)">
<span :class="{completed: todo.completed}">{{ todo.title }}</span>
</li>

Di atas, kita buat sebuah kondisi, jika ada todo dengan status completed, maka akan
menambahkan attribute class completed.

<span :class="{completed: todo.completed}">{{ todo.title }}</span>

Kemudian, untuk event @click="changeTodo(todo)" akan memanggil method di dalam


Vue dengan nama changeTodo.

changeTodo(todo) {
//call action "changeTodo" on vuex
this.$store.dispatch('changeTodo', todo)

Di atas, di dalam method changeTodo akan memanggil/dispatch sebuah action di dalam


Vuex dengan nama changeTodo dan disini juga menyertakan parameter data todo.

//action changeTodo
changeTodo({ commit }, data) {

//commit to mutations "STATUS_TODO"


commit('STATUS_TODO', data)

Di atas, action changeTodo akan melakukan commit ke dalam mutations dengan nama
STATUS_TODO, kurang lebih seperti berikut ini :

401
//status todo
STATUS_TODO(state, data) {
data.completed = !data.completed
}

Di atas, kita cek apakah todo tersebut statusnya false, jika iya, maka akan diubah ke
true, begitu juga sebaliknya. Intinya adalah perubahan state akan di lakukan di dalam
mutation.

Jika sekarang kita jalankan, maka kurang lebih hasilnya seperti berikut ini :

402
INSTALLASI & PERSIAPAN -
FRONTEND

403
Installasi dan Perispan Frontend

Pada tahap ini kita semua akan mempersiapnkan apa saja tool dan aplikasi yang akan
digunakan untuk mengembangkan halaman frontend dengan Vue Js. Jika sebelumnya kita
belajar membuat aplikasi Vue Js dengan CDN, maka kali ini kita akan membuatnya
menggunakan Vue CLI, yang bertujuan agar lebih mudah dalam manajemen project dan
install library pendukung. Jadi apa itu Vue CLI ?

Apa itu Vue CLI ?


Vue CLI is a Standard Tooling for Vue.js Development

Vue CLI merupakan standart tools yang digunakan untuk pengembangan aplikasi Vue Js.
Dengan Vue CLI pengembangan aplikasi menjadi lebih efisien, mudah di maintenance. Di
dalam Vue CLI terdiri dari 3 main tool, yaitu :

CLI - adalah paket npm yang terinstal secara global dan menyediakan perintah vue
di terminal kita, Ini digunakan untuk membuat scaffolding aplikasi baru melalui
perintah vue create. Atau langsung membuat prototipe ide baru melalui vue
serve. Kita juga dapat mengelola aplikasi Vue menggunakan userinterface melalui
vue ui.
CLI Service - adalah dependensi development dan Ini adalah paket npm yang
diinstal secara lokal ke dalam setiap aplikasi yang dibuat oleh @vue/cli.
CLI Plugins - adalah paket npm yang menyediakan fitur opsional untuk proyek Vue
CLI kita, seperti transpilasi Babel / TypeScript, integrasi ESLint, unit testing dan end-
to-end testing.

Pada pengembangan halaman frontend nanti, kita akan menggunakan Vue CLI sebagai
standart tool development agar project yang dibuat lebih mudah dikelola dan dikembangkan
kedepannya. Dan berikut ini beberapa tools dan aplikasi yang harus kita persiapkan
sebelum memulai mengembangkan halaman frontend dengan Vue Js.

Node Js dan NPM


Text Editor (Sublime Text, VS Code, Notepad ++)
Vue CLI
Browser
Vue Devtools

Installasi Node Js
Untuk installasi Node Js sangat mudah sekali, kita bisa mengunduh binary file dari situs
resminya atau bisa menggunakan command line. Untuk installasi silahkan bisa

404
membukanya di link berikut ini : https://nodejs.org/en/download/

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

Jika berhasil, maka akan muncul tampilan yang kurang lebih seperti berikut ini :

Text Editor
Untuk Text Editor saya rekomendasikan untuk menggunkan VS Code, untuk installasinya
silahkan bisa mengunjungi di situs resminya disini : https://code.visualstudio.com/ silahkan
diinstall sesuai sistem operasi yang digunakan.

Setelah berhasil menginstall VS Code sekarang saya akan memberikan beberapa saran
untuk plugin yang akan kita gunakan dalam pengembangan Vue Js. Berikut ini beberapa
plugin Vue Js yang perlu kita install di dalam VS Code.

vue - by jcbuisson

405
(https://marketplace.visualstudio.com/items?itemName=jcbuisson.vue)
vue 3 snippet - by hollowtree
(https://marketplace.visualstudio.com/items?itemName=hollowtree.vue-snippets)

Installasi Vue CLI


CATATAN! : Vue CLI 4.x membutuhkan Node.js versi 12 atau lebih tinggi (disarankan v14
+).

Untuk installasi Vue CLI secara global, silahkan teman-teman jalankan perintah di bawah ini
:

npm install -g @vue/cli

Setelah instalasi berhasil, maka kita bisa menjalankan perintah vue di dalam terimal/CMD.
kita dapat memeriksa apakah kita sudah memiliki versi yang benar dengan perintah ini
berikut ini :

vue --version

Untuk melakukan upgrade versi Vue CLI, kita bisa menggunakan perintah seperti berikut ini
:

npm update -g @vue/cli

Installasi Vue Devtools


Silahkan teman-teman install extension Vue Devtools di dalam browser, ini akan
digunakan untuk proses debugging aplikasi yang dibangun dengan Vue Js.

Vue Devtools - Vue 2


(https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhb
ledajbpd)
Vue Devtools - Vue 3 Beta
(https://chrome.google.com/webstore/detail/vuejs-devtools/ljjemllljcmogpfapbkkighbh
hppjdbg)

406
Silahkan diinstall kedua extension di atas, untuk versi Vue Devtools - Vue 3 Beta saat ini
belum bisa digunakan untuk proses debuggin Vuex.

407
Membuat Project Vue Js dengan Vue CLI

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat project baru Vue Js
menggunakan Vue CLI. Jika sebelumnya kita sudah berhasil menginstall Vue CLI, maka
untuk proses pembuatan project akan semakin mudah. Kita bisa menggunakan perintah vue
create <nama-project> di dalam terminal/CMD untuk membuat sebuah project Vue js
baru.

Langkah 1 - Membuat Project Vue Js dengan Vue CLI


Sekarang, silahkan masuk ke dalam folder dimana kita akan menyimpan project Vue Js. Dan
jalankan perintah berikut ini di dalam terminal/CMD :

vue create frontend-donasi

Jika perintah di atas dijalankan, maka akan mendapatkan sebuah pilihan untuk jenis preset
yang akan digunakan. Disini kita bisa memilih versi Vue Js yang ingin digunakan. Dan untuk
project yang akan kita buat, silahkan pilih Default (Vue 3 Preview) ([Vue 3]
Babel, eslint).

Silahkan tunggu proses installasi project Vue js sampai selesai, dan jangan lupa harus
terkoneksi dengan internet, karena semua paket akan diunduh secara online. Jika berhasil,
maka kita akan mendapatkan folder baru dengan nama frontend-donasi dan folder
tersebut merupakan folder project Vue Js kita.

408
Langkah 2 - Menjalankan Project Vue Js
Setelah proses installasi selesai, sekarang kita bisa mencoba menjalankan project Vue Js
kita. Silahkan jalankan perintah di bawah ini :

cd frontend-donasi

Perintah cd di atas, digunakan untuk masuk ke dalam folder frontend-donasi. Setelah


itu, silahkan jalankan perintah di bawah ini untuk menjalankan server.

npm run serve

Silahkan tunggu beberapa saat dan jika berhasil, maka project Vue Js kita akan di jalankan
di dalam localhost menggunakan port 8080. Jika port tersebut digunakan aplikasi lain, maka
Vue otomatis mencari port yang masih kosong.

Sekarang, kita bisa membukanya di http://localhost:8080, jika berhasil kurang lebih


tampilannya seperti berikut ini :

409
410
Installasi Library Pendukung

Setelah berhasil memebuat project Vue Js baru, sekarang kita akan lanjutkan untuk
menginstall beberapa library pendukung tambahan yang mana library-library ini akan kita
gunakan di dalam project. Beberapa library tersebut adalah :

Vue Router
Vuex
Axios
Vue Content Loader
VueperSlides
Vue Toastification

Langkah 1 - Installasi Vue Router


Vue Router merupakan library pendukung yang secara official dari Vue Js, yang mana
digunakan untuk membuat sebuah route di dalam aplikasi Vue dengan lebih cepat dan SPA
atau Single Page Application. Langsung saja kita install Vue Router di dalam project kita.
Jalankan perintah di bawah ini di dalam project Vue Js :

npm install vue-router@next

Silahkan tunggu proses installasi berjalan sampai selesai dan jangan lupa pastikan selalu
terhubung dengan internet.

Langkah 2 - Installasi Vuex


Vuex merupakan state manajemen pattern dan library untuk Vue Js yang digunakan untuk
membuat store secara centralized untuk semua component yang ada di dalam aplikasi.

Sekarang, kita akan melakukan installasi Vuex di dalam project Vue Js kita. SIlahkan
jalankan perintah di bawah ini di dalam terminal/CMD dan tentunya di dalam folder project
Vue Js.

npm install vuex@next --save

411
Langkah 3 - Installasi Axios
Axios adalah library JavaScript yang digunakan untuk melakukan HTTP Request ke dalam
suatu server untuk melakukan memanipulasi data seperti mendapatkan, mengirim,
mengupdate dan menghapus. Axios bersifat open source dan mudah untuk digunakan. Dan
secara default Axios menggunakan callback response berupa promise.

Untuk repository pengembangannya bisa dilihat disini : https://github.com/axios/axios,


Sekarang kita lanjutkan untuk menginstall Axios di dalam project kita, karena nanti kita
akan menggunakan Axios untuk mendapatkan data dari REST API yang sudah kita buat di
Laravel. Silahkan jalankan perintah di bawah ini di dalam project Vue Js :

npm install axios

Silahkan di tunggu proses installasinya sampai selesai dan pastikan harus terhubung dengan
internet.

Langkah 4 - Installasi Vue Content Loader


Vue Content Loader di gunakan untuk menampilkan loading block sebelum data berhasil
ditampilkan di dalam template, ini seperti halnya Facebook, Instagram, Linkedin dan
lain-lain. Contohnya seperti gambar di bawah ini :

412
Silahkan jalankan perintah di bawah ini untuk menambahkan Vue Content Loader di
dalam project Vue Js kita.

npm install vue-content-loader@2.0.0

413
Langkah 5 - Installasi VueperSlides
VueperSlides merupakan salah satu dari banyak library yang digunakan untuk menampilkan
gambar slider di halaman website, untuk menambahkan library ini silahkan jalankan
perintah berikut ini di dalam terminal/CMD dan di dalam project Vue Js kita.

npm install vueperslides@next

Langkah 6 - Installasi Vue Toastification


Terakhir, kita akan menambahkan library yang bernama Vue Toastification, library ini
digunakan untuk menampilkan alert atau toast message di dalam project Vue Js, contohnya
jika gagal melakukan login maka akan keluar sebuah alert yang memberikan informasi
bahwa terjadi login failed dan lain-lain.

Untuk menambahkan library ini di dalam project Vue, silahkan jalankan perintah berikut ini
di dalam terminal/CMD dan di dalam project Vue Js kita.

npm install --save vue-toastification@next

Setelah Vue Toastification berhasil terinstall, sekarang kita akan lakukan sedikit konfigurasi
agar CSS dari library ini dapat digunakan secara global di dalam project. Silahkan buka file
src/main.js kemudian ubah kode-nya menjadi seperti berikut ini :

414
import { createApp } from 'vue'
import App from './App.vue'

/**
* import Toastr
*/
import Toast from "vue-toastification";
import "vue-toastification/dist/index.css";

//create App Vue


const app = createApp(App)

//gunakan "Toast" di Vue Js dengan plugin "use"


app.use(Toast)

app.mount('#app')

Di atas, kita import Toast library dan CSS-nya.

/**
* import Toastr
*/
import Toast from "vue-toastification";
import "vue-toastification/dist/index.css";

Kemudian, kita buat sebuah variable/state dengan nama app, dimana isinya adalah
mendefinisikan Vue Instance. Dan agar library Toast dapat digunakan secara global, kita
masukkan ke dalam Vue Instance dengan plugin use.

//gunakan "Toast" di Vue Js dengan plugin "use"


app.use(Toast)

415
Installasi dan Konfigurasi Tailwind CSS di Vue Js

Pada tahap kali ini kita semua akan belajar bagaimana cara menginstall dan konfigurasi
Tailwind CSS di dalam project Vue Js.

Langkah 1 - Installasi Tailwind CSS


Sekarang, kita akan belajar bagaimana cara mengintegrasikan project Vue Js dengan
Tailwind CSS, silahkan jalankan perintah di bawah ini di dalam project Vue Js:

npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat


@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9

Langkah 2 - Konfigurasi Tailwind CSS di Vue Js


Setelah proses installasi selesai, sekarang kita lanjutkan untuk melakukan konfigurasi
Tailwind CSS di dalam project Vue Js, silahkan jalankan perintah berikut ini :

npx tailwindcss init -p

Perintah di atas digunakan untuk melakukan generate file konfigurasi dari Tailwind, yaitu :
tailwind.config.js dan postcss.config.js.

Sekarang, kita lanjutkan agar Tailwind CSS dapat digunakan secara global di dalam project
Vue Js, silahkan buat file baru dengan nama index.css di dalam folder src. Kemudian
masukkan kode berikut ini :

/*! @import */
@tailwind base;
@tailwind components;
@tailwind utilities;

416
Setelah itu, kita import file tersebut di dalam file src/main.js, kurang lebih seperti berikut
ini :

import { createApp } from 'vue'


import App from './App.vue'

/**
* import Toastr
*/
import Toast from "vue-toastification";
import "vue-toastification/dist/index.css";

/**
* Tailwind CSS
*/
import './index.css'

//create App Vue


const app = createApp(App)

//gunakan "Toast" di Vue Js dengan plugin "use"


app.use(Toast)

app.mount('#app')

Di atas, kita melakukan import file CSS yang bernama index.css. Dimana di dalam file

417
tersebut berisi CSS dari Tailwind CSS.

/**
* Tailwind CSS
*/
import './index.css'

Sekarang, project Vue Js kita sudah terintegrasi dengan Tailwind CSS, kita akan uji coba
nanti ketika membuat component Header dan juga Footer.

418
Membuat Helpers Menggunakan Mixins

Saat mengembangkan aplikasi yang memiliki sekala besar dengan Vue Js, maka tidak heran
jika banyak kode-kode yang sama yang mungkin bisa digunakan untuk berulang-ulang
(reusable). Tidak mungkin kita melakukan copy dan paste kode yang memiliki fungsionalitas
yang sama di beberapa component. Dengan problem seperti itu, kita bisa menerapkan
Mixins di dalam project Vue Js.

Apa itu Mixins ?


Mixins merupakan sebuah function yang bisa digunakan secara berulang (reusable) untuk
component Vue. Bisa dikatakan Mixins ini seperti helpers. Jika dikutip dari situs resminya,
kurang lebih seperti berikut ini :

Mixins distribute reusable functionalities for Vue components. A mixin object can contain
any component options. When a component uses a mixin, all options in the mixin will be
"mixed" into the component's own options.

Sekarang kita akan membuat sebuah Mixins untuk beberapa fungsi, seperti merubah
format angka, menghitung persentase, menghitung tanggal dan menghitung jumlah hari.

Langkah 1 - Membuat Mixins


Sekarang kita lanjutkan untuk membuat function untuk helpers, silahkan buat folder baru
dengan nama mixins di dalam folder src. Dan di dalam folder mixins silahkan buat file
baru dengan nama index.js kemudian masukkan kode berikut ini :

const mixins = {

methods: {

/**
*
* @param {*} value
*/
formatPrice(value) {
let val = (value/1).toFixed(0).replace('.', ',')
return val.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".")
},

/**

419
*
* @param {*} partialValue
* @param {*} totalValue
*/
percentage(partialValue, totalValue) {
return (100 * partialValue) / totalValue;
},

/**
*
* @param {*} maxDate
*/
maxDate(maxDate) {
let date = new Date()
let now = date.getFullYear() + '-' + ('0' +
(date.getMonth()+1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2)

//if same date


if(now == maxDate) {
return true
}

//if now > max date


if(now > maxDate) {
return true
}
return false
},

/**
*
* @param {*} maxDate
*/
countDay(maxDate) {

let date = new Date()


let now = date.getFullYear() + '-' + ('0' +
(date.getMonth()+1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2)

let dt1 = new Date(now);


let dt2 = new Date(maxDate);

let result = Math.floor((Date.UTC(dt2.getFullYear(),


dt2.getMonth(), dt2.getDate()) -
Date.UTC(dt1.getFullYear(), dt1.getMonth(), dt1.getDate()) )
/(1000 * 60 * 60 * 24));

420
if(result < 0) {
return 0
}

return result

}
},
}

export default mixins

function formatPrice

Pertama, kita membuat function yang bernama formatPrice di dalam di dalamnya


terdapat sebuah parameter, parameter ini nanti akan berupa angka yang akan di format.
Contohnya seperti berikut ini :

421
//define variable
let angka = 10000

//format variable
let result = formatPrice(angka)

//tampilkan hasil
console.log(result) // <-- hasilnya akan menjadi 10.000

function percentage

Kedua, kita membuat function untuk menghitung persentasi dari sebuah nilai, disini
nantinya kita akan gunakan untuk menghitung jumlah donasi dari total donasi yang sudah
terkumpul. Kurang lebih rumusnya seperti berikut ini :

(100 * jumlah donasi) / target donasi

Yaitu, 100 dikali jumlah donasi yang masuk, kemudian di bagi dengan target donasi yang
sudah ditentukan, kita bisa menggunakan kode seperti berikut ini :

percentage(partialValue, totalValue) {
return (100 * partialValue) / totalValue;
}

Di atas, kita menambahkan 2 parameter, yang pertama adalah total donasi yang terkumpul
yaitu partialValue dan yang kedua adalah total target donasi yang ditentukan yaitu
totalValue. Dan di dalamnya kita melakukan perkalian antara nilai 100 dengan total
donasi masuk, kemudian dibagikan dengan total dari target donasi.

function maxDate

Ketiga, kita membuat function untuk menentukan, apakah tanggal sekarang melebihi dari
tanggal yang ditentukan atau tanggal sekarang sama dengan tanggal yang sudah
ditentukan. Function ini akan kita gunakan untuk mengaktifkan tombol donasi yang ada di
dalam website.

Jika tanggal sekarang melebihi dari tanggal yang ditentukan, atau tanggal sekarang sama
dengan tanggal yang ditentukan, maka tombol untuk donasi akan di nonaktifkan. Kurang
lebih kita menggunakan function seperti berikut ini :

422
maxDate(maxDate) {
let date = new Date()
let now = date.getFullYear() + '-' + ('0' +
(date.getMonth()+1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2)

//if same date


if(now == maxDate) {
return true
}

//if now > max date


if(now > maxDate) {
return true
}
return false
}

function countDay

Keempat, kita membuat sebuah function untuk menghitung jumlah hari, contohnya jika kita
menentukan tanggal berakhir adalah 10-03-2021 dan tanggal sekarang adalah
03-03-2021, maka akan menghasilkan 7 hari. Konsepnya yaitu menghitung antara tanggal
sekarang dengan tanggal yang sudah ditentukan.

Kurang lebih kita bisa menggunakan kode seperti berikut ini :

423
countDay(maxDate) {

let date = new Date()


let now = date.getFullYear() + '-' + ('0' +
(date.getMonth()+1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2)

let dt1 = new Date(now);


let dt2 = new Date(maxDate);

let result = Math.floor((Date.UTC(dt2.getFullYear(), dt2.getMonth(),


dt2.getDate()) -
Date.UTC(dt1.getFullYear(), dt1.getMonth(), dt1.getDate()) )
/(1000 * 60 * 60 * 24));

if(result < 0) {
return 0
}

return result

Langkah 2 - Register Global Mixins


Setelah berhasil membuat beberapa function helpers dengan Mixins, sekarang kita harus
membuat bagaimana agar function-function tersebut dapat digunakan secara global di
dalam semua component Vue.

Sekarang, silahkan buka file src/main.js kemudian ubah kode-nya menjadi seperti
berikut ini :

424
import { createApp } from 'vue'
import App from './App.vue'

/**
* import Toastr
*/
import Toast from "vue-toastification";
import "vue-toastification/dist/index.css";

/**
* Tailwind CSS
*/
import './index.css'

/**
* Mixins
*/
import mixins from './mixins'

//create App Vue


const app = createApp(App)

//gunakan "Toast" di Vue Js dengan plugin "use"


app.use(Toast)

//gunakan "Mixins" di Vue Js dengan plugin "use"


app.mixin(mixins)

app.mount('#app')

Di atas, pertama kita import file Mixins yang sudah kita buat sebelumnya.

/**
* Mixins
*/
import mixins from './mixins'

Kemudian, kita gunakan plugin .mixin agar Mixins di masukkan di dalam Instance dari
Vue.

425
//gunakan "Mixins" di Vue Js dengan plugin "use"
app.mixin(mixins)

Maka sekarang Mixins yang sudah kita buat bisa digunakan secara global di dalam
component Vue.

426
Konfigurasi Midtrans di Vue Js

Pada tahap kali ini kita semua akan belajar bagaimana cara melakukan konfigurasi Midtrans
di project Vue Js. Disini kita hanya butuh melakukan pemanggilan kode JavaScript yang
sudah disediakan oleh Miditrans.

Silahkan buka file public/index.html kemudian ubah kode-nya menjadi seperti berikut
ini :

<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-
scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>DONASI ONLINE - SANTRIKODING</title>
<link
href="https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;50
0;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all
.min.css">
<style>
body {
font-family: 'Quicksand', sans-serif!important;
}
</style>
</head>
<body class="bg-gray-300">
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="https://app.sandbox.midtrans.com/snap/snap.js" data-
client-key="isi_dengan_client_key_midtrans"></script>
</body>
</html>

Di atas, pertama kita menambahkan 2 CSS baru dari CDN, yaitu Google Font dan Font
Awesome. Google Font akan kita gunakan untuk merubah font yang akan kita gunakan di
dalam website. Kemudian untuk Font Awesome, akan kita gunakan untuk menampilkan

427
icon-icon untuk website.

<link
href="https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;50
0;600;700&display=swap" rel="stylesheet">

<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all
.min.css">

Kemudian kita memanggil SNAP JS yang disediakan oleh Midtrans, ini bertujuan agar kita
dapat menampilkan halaman popup pembayaran dari Midtrans di halaman project Vue.

<script src="https://app.sandbox.midtrans.com/snap/snap.js" data-client-


key="isi_dengan_client_key_midtrans"></script>

Di atas, silahkan ganti isi dari attribute data-client-key dengan client key yang kita
dapatkan dari Midtrans. Kurang lebih untuk contoh tampilan popup pembayaran dari
Midtrans seperti berikut ini :

428
Membuat Component Header dan Footer

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat component untuk
menampilkan Header dan juga Footer. Kedua component ini akan digunakan berulang-
ulang di semua halaman pada website. Sebelum itu, silahkan unduh file assets berupa
gambar untuk kebutuhan project kita.

DOWNLOAD ASSETS :
https://drive.google.com/file/d/1hm4PfCIJlQL77_qtkWq0hrqkhTGHFTAB/view?usp=sharing

Setelah berhasil terunduh, silahkan extract dan masukkan di dalam folder src/assets dan
kurang lebih akan menjadi seperti berikut ini :

Langkah 1 - Membuat Component Header


Silahkan buat file baru dengan nama Header.vue di dalam folder src/components
kemudian, masukkan kode berikut ini :

429
<template>
<div>
<!-- header -->
<header>
<div class="bg-gray-700 text-white text-center fixed inset-
x-0 top-0 z-10">
<div class="container mx-auto grid grid-cols-10 p-3
sm:w-full md:w-5/12">
<div class="col-span-2 bg-white rounded-full h-10 w-10
p-1 mr-3 shadow-sm">
<a href="">
<img src="@/assets/images/muslim.png" class="inline-
block">
</a>
</div>
<div class="col-span-8">
<input type="text" class="appearance-none w-full bg-
gray-500 rounded-full h-7 shadow-md placeholder-white focus:outline-none
focus:placeholder-gray-600 focus:bg-white focus-within:text-gray-600
p-5"
placeholder="Cari yang ingin kamu bantu">
</div>
</div>
</div>
</header>
</div>
</template>

<script>
export default {

}
</script>

<style>

</style>

Langkah 2 - Membuat Component Footer


Di dalam component Footer kita akan load/memanggil beberapa image untuk
menampilkan menu. Sekarang, sIlahkan buat file baru di dalam folder src/components
dengan nama Footer.vue, kemudian masukkan kode berikut ini :

430
<template>
<div>

<div class="bg-white text-center fixed inset-x-0 bottom-0 z-10">


<div class="container mx-auto grid grid-cols-4 gap-4 p-2
sm:w-full md:w-5/12">

<div>
<a href="#"
class="w-full focus:text-teal-500 hover:text-
teal-500 justify-center inline-block text-center">
<img class="inline-block mb-1" width="25"
height="25"
src="@/assets/images/home.png">
<span class="block text-xs">Beranda</span>
</a>
</div>

<div>
<a href="#"
class="w-full focus:text-teal-500 hover:text-
teal-500 justify-center inline-block text-center">
<img width="25" height="25" class="inline-block
mb-1"
src="@/assets/images/heart.png">
<span class="block text-xs">Donasi Saya</span>
</a>
</div>

<div>
<a href="#"
class="w-full focus:text-teal-500 hover:text-
teal-500 justify-center inline-block text-center">
<img width="25" height="25" class="inline-block
mb-1"
src="@/assets/images/flag.png">
<span class="tab tab-kategori block text-
xs">Campaign</span>
</a>
</div>

<div>
<a href="#"
class="w-full focus:text-teal-500 hover:text-
teal-500 justify-center inline-block text-center">

431
<img width="25" height="25" class="inline-block
mb-1"
src="@/assets/images/user.png">
<span class="block text-xs">Akun</span>
</a>
</div>

</div>
</div>

</div>
</template>

<script>
export default {

}
</script>

<style>

</style>

Di atas, kita menambahkan 4 menu yang akan kita gunakan untuk navigasi di halaman
website, yaitu Beranda, Donasi Saya, Campaign dan Akun.

Di atas, kita coba memanggil file assets gambar yang sudah kita masukkan sebelumnya,
kurang lebih seperti berikut ini :

@/assets/images/home.png

@ merupakan inisialisasi dari folder src, jadi sama saja kita memanggil file gambar di dalam
folder src/assets/images/home.png, kurang lebih seperti itu penjelasannya.

Langkah 3 - Import Component Header dan Footer di File


App
File App.vue merupakan component yang akan dipanggil pertama kali saat project Vue kita
di jalankan, dan file ini merupakan induk dari template di dalam project Vue Js.

Oleh sebab itu, kita akan melakukan import component Header dan Footer di dalam file

432
App.vue, karena component Header dan Footer akan terus digunakan di semua template
di dalam aplikasi.

Silahkan buka file src/App.vue dan ubah kode-nya menjadi seperti berikut ini :

<template>
<div class="h-screen">
<!-- Header -->
<Header />

<!-- content -->


<router-view></router-view>

<!-- footer -->


<Footer />

</div>
</template>

<script>

import Header from '@/components/Header.vue'


import Footer from '@/components/Footer.vue'

export default {
components: {
Header,
Footer
}
}
</script>

<style>

</style>

Di atas, pertama kita import component Header dan Footer.

import Header from '@/components/Header.vue'


import Footer from '@/components/Footer.vue'

433
Setelah kita import, kemudian kita register di dalam properti components.

components: {
Header,
Footer
}

Dan untuk menampilkan di dalam template, kita menggunakan kode seperti berikut ini :

<div class="h-screen">
<!-- Header -->
<Header />

<!-- content -->


<router-view></router-view>

<!-- footer -->


<Footer />

</div>

Dan untuk kode <router-view /> akan digunakan untuk render vue router yang berisi
halaman website. Sekarang, silahkan refresh project Vue Js kita, maka kurang lebih hasilnya
seperti berikut ini :

434
Dari hasil di atas, kita sudah bisa melihat component Header dan Footer telah berhasil di
tampilkan. Dimana untuk component Header berisi logo dan form pencarian dan untuk
component Footer, berisi menu navigasi untuk website.

435
HALAMAN DONATUR

436
Konfigurasi Global API Endpoint

Pada tahap kali ini, kita semua akan belajar bagaimana cara membuat global API endpoint,
dimana ini merupakan alamat URL dari backend Laravel yang akan kita gunakan. Jika kita
tidak menggunakan global API seperti ini, maka jika ada perubahan URL endpoint, semua
akan dirubah satu-persatu dan hal itu kurang efisien. Dengan menerapkan konsep seperti
ini, maka jika ada perubahan URL dari endpoint, maka semua akan otomatis berubah.

Silahkan buat folder baru dengan nama api di dalam folder src, kemudian di dalam folder
api silahkan buat file baru dengan nama Api.js dan masukkan kode berikut ini :

//import axios
import axios from 'axios'

const Api = axios.create({


//set default endpoint API
baseURL: 'http://localhost:8000/api'
})

export default Api

Di atas, pertama kita import axios terlebih dahulu, kemudian kita buat object create dari
axios dengan isi alamat baseURL dari project Laravel/backend kita.

437
Konfigurasi Router untuk Authentication

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat routing untuk
menampilkan halaman register dan login. Untuk mendefinisikan sebuah route, maka
kita akan menggunakan library yang bernama Vue Router, dimana library ini sudah kita
install sebelumnya.

Langkah 1 - Membuat View Register dan Login


Sekarang, kita akan membuat sebuah view terlebih dahulu untuk menampilkan halaman
register dan juga login, tapi halaman ini tidak akan bisa digunakan jika kita tidak
membuatkan sebuah route.

Silahkan buat folder baru dengan nama views di dalam folder src. Dan di dalam folder
views silahkan buat folder lagi dengan nama auth, kemudian di dalam folder uath
silahkan buat file baru yaitu :

Register.vue
Login.vue

Kurang lebih struktur folder dan file-nya seperti berikut ini :

Kemudian, masukkan kode berikut ini di dalam file Register.vue.

438
<template>
<div class="pb-20 pt-20 text-center">
HALAMAN REGISTER
</div>
</template>

<script>
export default {

}
</script>

Dan masukkan kode berikut ini di dalam file Login.vue

<template>
<div class="pb-20 pt-20 text-center">
HALAMAN LOGIN
</div>
</template>

<script>
export default {

}
</script>

Langkah 2 - Membuat Route untuk Register dan Login


Setelah berhasil membuat sample untuk halaman register dan login, sekarang kita
lanjutkan untuk membuat route agar halaman register dan login dapat diakses melalui
browser.

Silahkan buat folder baru dengan nama router di dalam folder src, kemudian di dalam
folder router silahkan buat file baru dengan nama index.js. Kurang lebih seperti berikut
ini :

439
Setelah itu, silahkan masukkan kode berikut ini :

440
//import vue router
import { createRouter, createWebHistory } from 'vue-router'

//define a routes
const routes = [
{
path: '/register',
name: 'register',
component: () => import( /* webpackChunkName: "register" */
'@/views/auth/Register.vue')
},
{
path: '/login',
name: 'login',
component: () => import( /* webpackChunkName: "login" */
'@/views/auth/Login.vue')
}
]

//create router
const router = createRouter({
history: createWebHistory(),
routes // <-- routes,
})

export default router

Di atas, pertama kita import createRouter dan createWebHistory dari vue-router.

import { createRouter, createWebHistory } from 'vue-router'

createRouter akan digunakan untuk membuat konfigurasi sebuah router di dalam Vue
Router. Kemudian untuk createWebHistory digunakan untuk membuat URL dari Vue
Router menjadi lebih friendly.

Kemudian, di atas kita buat variable routes dengan type array dan di dalamnya kita define
2 route, yaitu untuk halaman register dan login.

441
{
path: '/register',
name: 'register',
component: () => import( /* webpackChunkName: "register" */
'@/views/auth/Register.vue')
},
{
path: '/login',
name: 'login',
component: () => import( /* webpackChunkName: "login" */
'@/views/auth/Login.vue')
}

Dari route di atas, kurang lebih penjelasannya seperti berikut ini :

path - merupakan URL yang akan dihasilkan, di atas kita set dengan /register dan
/login, jadi jika ada yang mengakses URL tersebut di browser, maka route inilah
yang akan di gunakan.
name - merupakan nama dari route itu sendiri, ini akan mempermudah kita dalam
pemanggilan route di dalam component.
component - merupakan views/component yang akan digunakan di dalam route, di
atas misalnya untuk register, kita arahkan ke dalam folder
@/views/auth/Register.vue.

Setelah itu, kita buat sebuah variable router dengan konfigurasi dari route di atas.

//create router
const router = createRouter({
history: createWebHistory(),
routes // <-- routes
})

history merupakan mode yang di gunakan di dalam Vue Router, disini kita menggunakan
createWebHistory yang artinya URL yang dihasilkan akan menjadi lebih friendly. Kita
juga bisa mengubahnya menjadi createWebHashHistory, jika kita menggunakan tipe ini,
maka URL di dalam project Vue Js akan di tambahkan # di depannya. Contohnya seperti
berikut ini :

Contoh Dengan createWebHistory.

http://domain.com/register
http://domain.com/login

442
Contoh Dengan createWebHashHistory.

http://domain.com/#/register
http://domain.com/#/login

Kurang lebih perbedaanya seperti itu, antara penggunaan jenis history mode pada Vue
Router.

Langkah 3 - Import Router di Main.js


Agar konfigurasi router kita dapat digunakan di global aplikasi, maka kita harus melakukan
register terlebih dahulu di dalam file main.js. Sekarang, silahkan buka file src/main.js
dan ubah kode-nya menjadi seperti berikut ini :

443
import { createApp } from 'vue'
import App from './App.vue'

/**
* import Toastr
*/
import Toast from "vue-toastification";
import "vue-toastification/dist/index.css";

/**
* Tailwind CSS
*/
import './index.css'

/**
* Mixins
*/
import mixins from './mixins'

/**
* Vue Router
*/
import router from './router'

//create App Vue


const app = createApp(App)

//gunakan "Toast" di Vue Js dengan plugin "use"


app.use(Toast)

//gunakan "Mixins" di Vue Js dengan plugin "use"


app.mixin(mixins)

//gunakan "router" di Vue Js dengan plugin "use"


app.use(router)

app.mount('#app')

Di atas, pertama kita import konfigurasi router yang sebelumnya udah kita buat.

444
/**
* Vue Router
*/
import router from './router'

Kemudian, agar dapat digunakan secara global, kita masukkan ke dalam Instance Vue
dengan plugin use.

//gunakan "router" di Vue Js dengan plugin "use"


app.use(router)

Sekarang, kita akan mengaktifkan route di menu component Footer, dimana agar kita
dapat klik link langsung melalui menu navigasi. Silahkan buka file
src/components/Footer.vue, kemudian cari kode berikut ini :

<div>
<a href="#"
class="w-full focus:text-teal-500 hover:text-teal-500 justify-
center inline-block text-center">
<img width="25" height="25" class="inline-block mb-1"
src="@/assets/images/user.png">
<span class="block text-xs">Akun</span>
</a>
</div>

Di atas, bisa kita lihat untuk link menuju menu navigasi masih menggunakan href="#".
Dan sekarang kita ubah menjadi seperti berikut ini :

445
<div>
<router-link :to="{name: 'login'}"
class="w-full focus:text-teal-500 hover:text-teal-500 justify-
center inline-block text-center">
<img width="25" height="25" class="inline-block mb-1"
src="@/assets/images/user.png">
<span class="block text-xs">Akun</span>
</router-link>
</div>

Di atas, kita ubah yang semual pakai a href menjadi router-link, dan kita panggil
nama route-nya, yaitu login.

Sekarang, kita coba untuk mengakses route register dan login, silahkan buka link
berikut ini di browser http://localhost:8080/register jika berhasil maka kurang lebih seperti
berikut ini :

Dan untuk halaman login, silahkan buka link berikut ini http://localhost:8080/login dan jika
berhasil kurang lebih seperti berikut ini :

446
447
Konfigurasi Module Auth Vuex

Pada tahap kali ini kita semua akan belajar tentang module di Vuex, dengan menerapkan
konsep module maka kode-kode untuk state management tidak akan tercampur dengan
kode yang lain. Dan kali ini kita semua akan belajar membuat module auth untuk proses
otentikasi, seperti register, login dan logout.

Langkah 1 - Membuat Induk Store Vuex


Pertama, kita akan mendefinisikan store Vuex terlebih dahulu, file ini nanti akan kita
jadikan sebagai induk dari module-module yang terpisah. SIlahkan buat folder baru dengan
nama store di dalam folder src. Kemudian, di dalam folder store silahkan buat file baru
dengan nama index.js dan masukkan kode berikut ini :

//import vuex
import { createStore } from 'vuex'

//create store vuex


export default createStore({

modules: {
}

})

448
Di atas, pertama kita import createStore dari vuex.

//import vuex
import { createStore } from 'vuex'

Kemudian, kita buat sebuah variable store dengan createStore, yang mana di dalamnya
kita hanya memiliki module yang masih kosong. Di dalam module inilah file-file store akan
di masukkan.

modules: {
//...
}

Langkah 2 - Membuat Module Auth Vuex


Setelah berhasil membuat induk store untuk menyimpan module-module nanti, maka
sekarang kita akan coba belajar bagaimana cara membuat sebuah module baru. Silahkan
buat folder baru dengan nama module di dalam folder src/store dan di dalam folder
module tersebut silahkan buat file baru dengan nama auth.js dan masukkan kode seperti
berikut ini :

449
const auth = {

//set namespace true


namespaced: true,

//state
state: {
},

//mutations
mutations: {

},

//actions
actions: {

},

//getters
getters: {

export default auth

450
Di dalam module uath di atas, kita menambahkan beberapa store, yaitu state,
mutations, actions dan getters. Dan untuk namespace digunakan karena kita
menggunakan teknik module, ini bertujuan agar module kita dapat diakses langsung melalui
component.

Langkah 3 - Import Module Auth di Store Vuex


Sekarang, kita akan import dan register module auth di atas di dalam store induk, silahkan
buka file src/store/index.js kemudian ubah kode-nya menjadi seperti berikut ini :

//import vuex
import { createStore } from 'vuex'

//import module auth


import auth from './module/auth'

//create store vuex


export default createStore({

modules: {
auth, // <-- module auth
}

})

451
Di atas, pertama kita import module auth dari folder module.

//import module auth


import auth from './module/auth'

Kemudian, kita register di dalam store modules.

modules: {
auth, // <-- module auth
}

Langkah 4 - Import Induk Store Vuex di Main.js


Agar Vuex dapat digunakan secara global di dalam aplikasi, maka kita harus melakukan
register di dalam file main.js. Silahkan buka file src/main.js kemudian ubah kode-nya
menjadi seperti berikut ini :

import { createApp } from 'vue'


import App from './App.vue'

/**
* import Toastr
*/
import Toast from "vue-toastification";
import "vue-toastification/dist/index.css";

/**
* Tailwind CSS
*/
import './index.css'

/**
* Mixins
*/
import mixins from './mixins'

/**
* Vue Router
*/
import router from './router'

452
/**
* Vuex
*/
import store from './store'

//create App Vue


const app = createApp(App)

//gunakan "Toast" di Vue Js dengan plugin "use"


app.use(Toast)

//gunakan "Mixins" di Vue Js dengan plugin "use"


app.mixin(mixins)

//gunakan "router" di Vue Js dengan plugin "use"


app.use(router)

//gunakan "store" di Vue Js dengan plugin "use"


app.use(store)

app.mount('#app')

Di atas, pertama kita import file store Vuex.

/**
* Vuex
*/
import store from './store'

Kemudian, kita gunakan plugin use agar Vuex dapat digunakan secara global di dalam
aplikasi Vue.

//gunakan "store" di Vue Js dengan plugin "use"


app.use(store)

453
Membuat Proses Register Donatur

Pada tahap kali ini kita akan belajar bagaimana cara membuat proses register
menggunakan Vue Js, dimana kita akan menggunakan Rest API yang sebelumnya sudah
kita buat di Laravel. Di dalam Vue Js nanti kita akan menggunakan Composition API agar
kode yang ditulis bisa lebih terstruktur dan kita akan kombinasikan juga menggunakan Vuex
sebagai state management agar data menjadi centralize atau terpusat dan juga bersifat
reaktif.

Langkah 1 - View/Component Register


Sekarang kiita akan melakukan perubahan di dalam view/component register. Silahkan buka
file src/views/auth/Register.vue dan ubah kode-nya menjadi seperti berikut ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-5 sm:w-full
md:w-5/12">

<form @submit.prevent="register">
<div class="bg-white rounded-md shadow-md p-5">
<div class="text-xl">
REGISTER AKUN
</div>
<div class="border-2 border-gray-200 mt-3
mb-2"></div>

<div class="mb-2">
<label class="mt-2">Nama Lengkap</label>
<input type="text" v-model="user.name"
class="mt-2 appearance-none w-full bg-
gray-200 border border-gray-200 rounded h-7 shadow-sm placeholder-
gray-600 focus:outline-none focus:placeholder-gray-600 focus:bg-white
focus-within:text-gray-600 p-5"
placeholder="Nama Lengkap">
</div>

<div class="mb-2">
<label class="mt-2">Alamat Email</label>
<input type="email" v-model="user.email"
class="mt-2 appearance-none w-full bg-

454
gray-200 border border-gray-200 rounded h-7 shadow-sm placeholder-
gray-600 focus:outline-none focus:placeholder-gray-600 focus:bg-white
focus-within:text-gray-600 p-5"
placeholder="Alamat Email">
</div>

<div class="cols-span-1 mb-5">


<label class="mt-2">Password</label>
<input type="password" v-model="user.password"
class="mt-2 appearance-none w-full bg-
gray-200 border border-gray-200 rounded h-7 shadow-sm placeholder-
gray-600 focus:outline-none focus:placeholder-gray-600 focus:bg-white
focus-within:text-gray-600 p-5"
placeholder="Password">
</div>

<div class="cols-span-1 mb-5">


<label class="mt-2">Konfirmasi Password</label>
<input type="password" v-
model="user.password_confirmation"
class="mt-2 appearance-none w-full bg-
gray-200 border border-gray-200 rounded h-7 shadow-sm placeholder-
gray-600 focus:outline-none focus:placeholder-gray-600 focus:bg-white
focus-within:text-gray-600 p-5"
placeholder="Konfirmasi Password">
</div>

<div>
<button
class="bg-gray-700 py-1 px-3 text-white
rounded-md shadow-md text-xl inline-block w-full focus:outline-none
focus:bg-gray-900">DAFTAR</button>
</div>

</div>
</form>

<div class="text-center mt-5">


Sudah punya akun ? <router-link :to="{name: 'login'}"
class="underline text-blue-600">Masuk Disini
!</router-link>
</div>

</div>
</div>
</template>

455
<script>

//hook vue
import { ref, reactive } from 'vue'
//hook vuex
import { useStore } from 'vuex'
//hook vue router
import { useRouter } from 'vue-router'
//hook Toast
import { useToast } from "vue-toastification"

export default {

setup() {

//user state
const user = reactive({
name: '',
email: '',
password: '',
password_confirmation: ''
})

//validation state
const validation = ref([])

//store vuex
const store = useStore()

//route
const router = useRouter()

//same interface as this.$toast


const toast = useToast()

//function register, fungsi ini di jalankan ketika form di


submit
function register() {

//define variable
let name = user.name
let email = user.email
let password = user.password
let password_confirmation = user.password_confirmation

//panggil actions "register" dari module "auth"

456
store.dispatch('auth/register', {
name,
email,
password,
password_confirmation
})
.then(() => {

//redirect ke dashboard
router.push({name: 'dashboard'})

toast.success("Register Berhasil!")

}).catch(error => {
//show validaation message
validation.value = error

//show validation name with toast


if(validation.value.name) {
toast.error(`${validation.value.name[0]}`)
}

//show validation email with toast


if(validation.value.email) {
toast.error(`${validation.value.email[0]}`)
}

//show validation password with toast


if(validation.value.password) {
toast.error(`${validation.value.password[0]}`)
}
})
}

//return a state and function


return {
user, // <-- state user
validation, // <-- state validation
register, // <-- method register
toast // <-- hook toast
}

}
</script>

457
<style>

</style>

Dari penambahan kode di atas, pertama kita melakukan import Reactivity API yaitu ref
dan reactive, kedua Reactivy API ini akan kita gunakan untuk menjadikan sebuah
variable/state menjadi reaktif/realtime.

//hook vue
import { ref, reactive } from 'vue'

Selanjutnya, karena kita akan menggunakan Vuex di dalam Composition API, maka kita
harus menggunakan hook dari Vuex yang bernama useStore, dengan menggunakan hook
ini, maka kita dapat menggunakan Vuex dengan lebih mudah di dalam Composition API.

//hook vuex
import { useStore } from 'vuex'

Kita juga akan menggunakan Vue Router di dalam Composition API, maka oleh sebab itu
kita harus menggunakan hook yang bernama useRouter agar Vue Router dapat di
gunakan di dalam Composition API.

//hook vue router


import { useRouter } from 'vue-router'

Terakhir, kita juga import library pendukung yaitu Toastification, karena kita akan
menggunakan Composition API, maka kita juga akan menggunakan sebuah hook dari
Toastification di dalam Composition API.

//hook Toast
import { useToast } from "vue-toastification"

Dan untuk mendefinisikan component sebagai Composition API, kita bisa menggunakan
function yang bernama setup.

458
setup() {

//...

Di dalam Composition API, pertama kita membuat sebuah state yang bernama user,
dimana state ini menggunakan Reactivity API dengan jenis reactive. Dan di dalamnya
kita membuat beberapa object.

//user state
const user = reactive({
name: '',
email: '',
password: '',
password_confirmation: ''
})

State di atas akan kita gunakan untuk menerima data yang dikirim melalui form.

Selanjutnya, kita buat state lagi dengan nama validation, dimana state ini akan
menggunakan Reactivity API dengan jenis ref dan isinya nanti akan kita assign dengan
sebuah response validasi error dari Laravel.

//validation state
const validation = ref([])

CATATAN:
Jika menggunakan Reactivity API ref di dalam function setup, maka untuk set dan get
data harus menggunakan single object .value.
Tapi untuk menampilkan di template kita tidak perlu menggunakan single object .value.

Jika di atas sebelumnya kita sudah melakukan import beberapa hook dari Vuex, Vue
Router dan Toastification, maka sekarang kita akan membuat state yang isinya adalah
hook tersebut. Ini digunakan agar kita dapat menggunakan library tersebut di dalam
Composition API.

459
//store vuex
const store = useStore()

//route
const router = useRouter()

//same interface as this.$toast


const toast = useToast()

Selanjutnya, kita akan membuat sebuah function yang bernama register, function ini
akan di jalankan ketika form di submit.

//function register, fungsi ini di jalankan ketika form di submit


function register() {

//...

Di dalam function register di atas, pertama kita melakukan define sebuah variable
menggunakan let, yang isinya merupakan object yang sudah kita buah sebelumnya di
dalam state user.

//define variable
let name = user.name
let email = user.email
let password = user.password
let password_confirmation = user.password_confirmation

Contohnya di atas, untuk variable name akan mengambil nilai dari state user dan object-
nya adalah name. Begitu juga seterusnya.

Setelah data yang dikirim melalui form di simpan di dalam variable-variable di atas,
sekarang kita lanjutkan untuk melakukan dispatch ke dalam action Vuex dengan
parameter variable-variable yang berisi data dari form di atas.

460
//panggil actions "register" dari module "auth"
store.dispatch('auth/register', {
name,
email,
password,
password_confirmation
})

Di atas, kita melakukan dispatch atau memanggil ke dalam module Vuex yang bernama
auth dan action yang bernama register.

Dari proses dispatch di atas, kita memiliki 2 jenis callback yaitu then dan catch. Jika data
berhasil di simpan ke database atau proses register berhasil, maka akan masuk ke dalam
then. Dimana di dalamnya kita akan melakukan proses redirect ke dalam sebuah route
yang bernama dashboard dan menampilkan sebuah Toast/Message Register Berhasil.

.then(() => {

//redirect ke dashboard
router.push({name: 'dashboard'})

toast.success("Register Berhasil!")

})

Tapi jika gagal, maka akan masuk ke dalam catch. Dimana di dalamnya kita melakukan
sebuah assign state validation dengan data error hasil response.

461
.catch(error => {
//show validation message
validation.value = error

//show validation name with toast


if(validation.value.name) {
toast.error(`${validation.value.name[0]}`)
}

//show validation email with toast


if(validation.value.email) {
toast.error(`${validation.value.email[0]}`)
}

//show validation password with toast


if(validation.value.password) {
toast.error(`${validation.value.password[0]}`)
}
})

Kemudian, kita lakukan return semua state dan function yang ada di dalam Composition
API agar dapat digunakan di dalam template.

//return a state and function


return {
user, // <-- state user
validation, // <-- state validation
register, // <-- method register
toast // <-- hook toast
}

Kemudian, di dalam template untuk form action ketika di submit, maka akan memanggil
function register yang sudah kita buat di atas.

<form @submit.prevent="register">

Dan di dalam input text, kita membuat directive v-model yang isinya merupakan state
user dan object name di dalamnya. Contohnya berikut ini :

462
<input type="text" v-model="user.name" class="mt-2 appearance-none w-
full bg-gray-200 border border-gray-200 rounded h-7 shadow-sm
placeholder-gray-600 focus:outline-none focus:placeholder-gray-600
focus:bg-white focus-within:text-gray-600 p-5" placeholder="Nama
Lengkap">

Langkah 2 - Edit Module Auth Vuex


Sekarang, kita akan menambahkan beberapa kode di dalam module auth di Vuex, disini
kita akan menambahkan sebuah state, actions dan mutations.

Silahkan buka file src/store/module/auth.js, kemudian ubah kode-nya menjadi seperti


berikut ini :

//import global API


import Api from '../../api/Api'

const auth = {

//set namespace true


namespaced: true,

//state
state: {

//state untuk "token", pakai localStorage, untuk menyimpan


informasi tentang token
token: localStorage.getItem('token') || '',

//state "user", pakai localStorage, untuk menyimpan data user


yang sedang login
user: JSON.parse(localStorage.getItem('user')) || {},

},

//mutations
mutations: {

//update state "token" dan state "user" dari hasil response


AUTH_SUCCESS(state, token, user) {
state.token = token // <-- assign state "token" dengan
response "token"

463
state.user = user // <-- assign state "user" dengan
response data "user"
},

},

//actions
actions: {

//action register
register({ commit }, user) {

//define callback promise


return new Promise((resolve, reject) => {

//send data ke server


Api.post('/register', {

//data yang dikirim ke serve untuk proses


register
name: user.name,
email: user.email,
password: user.password,
password_confirmation:
user.password_confirmation

})

.then(response => {

//define variable dengan isi hasil response dari


server
const token = response.data.token
const user = response.data.data

//set localStorage untuk menyimpan token dan


data user
localStorage.setItem('token', token)
localStorage.setItem('user',
JSON.stringify(user))

//set default header axios dengan token


Api.defaults.headers.common['Authorization'] =
`Bearer ${token}`

//commit auth success ke mutation

464
commit('AUTH_SUCCESS', token, user)

//resolve ke component dengan hasil response


resolve(response)

}).catch(error => {

//jika gagal, remove localStorage dengan key


token
localStorage.removeItem('token')

//reject ke component dengan hasil response


reject(error.response.data)

})

})
},

},

//getters
getters: {

export default auth

Dari penambahan kode di atas, pertama kita import konfiguasri global API, ini akan kita
gunakan sebagai baseURL endpoint Laravel.

//import global API


import Api from '../../api/Api'

Selanjutnya, di dalam Vuex State kita definisikan 2 state baru, yaitu token yang akan di
gunakan untuk menyimpan informasi token yang di dapatkan dari Laravel Passport dan
user yang akan digunakan untuk menyimpan informasi data user yang sedang login.

465
//state
state: {

//state untuk "token", pakai localStorage, untuk menyimpan informasi


tentang token
token: localStorage.getItem('token') || '',

//state "user", pakai localStorage, untuk menyimpan data user yang


sedang login
user: JSON.parse(localStorage.getItem('user')) || {},

},

Kemudian di dalam Vuex Actions, kita membuat sebuah action baru dengan nama
register. Dan di dalamnya kita menggunakan 2 parameter, yang pertama adalah commit
dan yang kedua adalah user.

Untuk parameter commit akan kita gunakan untuk melakukan proses commit ke dalam
sebuah mutation. Dan untuk parameter user merupakan data user yang dikirim dari
component register, yang isinya adalah name, email, password dan
password_confirmation.

karena kita nanti akan mengembalikan sebuah callback ke dalam component, maka kita
harus definisikan di dalam sebuah Promise yang di dalamnya ada parameter resolve dan
reject. Jika proses berhasil, maka akan mengembalikan callback berupa resolve dan jika
proses gagal, maka akan mengembalikan callback reject.

//define callback promise


return new Promise((resolve, reject) => {
//...
}

Dan di dalam action register kita melakukan send data user yang kita dapatkan dari
parameter ke dalam Laravel melalui endpoint API http://localhost/8000/api/register.

466
//send data ke server
Api.post('/register', {

//data yang dikirim ke serve untuk proses register


name: user.name,
email: user.email,
password: user.password,
password_confirmation: user.password_confirmation

})

Jika proses pengiriman data di atas berhasil, maka akan masuk di dalam callback then dan
isinya sebagai berikut :

.then(response => {

//define variable dengan isi hasil response dari server


const token = response.data.token
const user = response.data.data

//set localStorage untuk menyimpan token dan data user


localStorage.setItem('token', token)
localStorage.setItem('user', JSON.stringify(user))

//set default header axios dengan token


Api.defaults.headers.common['Authorization'] = `Bearer ${token}`

//commit auth success ke mutation


commit('AUTH_SUCCESS', token, user)

//resolve ke component dengan hasil response


resolve(response)

})

Di atas, pertama kita definisikan 2 variable, yaitu token dan user yang mana isinya akan
mengambil dari hasil response data yang berupa token data informasi user yang sedang
login.

467
//define variable dengan isi hasil response dari server
const token = response.data.token
const user = response.data.data

Setelah itu, kita lakukan set data token dan user tersebut ke dalam sebuah
localStorage.

//set localStorage untuk menyimpan token dan data user


localStorage.setItem('token', token)
localStorage.setItem('user', JSON.stringify(user))

Dan selanjutnya kita konfigurasi Header Axios Authorization dengan value Bearer +
Token.

//set default header axios dengan token


Api.defaults.headers.common['Authorization'] = `Bearer ${token}`

Kemudian, kita melakukan commit ke dalam mutation yang bernama AUTH_SUCCES dan di
dalamnya kita parsing 2 parameter, yaitu data token dan data user.

//commit auth success ke mutation


commit('AUTH_SUCCESS', token, user)

Dan di dalam mutation AUTH_SUCCESS kita melakukan perubahan nilai dari state token
dan user sesuai dengan parameter yang dikirim melalui commit di atas.

468
//mutations
mutations: {

//update state "token" dan state "user" dari hasil response


AUTH_SUCCESS(state, token, user) {
state.token = token // <-- assign state "token" dengan response
"token"
state.user = user // <-- assign state "user" dengan response
data "user"
},

},

INFORMASI : mutation digunakan untuk mengubah nilai dari sebuah state

Terakhir kita lakukan resolve dengan isi response dari API, ini digunakan agar di dalam
component kita dapat menerima sebuah callback dari Promise yang berstatus sukses.

//resolve ke component dengan hasil response


resolve(response)

Jika di dalam server terdapat error, entah itu validasi atau kesalahan server, maka akan
masuk ke dalam callback catch dan isinya kita melakukan remove sebuah localStorage
dan melakukan reject dengan isi error response. Ini digunakan agar component dapat
menerima sebuah callback berupa error.

Langkah 3 - Uji Coba Proses Register


Sekarang, kita akan mencoba menjalankan proses register, silahkan buka di
http://localhost:8080/register, kemudian silahkan klik REGISTER tanpa mengisi data
apapun. Maka kita akan mendapatkan validasi seperti berikut ini :

469
Di atas, kita mendapatkan sebuah Toast/Message berupa validasi yang dikirim melalui
Laravel. Sekarang, kita coba memasukkan data name, email, password dan
password_confirmation, jika berhasil maka kurang lebih hasilnya seperti berikut ini :

Di atas, kita sebenarnya sudah berhasil melakukan proses register. Toast/Message E muncul
karena kita belum memiliki route yang bernama dashboard.

Bisa kita lihat juga di tab Application > Local Storage, maka kita sudah berhasil
melakukan penyimpanan data token dan user di dalam localStorage.

470
471
Menampilkan Halaman Dashboard Donatur

Pada tahap kali ini kita akan belajar bagaimana cara membuat halaman dashboard, halaman
ini akan muncul ketika kita berhasil melakukan proses register maupun login. Dan di dalam
halaman ini kita akan menampilkan informasi data user yang sedang login, seperti foto dan
nama.

Kita juga akan menerapkan keamanan untuk route daashboard, yaitu halaman ini hanya
bisa diakses jika user tersebut sudah melakukan proses register ataupun login.

Langkah 1 - Membuat View/Component Dashboard


Pertama, sebelum kita membuat konfigurasi route untuk halaman dashboard, maka kita
akan membuat view/component untuk halaman dashboard terlebih dahulu. Silahkan buat
folder baru dengan nama dashboard di dalam folder src/views dan di dalam folder
dashboard silahkan buat file baru dengan nama Index.vue dan masukkan kode berikut
ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">

<div class="bg-white p-5 rounded-md shadow-md mb-5">

<div class="grid grid-cols-1 md:grid-cols-12 items-


center gap-4">
<div class="col-span-1 md:col-span-2">
<img :src="user.avatar" class="rounded-full w-30
h-30">
</div>
<div class="col-span-1 md:col-span-6">
<div class="font-bold text-base">
{{ user.name }}
</div>
<div class="mt-3">
<a href="#"
class="bg-gray-700 py-1 px-3 rounded
shadow-md text-white uppercase">Edit Profile</a>
</div>
</div>
</div>
<div class="border-2 border-gray-200 mt-3 mb-2"></div>

472
<a href="#">
<div class="grid grid-cols-5 gap-4 bg-gray-300 p-3
rounded-md shadow-sm mb-3">
<div class="col-span-5">
<i class="fa fa-heart" aria-
hidden="true"></i> Donasi Saya
</div>
</div>
</a>

<a href="#">
<div class="grid grid-cols-5 gap-4 bg-gray-300 p-3
rounded-md shadow-sm mb-3">
<div class="col-span-5">
<i class="fa fa-user-circle" aria-
hidden="true"></i> Profile Saya
</div>
</div>
</a>

<a href="#">
<div class="grid grid-cols-5 gap-4 bg-gray-300 p-3
rounded-md shadow-sm mb-3">
<div class="col-span-5">
<i class="fa fa-key" aria-hidden="true"></i>
Ubah Password
</div>
</div>
</a>

<a href="#">
<div class="grid grid-cols-5 gap-4 bg-gray-300 p-3
rounded-md shadow-sm mb-3">
<div class="col-span-5">
<i class="fa fa-sign-out-alt" aria-
hidden="true"></i> Logout
</div>
</div>
</a>

</div>

</div>
</div>
</template>

473
<script>

//hook vuex
import { useStore } from 'vuex'
//hook vue
import { computed, onMounted } from 'vue'

export default {

setup() {

//store vuex
const store = useStore()

//mounted
onMounted(() => {
//panggil action "getUser" dari module "auth" vuex
store.dispatch('auth/getUser')

})

//data user login


const user = computed(() => {
return store.state.auth.user
})

//return a state and function


return {
user, // <-- state user
}

}
}
</script>

<style>

</style>

Di atas, pertama kita import hook yang bernama useStore dari Vuex, hook ini akan
berfungsi agar kita dapat menggunakan Vuex di dalam Composition API.

474
//hook vuex
import { useStore } from 'vuex'

Selanjutnya, kita juga import 2 hook dari Vue, yaitu computed dan onMounted.

//hook vue
import { computed, onMounted } from 'vue'

kemudian kita definisikan sebuah Composition APi menggunakan function setup. Kurang
lebih seperti berikut ini :

setup() {

//...

kemudian kita buat state dengan nama store yang mana di dalamnya berisi hook dari
Vuex. Ini digunakan agar kita dapat memanggil Vuex di dalam Composition API.

//store vuex
const store = useStore()

Kemudian kita saat component melakukan proses mounted dengan hook onMounted, kita
melakukan dispatch ke dalam module auth Vuex dan memanggil sebuah action yang
bernama getUser.

//mounted
onMounted(() => {
//panggil action "getUser" dari module "auth" vuex
store.dispatch('auth/getUser')

})

Selanjutnya, kita buat sebuah state yang bernama user dengan jenis computed. Hook

475
computed disini akan digunakan untuk mendapatkan sebuah state yang bernama user
yang berada di dalam module auth Vuex dan hook computed ini bersifat reaktif.

const user = computed(() => {


return store.state.auth.user // <-- get state "user" dari module
"auth" Vuex
})

Setelah itu, agar state di atas dapat dipanggil di dalam component, maka kita harus
melakukan return terlebih dahulu. Kurang lebih seperti berikut ini :

//return a state and function


return {
user, // <-- state user
}

Dan untuk memanggil data user di template, kita bisa menggunakan kode seperti berikut ini
:

<img :src="user.avatar" class="rounded-full w-30 h-30">

Kode di atas akan menampilkan foto user yang sedang login. Dan kode berikut ini untuk
menampilkan nama user yang sedang login.

{{ user.name }}

Langkah 2 - Edit Module Auth Vuex


Sekarang, kita akan lanjutkan untuk menambahkan beberapa kode di dalam module auth
Vuex. Disini kita akan menambahkan action, mutation dan getter untuk proses
mendapatkan data user yang sedang login. Silahkan buka file
src/store/module/auth.js dan ubah semua kode-nya menjadi seperti berikut ini :

//import global API


import Api from '../../api/Api'

476
const auth = {

//set namespace true


namespaced: true,

//state
state: {

//state untuk "token", pakai localStorage, untuk menyimpan


informasi tentang token
token: localStorage.getItem('token') || '',

//state "user", pakai localStorage, untuk menyimpan data user


yang sedang login
user: JSON.parse(localStorage.getItem('user')) || {},

},

//mutations
mutations: {

//update state "token" dan state "user" dari hasil response


AUTH_SUCCESS(state, token, user) {
state.token = token // <-- assign state "token" dengan
response "token"
state.user = user // <-- assign state "user" dengan
response data "user"
},

//update state user dari hasil response register / login


GET_USER(state, user) {
state.user = user // <-- assign state user dengan response
data user
},

},

//actions
actions: {

//action register
register({ commit }, user) {

//define callback promise


return new Promise((resolve, reject) => {

477
//send data ke server
Api.post('/register', {

//data yang dikirim ke serve untuk proses


register
name: user.name,
email: user.email,
password: user.password,
password_confirmation:
user.password_confirmation

})

.then(response => {

//define variable dengan isi hasil response dari


server
const token = response.data.token
const user = response.data.data

//set localStorage untuk menyimpan token dan


data user
localStorage.setItem('token', token)
localStorage.setItem('user',
JSON.stringify(user))

//set default header axios dengan token


Api.defaults.headers.common['Authorization'] =
`Bearer ${token}`

//commit auth success ke mutation


commit('AUTH_SUCCESS', token, user)

//commit get user ke mutation


commit('GET_USER', user)

//resolve ke component dengan hasil response


resolve(response)

}).catch(error => {

//jika gagal, remove localStorage dengan key


token
localStorage.removeItem('token')

//reject ke component dengan hasil response

478
reject(error.response.data)

})

})
},

//action getUser
getUser({ commit }) {

//ambil data token dari localStorage


const token = localStorage.getItem('token')

Api.defaults.headers.common['Authorization'] = `Bearer
${token}`
Api.get('/user')
.then(response => {
//commit ke mutatuin GET_USER dengan hasil response
commit('GET_USER', response.data)

})
},

},

//getters
getters: {

//get current user


currentUser(state) {
return state.user // <-- return dengan data user
},

//loggedIn
isLoggedIn(state) {
return state.token // return dengan data token
},

export default auth

Di atas, kita menambahkan 1 action baru dengan nama getUser, action ini di jalankan

479
ketika kita menjalankan halaman dashboard, dimana di dalam halaman tersebut kita
melakukan dispatch yang mengarah ke action ini.

Di dalam action getUser kita melakukan fetching data ke API Laravel dengan endpoint
http://localhost:8000/api/user dan saat melakukan fetching, kita juga mengirimkan sebuah
Bearer + Token ke dalam header Authorization.

//ambil data token dari localStorage


const token = localStorage.getItem('token')

Api.defaults.headers.common['Authorization'] = `Bearer ${token}`


Api.get('/user')

//...

Jika proses fetching berhasil dan mendapatkan sebuah response, maka kita akan lakukan
sebuah commit ke dalam mutation yang bernama GET_USER dan di dalamnya kita berikan
parameter dari hasil response data API.

.then(response => {
//commit ke mutatuin GET_USER dengan hasil response
commit('GET_USER', response.data)

})

Dan di dalam mutation GET_USER, kita akan mengubah nilai state user dengan value
yang di dapatkan dari response data API di atas.

//update state user dari hasil response register / login


GET_USER(state, user) {
state.user = user // <-- assign state user dengan response data user
},

Selanjutnya, kita juga menambahkan 2 getter, yaitu :

currentUser - getter ini akan mengembalikan sebuah data user yang diambil dari
state user.
isLoggedIn - getter ini akan mengembalikan sebuah data token yang diambil dari
state token.

480
getter tersebut nantinya akan kita gunakan untuk menentukan sebuah halaman yang
membutuhkan proses otentikasi terlebih dahulu.

Langkah 3 - Membuat Route untuk Halaman Dashboard


Sekarang, kita lanjutkan untuk membuat route untuk halaman dashboard, disini kita juga
akan menambahkan sebuah kondisi untuk route dashboard. Jadi route tersebut hanya bisa
di akses ketika user sudah melakukan proses otentikasi.

Silahkan buka file src/router/index.js, kemudian ubah semua kode-nya menjadi


seperti berikut ini :

//import vue router


import { createRouter, createWebHistory } from 'vue-router'

//import store vuex


import store from '@/store'

//define a routes
const routes = [
{
path: '/register',
name: 'register',
component: () => import( /* webpackChunkName: "register" */
'@/views/auth/Register.vue')
},
{
path: '/login',
name: 'login',
component: () => import( /* webpackChunkName: "login" */
'@/views/auth/Login.vue')
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import( /* webpackChunkName: "dashboard" */
'@/views/dashboard/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
]

481
//create router
const router = createRouter({
history: createWebHistory(),
routes // <-- routes,
})

//define route for handle authentication


router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
//cek nilai dari getters isLoggedIn di module auth
if (store.getters['auth/isLoggedIn']) {
next()
return
}
next('/login')
} else {
next()
}
})

export default router

Di ata, pertama kita import store Vuex dari folder store. Karena disini kita akan
menggunakan Vuex untuk mendapatkan data getters dari module auth untuk data
token.

//import store vuex


import store from '@/store'

kemudian, kita define route baru untuk halaman dashboard kurang lebih seperti berikut ini :

482
{
path: '/dashboard',
name: 'dashboard',
component: () => import( /* webpackChunkName: "dashboard" */
'@/views/dashboard/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},

Dari route di atas, kita tambahkan sebuah meta yang isinya adalah requiresAuth: true,
yang artinya halaman ini hanya bisa diakses ketika user sudah melakukan proses otentikasi.

kemudian kita akan membuat fungsi untuk requiresAuth, yang mana di dalamnya akan
mengecek sebuah getters dari module auth yang bernama isLoggedIn.

//define route for handle authentication


router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
//cek nilai dari getters isLoggedIn di module auth
if (store.getters['auth/isLoggedIn']) {
next()
return
}
next('/login')
} else {
next()
}
})

Sekarang, jika kita buka halaman http://localhost:8080/dashboard, maka kita sudah berhasil
menampilkan nama user yang sedang login dan juga foto profile-nya.

483
484
Membuat Proses Logout Donatur

Pada kesempatan kali ini kita semua akan belajar bagaimana cara membuat fungsi logout di
dalam Vue Js. Disini kita akan menggunakan 2 proses, yang pertama akan mengahpus data
token dan user yang kita simpan di localStorage dan yang kedua kita akan mengahpus
data token yang ada di server.

Langkah 1 - Edit View/Component Dashboard


Sekarang, kita akan menambahkan sebuah function di dalam component dashboard,
dimana function ini akan melakukan proses logout di dalam Vue Js. Silahkan buka file
src/views/dashboard/Index.vue dan ubah kode-nya menjadi seperti berikut ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">

<div class="bg-white p-5 rounded-md shadow-md mb-5">

<div class="grid grid-cols-1 md:grid-cols-12 items-


center gap-4">
<div class="col-span-1 md:col-span-2">
<img :src="user.avatar" class="rounded-full w-30
h-30">
</div>
<div class="col-span-1 md:col-span-6">
<div class="font-bold text-base">
{{ user.name }}
</div>
<div class="mt-3">
<a href="#"
class="bg-gray-700 py-1 px-3 rounded
shadow-md text-white uppercase">Edit Profile</a>
</div>
</div>
</div>
<div class="border-2 border-gray-200 mt-3 mb-2"></div>

<a href="#">
<div class="grid grid-cols-5 gap-4 bg-gray-300 p-3
rounded-md shadow-sm mb-3">

485
<div class="col-span-5">
<i class="fa fa-heart" aria-
hidden="true"></i> Donasi Saya
</div>
</div>
</a>

<a href="#">
<div class="grid grid-cols-5 gap-4 bg-gray-300 p-3
rounded-md shadow-sm mb-3">
<div class="col-span-5">
<i class="fa fa-user-circle" aria-
hidden="true"></i> Profile Saya
</div>
</div>
</a>

<a href="#">
<div class="grid grid-cols-5 gap-4 bg-gray-300 p-3
rounded-md shadow-sm mb-3">
<div class="col-span-5">
<i class="fa fa-key" aria-hidden="true"></i>
Ubah Password
</div>
</div>
</a>

<a @click="logout" style="cursor:pointer">


<div class="grid grid-cols-5 gap-4 bg-gray-300 p-3
rounded-md shadow-sm mb-3">
<div class="col-span-5">
<i class="fa fa-sign-out-alt" aria-
hidden="true"></i> Logout
</div>
</div>
</a>

</div>

</div>
</div>
</template>

<script>

//hook vuex

486
import { useStore } from 'vuex'

//hook vue router


import { useRouter } from 'vue-router'
//hook vue
import { computed, onMounted } from 'vue'

//hook Toast
import { useToast } from "vue-toastification"

export default {

setup() {

//store vuex
const store = useStore()

//vue router
const router = useRouter()

// Same interface as this.$toast


const toast = useToast()

//mounted
onMounted(() => {
//panggil action "getUser" dari module "auth" vuex
store.dispatch('auth/getUser')

})

//data user login


const user = computed(() => {
return store.state.auth.user
})

//method logout
function logout() {

//panggil action "logout" di dalam module "auth"


store.dispatch('auth/logout')
.then(() => {

//jika berhasil, akan di arahkan ke route login


router.push({
name: 'login'
})

487
toast.success("Logout Berhasil!")

})

//return a state and function


return {
logout, // <-- method logout
user, // <-- state user
}

}
}
</script>

<style>

</style>

Dari perubahan kode view/component dashboard di atas, pertama kita import hook yang
bernama useRouter dari Vue Router, ini digunakan agar kita dapat memanggil Vue
Router di dalam Composition API.

//hook vue router


import { useRouter } from 'vue-router'

Kemudian kita juga import hook yang bernama useToast, hook ini akan digunakan untuk
menampilkan Toast/Message di dalam Composition API.

//hook Toast
import { useToast } from "vue-toastification"

Dan agar kedua hook tersebut mudah digunakan di dalam Composition API, maka kita
masukkan ke dalam sebuah state.

488
//vue router
const router = useRouter()

// Same interface as this.$toast


const toast = useToast()

Dan jika kita perhatikan, kita menambahkan sebuah event @click di dalam template yang
mengarah ke dalam sebuah function yang bernama logout.

<a @click="logout" style="cursor:pointer">


<div class="grid grid-cols-5 gap-4 bg-gray-300 p-3 rounded-md shadow-
sm mb-3">
<div class="col-span-5">
<i class="fa fa-sign-out-alt" aria-hidden="true"></i> Logout
</div>
</div>
</a>

Jadi, jika link di atas di klik, maka akan memanggil function yang bernama logout.

//method logout
function logout() {

// ...

Di dalam function logout di atas, kita melakukan dispatch ke dalam module auth Vuex
dan memanggil action yang bernama logout.

//panggil action "logout" di dalam module "auth"


store.dispatch('auth/logout')

Jika proses dispatch di atas berhasil, maka kita akan di redirect atau diarahkan ke dalam
sebuah route yang bernama login dan akan menampilkan Toast/Message Logout
Berhasil!.

489
.then(() => {

//jika berhasil, akan di arahkan ke route login


router.push({
name: 'login'
})

toast.success("Logout Berhasil!")

})

Dan agar function logout dapat di gunakan di dalam component, maka kita harus
melakukan return terlebih dahulu.

//return a state and function


return {
logout, // <-- method logout
user, // <-- state user
}

Langkah 2 - Menambahkan Action Logout di Module Auth


Vuex
Sekarang, kita lanjutkan untuk menambahkan 1 action yang bernama logout di dalam
module auth Vuex. Silahkan buka file src/store/module/auth.js dan ubah kode-nya
menjadi seperti berikut ini :

//import global API


import Api from '../../api/Api'

const auth = {

//set namespace true


namespaced: true,

//state
state: {

//state untuk "token", pakai localStorage, untuk menyimpan

490
informasi tentang token
token: localStorage.getItem('token') || '',

//state "user", pakai localStorage, untuk menyimpan data user


yang sedang login
user: JSON.parse(localStorage.getItem('user')) || {},

},

//mutations
mutations: {

//update state "token" dan state "user" dari hasil response


AUTH_SUCCESS(state, token, user) {
state.token = token // <-- assign state "token" dengan
response "token"
state.user = user // <-- assign state "user" dengan
response data "user"
},

//update state user dari hasil response register / login


GET_USER(state, user) {
state.user = user // <-- assign state user dengan response
data user
},
//fungsi logout
AUTH_LOGOUT(state) {
state.token = '' // <-- set state token ke empty
state.user = {} // <-- set state user ke empty array
},

},

//actions
actions: {

//action register
register({ commit }, user) {

//define callback promise


return new Promise((resolve, reject) => {

//send data ke server


Api.post('/register', {

//data yang dikirim ke serve untuk proses

491
register
name: user.name,
email: user.email,
password: user.password,
password_confirmation:
user.password_confirmation

})

.then(response => {

//define variable dengan isi hasil response dari


server
const token = response.data.token
const user = response.data.data

//set localStorage untuk menyimpan token dan


data user
localStorage.setItem('token', token)
localStorage.setItem('user',
JSON.stringify(user))

//set default header axios dengan token


Api.defaults.headers.common['Authorization'] =
`Bearer ${token}`

//commit auth success ke mutation


commit('AUTH_SUCCESS', token, user)

//commit get user ke mutation


commit('GET_USER', user)

//resolve ke component dengan hasil response


resolve(response)

}).catch(error => {

//jika gagal, remove localStorage dengan key


token
localStorage.removeItem('token')

//reject ke component dengan hasil response


reject(error.response.data)

})

492
})
},

//action getUser
getUser({ commit }) {

//ambil data token dari localStorage


const token = localStorage.getItem('token')

Api.defaults.headers.common['Authorization'] = `Bearer
${token}`
Api.get('/user')
.then(response => {
//commit ke mutatuin GET_USER dengan hasil response
commit('GET_USER', response.data)

})
},

//action logout
logout({ commit }) {
//define callback promise
return new Promise((resolve) => {
//commit ke mutation AUTH_LOGOUT
commit('AUTH_LOGOUT')
//remove value dari localStorage
localStorage.removeItem('token')
localStorage.removeItem('user')
//di atas kita set data-nya menjadi 0

//delete header axios


delete Api.defaults.headers.common['Authorization']
//return resolve ke component
resolve()

})
},

},

//getters
getters: {

//get current user


currentUser(state) {
return state.user // <-- return dengan data user

493
},

//loggedIn
isLoggedIn(state) {
return state.token // return dengan data token
},

export default auth

Di atas, kita menambahkan 1 action yang bernama logout di dalam di dalamnya kita
membuat sebuah Promise agar dapat mengembalikan sebuah callback ke dalam
component.

//define callback promise


return new Promise((resolve) => {
//...
}

Pertama, kita melakukan commit ke dalam mutation yang bernama AUTH_LOGOUT, dan di
dalam mutation tersebut kita mengubah nilai state token dan user menjadi null/kosong.

//fungsi logout
AUTH_LOGOUT(state) {
state.token = '' // <-- set state token ke empty
state.user = {} // <-- set state user ke empty array
},

Setelah itu, kita akan menghapus 2 localStorage dengan nama key token dan user. Ini
berfungsi agar data localStoarge yang ada di dalam browser juga ikut dihapus.

//remove value dari localStorage


localStorage.removeItem('token')
localStorage.removeItem('user')

494
Kemudian, kita lakukan delete header Authorization dari Axios.

//delete header axios


delete Api.defaults.headers.common['Authorization']

Terakhir, kita lakukan resolve agar di dalam component bisa menerima callback dari
action logout.

Sekarang kita uji coba untuk melakukan proses logout di dalam project Vue Js, jika berhasil
maka kurang lebih kita akan di arahkan ke halaman login dan menampilkan sebuah
Toast/Message Logout berhasil!.

Dan jika kita cek juga di tab Application > Local Stoarge, maka data kita juga sudah
berhasil di hapus di dalam browser.

495
Membuat Proses Login Donatur

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat proses login di dalam
Vue Js menggunakan Rest API yang sudah kita buat di dalam Laravel. Disini kita juga akan
menerapkan validasi seperti yang sudah kita lakukan di proses register dan tentunya untuk
centralize atau data yang terpusat kita akan menggunakan Vuex.

Langkah 1 - View/Component Login


Pertama, kita akan melakukan perubahan dan penambahan kode di dalam view/component
login. Silahkan buka file src/views/auth/Login.vue dan ubah semua kode-nya menjadi
seperti berikut ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-5 sm:w-full
md:w-5/12">

<form @submit.prevent="login">
<div class="bg-white rounded-md shadow-md p-5">
<div class="text-xl">
MASUK AKUN
</div>
<div class="border-2 border-gray-200 mt-3
mb-2"></div>

<div class="mb-5">
<label class="mt-2">Alamat Email</label>
<input type="email" v-model="user.email"
class="mt-2 appearance-none w-full bg-
gray-200 border border-gray-200 rounded h-7 shadow-sm placeholder-
gray-600 focus:outline-none focus:placeholder-gray-600 focus:bg-white
focus-within:text-gray-600 p-5"
placeholder="Alamat Email">
</div>

<div class="mb-5">
<label class="mt-2">Password</label>
<input type="password" v-model="user.password"
class="mt-2 appearance-none w-full bg-
gray-200 border border-gray-200 rounded h-7 shadow-sm placeholder-
gray-600 focus:outline-none focus:placeholder-gray-600 focus:bg-white

496
focus-within:text-gray-600 p-5"
placeholder="Password">
</div>

<div>
<button
class="bg-gray-700 py-1 px-3 text-white
rounded-md shadow-md text-xl inline-block w-full focus:outline-none
focus:bg-gray-900">MASUK</button>
</div>

</div>
</form>

<div class="text-center mt-5">


Belum punya akun ? <router-link :to="{name: 'register'}"
class="underline text-blue-600">Daftar Sekarang
!</router-link>
</div>

</div>
</div>
</template>

<script>

//hook vue
import { ref, reactive } from 'vue'
//hook vuex
import { useStore } from 'vuex'
//hook vue router
import { useRouter } from 'vue-router'
//hook Toast
import { useToast } from "vue-toastification"

export default {

setup() {

//user state
const user = reactive({
email: '',
password: ''
})

//validation state

497
const validation = ref([])

//store vuex
const store = useStore()

//route
const router = useRouter()

// Same interface as this.$toast


const toast = useToast()

//method login
function login() {

//define variable
let email = user.email
let password = user.password
//panggil actions "login" dari module "auth" Vuex
store.dispatch('auth/login', {
email,
password
})
.then(() => {

//redirect ke dashboard
router.push({name: 'dashboard'})

toast.success("Login Berhasil!")

}).catch(error => {
//assign validaation message
validation.value = error

//show validation email with toast


if(validation.value.email) {
toast.error(`${validation.value.email[0]}`)
}

//show validation password with toast


if(validation.value.password) {
toast.error(`${validation.value.password[0]}`)
}

//show login failed


if(validation.value.message) {
toast.error(`${validation.value.message}`)

498
}
})
}

//check user is loggedIn


onMounted(() => {
if(store.getters['auth/isLoggedIn']) {
router.push({name: 'dashboard'})
}
})

//return object
return {
user, // <-- state user
validation, // <-- state validation
login, // <-- method login
}

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita lakukan import hook ref, reactive dari
Reactivity API.

//hook vue
import { ref, reactive } from 'vue'

Selanjutnya, kita import juga hook yang bernama useStore dari Vuex. Ini berguna agar
kita dapat memanggil store Vuex di dalam Composition API.

//hook vuex
import { useStore } from 'vuex'

Dan kita juga import 2 hook lagi, yaitu useRouter dari Vue Router dan useToast dari

499
Toastification.

//hook vue router


import { useRouter } from 'vue-router'
//hook Toast
import { useToast } from "vue-toastification"

Seperti sebelum-sebelumnya, untuk mendefinisikan sebuah Composition API, kita bisa


menggunakan function yaang bernama setup.

setup() {

//...

Di dalam Composition API, pertama kita definisikan sebuah state baru yang bernama
user dan state ini menggunakan jenis Reactivity API yang bernama reactive dan di
dalam state tersebut kita menambahkan 2 object, yaitu email dan password. State ini
akan digunakan untuk menampung data yang dikirim dari form nantinya.

//user state
const user = reactive({
email: '',
password: ''
})

kemudian, kita buat state lagi dengan nama validation, state ini akan kita gunakan untuk
menampung sebuah response error yang dikirim dari Rest API Laravel, entah itu berupa
validasi error ataupun sebuah response login failed. Dan state ini menggunakan Reactivity
API dengan jenis ref.

//validation state
const validation = ref([])

500
CATATAN:
Jika menggunakan Reactivity API ref di dalam function setup, maka untuk set dan get
data harus menggunakan single object .value.
Tapi untuk menampilkan di template kita tidak perlu menggunakan single object .value.

Selanjutnya, kita definisikan beberapa state lagi untuk mempermudah dalam pemanggilan
sebuah hook di Composition API, seperti Vue Router, Vuex dan Toastification.

//store vuex
const store = useStore()

//route
const router = useRouter()

// Same interface as this.$toast


const toast = useToast()

Kemudian kita buat sebuah function yang bernama login, function ini akan dijalankan
ketika form di dalam template di submit.

//method login
function login() {

//...

Di dalam function login di atas, pertama kita membuat 2 variable baru, yaitu email dan
password. Dan kita isi kedua variable tersebut dengan object dari state user.

//define variable
let email = user.email
let password = user.password

Selanjutnya kita melakukan dispatch ke dalam module auth Vuex dan memanggil action
dengan nama login. dan di dalam parameter, kita parsing kedua variable di atas agar
dapat dibaca di dalam action Vuex.

501
//panggil actions "login" dari module "auth" Vuex
store.dispatch('auth/login', {
email,
password
})

Jika proses login berhasil, maka akan masuk di dalam callback then dan di dalamnya akan
melakukan redirect ke dalam sebuah route yang bernama dashboard dan menampilkan
Toast/Message Login Berhasil!.

.then(() => {

//redirect ke dashboard
router.push({name: 'dashboard'})

toast.success("Login Berhasil!")

})

Tapi jika login gagal, maka akan masuk ke dalam callback catch dan di dalamnya akan
melakukan assign error response ke dalam state validation. Dan kita check validasinya
satu-satu untuk menampilkan error spesifik dengan key-nya.

502
.catch(error => {
//assign validaation message
validation.value = error

//show validation email with toast


if(validation.value.email) {
toast.error(`${validation.value.email[0]}`)
}

//show validation password with toast


if(validation.value.password) {
toast.error(`${validation.value.password[0]}`)
}

//show login failed


if(validation.value.message) {
toast.error(`${validation.value.message}`)
}
})

Terakhir, agar semua state dan function dapat digunakan di dalam template, maka kita
perlu melakukan sebuah return.

//return object
return {
user, // <-- state user
validation, // <-- state validation
login, // <-- method login
}

Kemudian, di dalam template untuk form action ketika di submit, maka akan memanggil
function login yang sudah kita buat di atas.

<form @submit.prevent="login">

Langkah 2 - Menambahkan Action Login di Module Auth

503
Vuex
Sekarang kita lanjutkan untuk menambahkan action login di dalam module auth Vuex.
Silahkan buka file src/store/module/auth.js dan ubah semua kode-nya menjadi
seperti berikut ini :

//import global API


import Api from '../../api/Api'

const auth = {

//set namespace true


namespaced: true,

//state
state: {

//state untuk "token", pakai localStorage, untuk menyimpan


informasi tentang token
token: localStorage.getItem('token') || '',

//state "user", pakai localStorage, untuk menyimpan data user


yang sedang login
user: JSON.parse(localStorage.getItem('user')) || {},

},

//mutations
mutations: {

//update state "token" dan state "user" dari hasil response


AUTH_SUCCESS(state, token, user) {
state.token = token // <-- assign state "token" dengan
response "token"
state.user = user // <-- assign state "user" dengan
response data "user"
},

//update state user dari hasil response register / login


GET_USER(state, user) {
state.user = user // <-- assign state user dengan response
data user
},

//fungsi logout

504
AUTH_LOGOUT(state) {
state.token = '' // <-- set state token ke empty
state.user = {} // <-- set state user ke empty array
},

},

//actions
actions: {

//action register
register({ commit }, user) {

//define callback promise


return new Promise((resolve, reject) => {

//send data ke server


Api.post('/register', {

//data yang dikirim ke serve untuk proses


register
name: user.name,
email: user.email,
password: user.password,
password_confirmation:
user.password_confirmation

})

.then(response => {

//define variable dengan isi hasil response dari


server
const token = response.data.token
const user = response.data.data

//set localStorage untuk menyimpan token dan


data user
localStorage.setItem('token', token)
localStorage.setItem('user',
JSON.stringify(user))

//set default header axios dengan token


Api.defaults.headers.common['Authorization'] =
`Bearer ${token}`

505
//commit auth success ke mutation
commit('AUTH_SUCCESS', token, user)

//commit get user ke mutation


commit('GET_USER', user)

//resolve ke component dengan hasil response


resolve(response)

}).catch(error => {

//jika gagal, remove localStorage dengan key


token
localStorage.removeItem('token')

//reject ke component dengan hasil response


reject(error.response.data)

})

})
},

//action getUser
getUser({ commit }) {

//ambil data token dari localStorage


const token = localStorage.getItem('token')

Api.defaults.headers.common['Authorization'] = `Bearer
${token}`
Api.get('/user')
.then(response => {
//commit ke mutatuin GET_USER dengan hasil response
commit('GET_USER', response.data)

})
},

//action logout
logout({ commit }) {
//define callback promise
return new Promise((resolve) => {
//commit ke mutation AUTH_LOGOUT
commit('AUTH_LOGOUT')
//remove value dari localStorage

506
localStorage.removeItem('token')
localStorage.removeItem('user')
//di atas kita set data-nya menjadi 0

//delete header axios


delete Api.defaults.headers.common['Authorization']
//return resolve ke component
resolve()

})
},

//action login
login({ commit }, user) {

//define callback promise


return new Promise((resolve, reject) => {
Api.post('/login', {
//data yang dikirim ke server
email: user.email,
password: user.password,
})
.then(response => {
//define variable dengan isi hasil response dari
server
const token = response.data.token
const user = response.data.data
//set localStorage untuk menyimpan token dan data
user
localStorage.setItem('token', token)
localStorage.setItem('user', JSON.stringify(user))
//set default header axios dengan token
Api.defaults.headers.common['Authorization'] =
`Bearer ${token}`

//commit auth success ke mutation


commit('AUTH_SUCCESS', token, user)

//commit get user ke mutation


commit('GET_USER', user)

//resolve ke component dengan hasil response


resolve(response)
}).catch(error => {
//jika gagal, remove localStorage dengan key token
localStorage.removeItem('token')

507
//reject ke component dengan hasil response
reject(error.response.data)
})

})

},

//getters
getters: {

//get current user


currentUser(state) {
return state.user // <-- return dengan data user
},

//loggedIn
isLoggedIn(state) {
return state.token // return dengan data token
},

export default auth

Dari penambahan kode di atas, sebenarnya kita hanya menambahkan 1 action baru yang
bernama login dimana di dalam action ini terdapat 2 parameter, yaitu commit dan user.

//action login
login({ commit }, user) {

//...

Untuk parameter commit akan kita gunakan untuk melakukan commit data ke dalam
mutation dan untuk parameter user merupakan data yang dikirim dari component, yang
isinya adalah email dan password.

508
karena kita nanti akan mengembalikan sebuah callback ke dalam component, maka kita
harus definisikan di dalam sebuah Promise yang di dalamnya ada parameter resolve dan
reject. Jika proses berhasil, maka akan mengembalikan callback berupa resolve dan jika
proses gagal, maka akan mengembalikan callback reject.

//define callback promise


return new Promise((resolve, reject) => {
//...
}

Selanjutnya, kita melakukan fecthing ke dalam API Laravel dengan endpoint


http://localhost:8000/api/login dan menggunakan method POST. Kemudian di dalamnya kita
kirim juga data user yang isinya adalah email dan password yang di dapatkan melalui
component.

Api.post('/login', {
//data yang dikirim ke server
email: user.email,
password: user.password,
})

Jika proses login berhasil, maka akan masuk ke dalam callback then. Dimana di dalamnya
pertama kita lakukan definisi sebuah variable yang isinya adalah response yang di dapatkan
dari Rest API.

//define variable dengan isi hasil response dari server


const token = response.data.token
const user = response.data.data

Kemudian kita set sebuah localStorage dengan key token dan user, yang isinya adalah
variable-variable di atas.

//set localStorage untuk menyimpan token dan data user


localStorage.setItem('token', token)
localStorage.setItem('user', JSON.stringify(user))

Dan kita set default header Axios dengan Authorization yang isinya adalah Bearer +

509
Token.

//set default header axios dengan token


Api.defaults.headers.common['Authorization'] = `Bearer ${token}`

Setelah itu, kita melakukan 2 commit ke dalam mutation yang bernama AUTH_SUCCESS
dan GET_USER.

//commit auth success ke mutation


commit('AUTH_SUCCESS', token, user)

//commit get user ke mutation


commit('GET_USER', user)

Dan di dalam mutation AUTH_SUCCESS dan GET_USER kita melakukan perubahan nilai state
token dan user.

//update state "token" dan state "user" dari hasil response


AUTH_SUCCESS(state, token, user) {
state.token = token // <-- assign state "token" dengan response
"token"
state.user = user // <-- assign state "user" dengan response data
"user"
},

//update state user dari hasil response register / login


GET_USER(state, user) {
state.user = user // <-- assign state user dengan response data user
},

Dan setelah itu kita lakukan resolve agar component login dapat menerima callback dari
action login Vuex.

//resolve ke component dengan hasil response


resolve(response)

Tapi jika proses login gagal, maka akan masuk ke dalam callback catch dan di dalamnya

510
kita melakukan remove sebuah localStorage dengan nama key token.

//jika gagal, remove localStorage dengan key token


localStorage.removeItem('token')

Dan melakukan reject dengan isi sebuah error response. Ini digunakan agar component
login bisa menerima sebuah callback error response dari Rest API Laravel.

//reject ke component dengan hasil response


reject(error.response.data)

Langkah 3 - Uji Coba Proses Login


Sekarang kita bisa uji coba untuk proses login, silahkan buka http://localhost:8080/login dan
jika berhasil maka kurang lebih tampilannya seperti berikut ini :

Sekarang, silahkan klik button MASUK tanpa mengisi data apapun, maka kita akan
mendapatkan error validasi kurang lebih seperti berikut ini :

511
Dan sekarang kita coba dengan mengisi email dan password yang belum terdaftar di
dalam database, maka kita akan mendapatkan error login failed/gagal. Kurang lebih seperti
berikut ini :

Dan terakhir, kita uji coba memasukkan data email dan password yang sudah ada di
dalam database, maka kita akan diarahkan ke dalam halaman dashboard dan
menampilkan Toast/Message Login Berhasil!.

512
Langkah 4 - Check Halaman Login
Problemnya disini jika user sudah melakukan proses login dan berhasil masuk ke dalam
halaman dashboard tapi ingin mengakses halaman login kembali, maka ini tidak boleh
dilakukan.

Sekarang kita akan menambahkan sebuah pengecekan untuk halaman login, dimana jika
user yang sudah berhasil melakukan proses login, maka tidak boleh mengakses halaman
login kembali, yang artinya kita bisa lakukan redirect paksa ke dalam halaman dashboard.

Silahkan buka file src/views/auth/Login.vue kemudian cari kode berikut ini :

//hook vue
import { ref, reactive } from 'vue'

Dan ubah menjadi seperti berikut ini :

//hook vue
import { ref, reactive, onMounted } from 'vue'

Di atas, kita menambahkan hook yang bernama onMounted.

Selanjutnya, di dalam function setup, silahkan tambahkan kode berikut ini di bawah

513
function login :

//check user is loggedIn


onMounted(() => {
if(store.getters['auth/isLoggedIn']) {
router.push({name: 'dashboard'})
}
})

Di atas, saat component mounted, maka di dalamnya akan membuat sebuah kondisi untuk
memeriksa sebuah getters yang bernama isLoggedIn di dalam module auth Vuex. Jika
menghasilkan nilai true, maka akan dipaksa redirect ke halaman dashboard.

Kurang lebih hasilnya seperti berikut ini :

514
Konfigurasi Router untuk Donation

Pada tahap kali ini kita semua akan belajar untuk menambahkan route baru untuk
menampilkan halaman donasi, dimana route ini nanti kita set agar bisa diakses jika user
tersebut sudah melakukan proses otentikasi.

Langkah 1 - Membuat View Donation


Sekarang, silahkan buat folder baru dengan nama donation di dalam folder src/views
dan di dalam folder donation silahkan buat file baru dengan nama Index.vue. Dan
masukkan kode berikut ini :

<template>
<div class="pb-20 pt-20 text-center">
HALAMAN DONASI
</div>
</template>

<script>
export default {

}
</script>

515
Di atas kita menambahkan sample kode sederhana terlebih dahulu, ini digunakan untuk
memastikan apakah route kita nanti sudah berjalan atau tidak.

langkah 2 - Membuat Route Donation


Sekarang kita akan lanjutkan untuk mendefinisikan route baru untuk halaman donasi.
Silahkan buka file src/router/index.js dan ubah kode-nya menjadi seperti berikut ini :

//import vue router


import { createRouter, createWebHistory } from 'vue-router'

//import store vuex


import store from '@/store'

//define a routes
const routes = [
{
path: '/register',
name: 'register',
component: () => import( /* webpackChunkName: "register" */
'@/views/auth/Register.vue')
},
{
path: '/login',
name: 'login',
component: () => import( /* webpackChunkName: "login" */
'@/views/auth/Login.vue')
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import( /* webpackChunkName: "dashboard" */
'@/views/dashboard/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/donation',
name: 'donation.index',
component: () => import( /* webpackChunkName: "donationIndex" */
'@/views/donation/Index.vue'),
meta: {

516
//chek is loggedIn
requiresAuth: true
}
},
]

//create router
const router = createRouter({
history: createWebHistory(),
routes // <-- routes,
})

//define route for handle authentication


router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
//cek nilai dari getters isLoggedIn di module auth
if (store.getters['auth/isLoggedIn']) {
next()
return
}
next('/login')
} else {
next()
}
})

export default router

Di atas, kita menambahkan 1 definisi route baru untuk halaman donasi, yaitu :

{
path: '/donation',
name: 'donation.index',
component: () => import( /* webpackChunkName: "donationIndex" */
'@/views/donation/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},

Di atas, untuk path atau URL dari halaman donasi akan di arahkan ke dalam /donation.

517
Dan untuk name kita set dengan donation.index, ini agar mempermudah kita dalam
pemanggilan route di dalam template.

Dan untuk component kita arahkan ke dalam file yang sebelumnya sudah kita buat di atas.
Dan kita juga tambahkan sebuah meta dan isinya adalah requiresAuth:true yang
artinya halaman/route ini hanya bisa diakses jika user sudah melakukan proses otentikasi.

Langkah 3 -Mengaktifkan Link Menu


Setelah berhasil membuat route untuk halaman donasi, sekarang kita lanjutkan untuk
menagaktifkan link menu untuk halaman donasi. Silahkan buka file
src/components/Footer.vue kemudian cari kode berikut ini :

<div>
<a href="#"
class="w-full focus:text-teal-500 hover:text-teal-500 justify-
center inline-block text-center">
<img width="25" height="25" class="inline-block mb-1"
src="@/assets/images/heart.png">
<span class="block text-xs">Donasi Saya</span>
</a>
</div>

Kemudian ubah menjadi seperti berikut ini :

<div>
<router-link :to="{name: 'donation.index'}"
class="w-full focus:text-teal-500 hover:text-teal-500 justify-
center inline-block text-center">
<img width="25" height="25" class="inline-block mb-1"
src="@/assets/images/heart.png">
<span class="block text-xs">Donasi Saya</span>
</router-link>
</div>

Di atas, kita mengubah yang semula menggunakan <a href="#" menjadi <router-link
:to="{name: 'donation.index'}". Link ini akan digunakan di menu bawah pada
website.

Setelah itu, kita juga akan mengaktifkan link menu di halaman dashboard, silahkan buka file
src/views/dashboard/Index.vue kemudian cari kode berikut ini :

518
<a href="#">
<div class="grid grid-cols-5 gap-4 bg-gray-300 p-3 rounded-md
shadow-sm mb-3">
<div class="col-span-5">
<i class="fa fa-heart" aria-hidden="true"></i> Donasi Saya
</div>
</div>
</a>

Kemudian ubah menjadi seperti berikut ini :

<router-link :to="{name: 'donation.index'}">


<div class="grid grid-cols-5 gap-4 bg-gray-300 p-3 rounded-md
shadow-sm mb-3">
<div class="col-span-5">
<i class="fa fa-heart" aria-hidden="true"></i> Donasi Saya
</div>
</div>
</router-link>

Sama seperti sebelumnya, kita mengubah yang semula menggunakan <a href="#"
menjadi router-link. Sekarang jika kita coba klik menu Donasi Saya di dalam website,
maka kita akan mendapatkan hasil seperti berikut ini :

519
Di atas bisa kita lihat, di halaman ini sudah berhasil menampilkan tulisan HALAMAN DONASI,
yang artinya route dan halaman donasi sudah berhasil dijalankan.

520
Konfigurasi Module Donation Vuex

Pada tahap ini kita semua akan belajar membuat module Vuex baru untuk mengelola data
donasi. Ini bertujuan agar kode-kode kita tidak tercampur dengan yang lainnya dan lebih
mudah untuk dikembangakn kedepannya.

Langkah 1 - Membuat Module Donation Vuex


Sekarang, silahkan buat file baru dengan nama donation.js di dalam folder
src/store/module dan masukkan kode berikut ini :

const donation = {

//set namespace true


namespaced: true,

//state
state: {
},

//mutations
mutations: {

},

//actions
actions: {

},

//getters
getters: {

export default donation

521
Di dalam module donation di atas, kita menambahkan beberapa store, yaitu state,
mutations, actions dan getters. Dan untuk namespace digunakan karena kita
menggunakan teknik module, ini bertujuan agar module kita dapat diakses langsung melalui
component.

Langkah 2 - Import Module Donation di Store Vuex


Sekarang, kita akan import dan register module donation di atas di dalam store induk,
silahkan buka file src/store/index.js kemudian ubah kode-nya menjadi seperti berikut
ini :

522
//import vuex
import { createStore } from 'vuex'

//import module auth


import auth from './module/auth'

//import module donation


import donation from './module/donation'

//create store vuex


export default createStore({

modules: {
auth, // <-- module auth
donation, // <-- module donation
}

})

Di atas, pertama kita import file donation.js dari folder module.

//import module donation


import donation from './module/donation'

Kemudian, kita register module tersebut di dalam store Vuex modules.

modules: {
auth, // <-- module auth
donation, // <-- module donation
}

523
524
Menampilkan Data Donation dari Donatur

Pada tahap kali ini kita akan belajar bagaimana cara menampilkan data donasi yang sudah
pernah dilakukan oleh user/donatur. Disini kita juga akan membuat fitur loadmore untuk
membatasi jumlah data yang akan ditampilkan perhalaman.

Langkah 1 - View/Component Donation


Pertama, silahkan buka file src/views/donation/Index.vue kemudian ubah semua
kode-nya menjadi seperti berikut ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">

<div class="bg-white rounded-md shadow-md p-5">

<div class="text-xl">
RIWAYAT DONASI SAYA
</div>
<div class="border-2 border-gray-200 mt-3 mb-2"></div>

<div v-if="donations.length > 0">


<div class="mt-5 grid grid-cols-4 gap-4">

<div class="col-span-4" v-for="donation in


donations" :key="donation.id">
<div class="bg-gray-200 rounded-md shadow-sm
p-2">
<figure class="md:flex rounded-xl
md:p-0">
<img class="w-full h-34 md:w-48
rounded mx-auto object-cover"
:src="donation.campaign.image"
alt="" width="384" height="512">
<div class="w-full pt-6 p-5 md:p-3
text-center md:text-left space-y-4">
<router-link :to="{name:
'campaign.show', params:{slug: donation.campaign.slug}}">
<p class="text-sm font-
semibold">

525
{{
donation.campaign.title }}
</p>
</router-link>
<figcaption class="font-medium">
<p class="text-xs text-
gray-500 mt-5">
<span class="font-bold
text-gray-500 mr-3">{{ donation.created_at }}</span>
<span class="font-bold
text-blue-900">Rp. {{ formatPrice(donation.amount) }}</span>
</p>
</figcaption>
<div v-if="donation.status ==
'pending'">
<button class="w-full bg-
yellow-600 rounded shadow-sm text-xs py-1 px-2 focus:outline-none">BAYAR
SEKARANG</button>
</div>
</div>
<div class="ml-auto text-sm text-
gray-500 underline">
<div v-if="donation.status ==
'success'">
<button class="bg-green-500
border-2 border-green-500 rounded shadow-sm text-xs py-1 px-2 text-black
focus:outline-none">Berhasil</button>
</div>
<div v-else-if="donation.status
== 'pending'">
<button class="bg-yellow-500
border-2 border-yellow-500 rounded shadow-sm text-xs py-1 px-2 text-
black focus:outline-none">Pending</button>
</div>
<div v-else-if="donation.status
== 'expired'">
<button class="bg-red-500
border-2 border-red-500 rounded shadow-sm text-xs py-1 px-2 text-black
focus:outline-none">Dibatalkan</button>
</div>
<div v-else-if="donation.status
== 'failed'">
<button class="bg-red-500
border-2 border-red-500 rounded shadow-sm text-xs py-1 px-2 text-black
focus:outline-none">Dibatalkan</button>
</div>

526
</div>
</figure>
</div>
</div>

</div>

<div class="text-center mt-7" v-show="nextExists">


<a @click="loadMore"
class="bg-gray-700 text-white p-1 px-3
rounded-md shadow-md focus:outline-none focus:bg-gray-900 cursor-
pointer">LIHAT
SEMUA <i class="fa fa-long-arrow-alt-
right"></i></a>
</div>

</div>
<div v-else>

<div class="mb-3 bg-red-500 text-white p-4 rounded-


md">
Anda Belum Memiliki Transaksi Donasi Saat ini!
</div>

</div>
</div>

</div>
</div>
</template>

<script>

//hook vue
import { computed, onMounted } from 'vue'

//hook vuex
import { useStore } from 'vuex'

export default {

setup() {

//store vuex
const store = useStore()

527
//onMounted akan menjalankan action "getDonation" di module
"donation"
onMounted(() => {
store.dispatch('donation/getDonation')
})

//digunakan untuk get data state "donations" di module


"donation"
const donations = computed(() => {
return store.state.donation.donations
})

//digunakan untuk get data state "nextExists" di module


"donation"
const nextExists = computed(() => {
return store.state.donation.nextExists
})

//digunakan untuk get data state "nextPage" di module


"donation"
const nextPage = computed(() => {
return store.state.donation.nextPage
})

//loadMore function
function loadMore() {
store.dispatch('donation/getLoadmore', nextPage.value)
}

return {
donations, // <-- return donations
nextExists, // <-- return nextExists
nextPage, // <-- return nextPage
loadMore, // <-- return loadMore
}

}
</script>

<style>

</style>

528
Dari perubahan view/component donation di atas, pertama kita melakukan import
computed dan hook onMounted dari Vue.

//hook vue
import { computed, onMounted } from 'vue

Selanjutnya, kita juga import hook yang bernama useStore dari Vuex. Ini digunakan agar
mempermudah dalam pemanggilan Vuex di dalam Composition API.

//hook vuex
import { useStore } from 'vuex'

Kemudian untuk mendefinisikan sebuah Composition API, kita menggunakan function


yang bernama setup.

setup() {

//...

Di dalamnya, pertama kita buat state baru dengan nama store yang isinya merupakan
hook yang bernama useStore dari Vuex.

//store vuex
const store = useStore()

Dan saat proses mounted component, kita melakukan dispatch ke dalam sebuah action
yang bernama getDonation yang berada di dalam module donation Vuex.

//onMounted akan menjalankan action "getDonation" di module "donation"


onMounted(() => {
store.dispatch('donation/getDonation')
})

529
Selanjutnya kita membuat sebuah state dengan nama donations dan state ini
menggunakan jenis computed. Dimana di dalamnya akan mengambil sebuah data donasi
dari state yang bernama donations di dalam module donation Vuex.

//digunakan untuk get data state "donations" di module "donation"


const donations = computed(() => {
return store.state.donation.donations
})

Kemudian kita buat 2 state lagi dengan jenis computed untuk mendapatkan data
nextExists dan juga nextPage dari sebuah state yang bernama nextExists dan
nextPage di dalam module donation Vuex.

//digunakan untuk get data state "nextExists" di module "donation"


const nextExists = computed(() => {
return store.state.donation.nextExists
})

//digunakan untuk get data state "nextPage" di module "donation"


const nextPage = computed(() => {
return store.state.donation.nextPage
})

Dan kita juga membuat function yang bernama loadMore, function ini akan dijalankan
ketika button untuk loadmore di klik.

Dimana di dalam function ini kita melakukan dispatch ke dalam sebuah action yang
bernama getLoadMore dan di dalamnya kita parsing parameter yang kita ambil dari state
nextPage dan action ini berada di dalam module donation Vuex.

//loadMore function
function loadMore() {
store.dispatch('donation/getLoadmore', nextPage.value)
}

Dan agar semua state dan function yang sudah kita buat di dalam Composition API dapat
digunakan di dalam template, maka kita perlu melakukan return terlebih dahulu.

530
return {
donations, // <-- return donations
nextExists, // <-- return nextExists
nextPage, // <-- return nextPage
loadMore, // <-- return loadMore
}

Sekarang, kita akan lanjutkan untuk membahas disisi template, yaitu bagaimana data
ditampilkan dan bagaimana menampilkan button loadmore.

Pertama, kita membuat sebuah kondisi untuk menentukan apakah nilai dari state
donations memiliki nilai lebih dari 0, jika bernilai true, maka kita akan menampilkan data
donation menggunakan directive v-for, yang artinya datanya harus dilakukan perulangan .

<div v-if="donations.length > 0">

//kode perulangan data


<div v-else>

<div class="mb-3 bg-red-500 text-white p-4 rounded-md">


Anda Belum Memiliki Transaksi Donasi Saat ini!
</div>

</div>

Tapi, jika data dari state donations sama dengan 0 atau kurang dari 0, maka akan
menampilkan pesan Anda Belum Memiliki Transaksi Donasi Saat ini!.

kemudian untuk menampilkan button loadmore, kita buat kondisi terlebih dahulu
menggunakan directive v-show. Apakah nilai dari state nextExists bernilai true, jika IYA
maka akan menampilkan button loadmore tersebut.

<div class="text-center mt-7" v-show="nextExists">


<a @click="loadMore" class="bg-gray-700 text-white p-1 px-3 rounded-
md shadow-md focus:outline-none focus:bg-gray-900 cursor-pointer">LIHAT
SEMUA <i class="fa fa-long-arrow-alt-right"></i>
</a>
</div>

Di dalam button loadmore di atas, kita berikan event @click dan mengarah ke dalam

531
function yang bernama loadMore dimana function tersebut sudah kita buat sebelumnya di
atas.

Langkah 2 - Edit Module Donation Vuex


Sekarang kita lanjutkan untuk menambahkan beberapa kode di dalam module donation.
Silahkan buka file src/store/module/donation.js, kemudian ubah kode-nya menjadi
seperti berikut ini :

//import global API


import Api from '../../api/Api'

const donation = {

//set namespace true


namespaced: true,

//state
state: {

//donations
donations: [],
//loadmore
nextExists: false,
nextPage: 0,

},

//mutations
mutations: {

//set state donations dengan data dari response


SET_DONATIONS(state, donations) {
state.donations = donations
},

//set state nextExists


SET_NEXTEXISTS(state, nextExists) {
state.nextExists = nextExists
},

//set state nextPage


SET_NEXTPAGE(state, nextPage) {
state.nextPage = nextPage

532
},

//set state "donations" dengan data dari response loadmore


SET_LOADMORE(state, data) {
data.forEach(row => {
state.donations.push(row);
});
},

},

//actions
actions: {

//action getDonation
getDonation({ commit }) {

//get data token dan user


const token = localStorage.getItem('token')

//set axios header dengan type Authorization + Bearer token


Api.defaults.headers.common['Authorization'] = `Bearer
${token}`

//get data donations ke server


Api.get('/donation')
.then(response => {

//commit ke mutation SET_DONATIONS dengan response data


commit('SET_DONATIONS', response.data.data.data)

if (response.data.data.current_page <
response.data.data.last_page) {
//commit ke mutation SET_NEXTEXISTS dengan true
commit('SET_NEXTEXISTS', true)
//commit ke mutation SET_NEXTPAGE dengan current
page + 1
commit('SET_NEXTPAGE',
response.data.data.current_page + 1)

} else {

//commit ke mutation SET_NEXTEXISTS dengan false


commit('SET_NEXTEXISTS', false)
}

533
}).catch(error => {

//show error log dari response


console.log(error)

})
},

//action getLoadMore
getLoadMore({ commit }, nextPage) {

//get data token dan user


const token = localStorage.getItem('token')

//set axios header dengan type Authorization + Bearer token


Api.defaults.headers.common['Authorization'] = `Bearer
${token}`

//get data donations dengan parameter page ke server


Api.get(`/donation?page=${nextPage}`)
.then(response => {

//commit ke mutation SET_LOADMORE dengan response data


commit('SET_LOADMORE', response.data.data.data)

//console.log(response.data.data.data)

if (response.data.data.current_page <
response.data.data.last_page) {
//commit ke mutation SET_NEXTEXISTS dengan true
commit('SET_NEXTEXISTS', true)
//commit ke mutation SET_NEXTPAGE dengan current
page + 1
commit('SET_NEXTPAGE',
response.data.data.current_page + 1)

} else {

//commit ke mutation SET_NEXTEXISTS dengan false


commit('SET_NEXTEXISTS', false)
}

}).catch(error => {

//show error log dari response


console.log(error)

534
})
},

},

//getters
getters: {

export default donation

Dari penambahan kode di atas, pertama kita import konfigurasi global API.

//import global API


import Api from '../../api/Api'

Kemudian, kita membuat beberapa state yang nantinya akan kita gunakan untuk
menyimpan data, yaitu :

donations - akan kita gunakan untuk menyimpan object data donations yang di
dapatkan dari response API.
nextExists - akan kita gunakan untuk menyimpan kondisi true atau false,
dimana kondisi tersebut ditentukan dari lats page pagination yang di dapatkan dari
response API.
nextPage - akan digunakan untuk menyimpan nomor page untuk pagination.

Selanjutnya kita menambahkan action yang bernama getDonation, dimana di dalamnya


kita melakukan fetching ke dalam endpoint http://localhost:8000/api/donation dan jika
berhasil, maka akan masuk ke dalam callback then.

//get data donations ke server


Api.get('/donation')
.then(response => {

//...
}

535
Di dalam callback then, pertama kita melakukan commit ke dalam mutation yang
bernama SET_DONATIONS dan di dalamnya kita berikan parameter yang berupa response
data donations yang di dapatkan dari Rest API.

//commit ke mutation SET_DONATIONS dengan response data


commit('SET_DONATIONS', response.data.data.data)

Kemudian di dalam mutation SET_DONATIONS, kita melakukan assign dari data response
yang dikirim dari action ke dalam state yang bernama donations.

//set state donations dengan data dari response


SET_DONATIONS(state, donations) {
state.donations = donations // <-- assign response data ke state
"donations"
},

Setelah itu kita melakukan sebuah pengecekan kondisi untuk menentukan nilai pagination,
yaitu jika nilai dari response current_page lebih kecil dari last_page maka kita akan
melakukan 2 commit ke dalam mutation, yaitu SET_NEXTEXISTS akan kita berikan nilai
true dan SET_NEXTPAGE akan kita berikan nilai response current_page + 1.

if (response.data.data.current_page < response.data.data.last_page) {


//commit ke mutation SET_NEXTEXISTS dengan true
commit('SET_NEXTEXISTS', true)
//commit ke mutation SET_NEXTPAGE dengan current page + 1
commit('SET_NEXTPAGE', response.data.data.current_page + 1)

} else {

//commit ke mutation SET_NEXTEXISTS dengan false


commit('SET_NEXTEXISTS', false)

Dan jika kita lihat di dalam kedua mutation tersebut, kurang lebih seperti berikut ini :

536
//set state nextExists
SET_NEXTEXISTS(state, nextExists) {
state.nextExists = nextExists
},

//set state nextPage


SET_NEXTPAGE(state, nextPage) {
state.nextPage = nextPage
},

Di atas kita melakukan assign data untuk mengubah nilai dari state nextExists dan
nextPage. Yang mana isinya di dapatkan dari parameter yang dikirim action di atas.

Selanjutnya untuk action getLoadMore memiliki fungsi yang sama dengan action
getDonation dan yang membedakan adalah di dalam endpoint-nya kita tambahkan
parameter ?page=.

//action getLoadMore
getLoadMore({ commit }, nextPage) {

//...
//get data donations dengan parameter page ke server
Api.get(`/donation?page=${nextPage}`)
}

Dan jika proses fetching ke server berhasil, maka akan masuk ke dalam callback then.
Dimana di dalamnya kita melakukan commit ke dalam mutation yang bernama
SET_LOADMORE dengan parameter response data.

.then(response => {

//commit ke mutation SET_LOADMORE dengan response data


commit('SET_LOADMORE', response.data.data.data)

//...

Kemudian di dalam mutation SET_LOADMORE, kurang lebih seperti berikut ini :

537
//set state "donations" dengan data dari response loadmore
SET_LOADMORE(state, data) {
data.forEach(row => {
state.donations.push(row);
});
},

Di atas, kita akan melakukan push data yang dikirim dari action ke dalam state donations.

Langkah 3 - Uji Coba Halaman Donation


Sekarang kita coba untuk mengakses halaman http://localhost:8080/donation dan jika
berhasil maka kita akan mendapatkan tampilan seperti berikut ini :

Di atas kita mendapatkan alert/message Anda Belum Memiliki Transaksi Donasi


Saat ini!, karena kita belum memili transaksi donasi apapun saat ini.

Sekarang, silahkan logout dan buka menu Donasi Saya lagi, maka kita akan di arahkan
ke dalam halaman login, karena halaman ini hanya bisa dibuka jika user sudah melakukan
proses otentikasi.

538
Konfigurasi Router untuk Profile

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat route baru untuk
menampilkan halaman profile. Route ini bersifat private, yang artinya hanya bisa diakses
jika user tersebut sudah melakukan proses otentikasi.

Langkah 1 - Membuat View Profile


Sekarang, silahkan buat folder baru dengan nama profile di dalam folder src/views dan
di dalam folder profile silahkan buat file baru dengan nama Index.vue. Dan masukkan
kode berikut ini :

<template>
<div class="pb-20 pt-20 text-center">
HALAMAN PROFILE
</div>
</template>

<script>
export default {

}
</script>

539
Di atas kita hanya memberikan sample untuk template halaman profile, ini bertujuan hanya
untuk memastikan apakah route yang kita buat nanti sudah berjalan dengan benar.

Langkah 2 - Membuat Route Profile


Sekarang kita akan lanjutkan untuk mendefinisikan route baru untuk halaman profile.
Silahkan buka file src/router/index.js dan ubah kode-nya menjadi seperti berikut ini :

//import vue router


import { createRouter, createWebHistory } from 'vue-router'

//import store vuex


import store from '@/store'

//define a routes
const routes = [
{
path: '/register',
name: 'register',
component: () => import( /* webpackChunkName: "register" */
'@/views/auth/Register.vue')
},
{
path: '/login',
name: 'login',
component: () => import( /* webpackChunkName: "login" */
'@/views/auth/Login.vue')
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import( /* webpackChunkName: "dashboard" */
'@/views/dashboard/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/donation',
name: 'donation.index',
component: () => import( /* webpackChunkName: "donationIndex" */
'@/views/donation/Index.vue'),
meta: {

540
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/profile',
name: 'profile',
component: () => import( /* webpackChunkName: "profile" */
'@/views/profile/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
]

//create router
const router = createRouter({
history: createWebHistory(),
routes // <-- routes,
})

//define route for handle authentication


router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
//cek nilai dari getters isLoggedIn di module auth
if (store.getters['auth/isLoggedIn']) {
next()
return
}
next('/login')
} else {
next()
}
})

export default router

Di atas, kita menambahkan definisi 1 route baru yaitu kurang lebih seperti berikut ini :

541
{
path: '/profile',
name: 'profile',
component: () => import( /* webpackChunkName: "profile" */
'@/views/profile/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},

Dari definisi route di atas, kurang lebih seperti ini penjelasannya :

path - akan digunakan untuk membuat URL dari route profile.


name - adalah nama dari route, dalam contoh di atas namanya adalah profile.
Dengan menggunakan name, maka akan mempermudah kita dalam pemanggilan
route di dalam component.
component - merupakan file view/component yang akan di render jika route ini
dijalankan.
meta - merupakan properti yang bisa kita gunakan untuk banyak hal, dalam kasus ini
kita gunakan untuk memerika auth dengan object requiresAuth.

Langkah 3 -Mengaktifkan Link Menu


Setelah berhasil membuat route untuk halaman profile, sekarang kita lanjutkan untuk
menagaktifkan link menu untuk halaman profile. Silahkan buka file
src/views/dashboard/Index.vue kemudian cari kode berikut ini :

<a href="#">
<div class="grid grid-cols-5 gap-4 bg-gray-300 p-3 rounded-md
shadow-sm mb-3">
<div class="col-span-5"> <i class="fa fa-user-circle" aria-
hidden="true"></i> Profile Saya
</div>
</div>
</a>

Kemudian kita ubah menjadi seperti berikut ini :

542
<router-link :to="{name: 'profile'}">
<div class="grid grid-cols-5 gap-4 bg-gray-300 p-3 rounded-md
shadow-sm mb-3">
<div class="col-span-5"> <i class="fa fa-user-circle" aria-
hidden="true"></i> Profile Saya
</div>
</div>
</router-link>

Di atas, kita ubah yang semula <a href="#" menjadi <router-link :to="{name:
'profile'}">, yang artinya jika menu ini di klik, maka akan diarahkan kedalam route
yang bernama profile.

Sekarang, silahkan buka atau klik menu Profile Saya yang ada di halaman dashboard
dan jika berhasil maka kita akan mendapatkan tampilan seperti berikut ini :

543
Konfigurasi Module Profile Vuex

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat module Vuex baru
untuk mengelola data profile, di dalam module ini nantinya akan kita gunakan untuk
mendapatkan data profile dan melakukan update password.

Langkah 1 - Membuat Module Profile Vuex


Sekarang, silahkan buat file baru dengan nama profile.js di dalam folder
src/store/module dan masukkan kode berikut ini :

const profile = {

//set namespace true


namespaced: true,

//state
state: {
},

//mutations
mutations: {

},

//actions
actions: {

},

//getters
getters: {

export default profile

544
Di dalam module profile di atas, kita menambahkan beberapa store, yaitu state,
mutations, actions dan getters. Dan untuk namespace digunakan karena kita
menggunakan teknik module, ini bertujuan agar module kita dapat diakses langsung melalui
component.

Langkah 2 - Import Module Profile di Store Vuex


Sekarang, kita akan import dan register module profile di atas di dalam store induk,
silahkan buka file src/store/index.js kemudian ubah kode-nya menjadi seperti berikut
ini :

545
//import vuex
import { createStore } from 'vuex'

//import module auth


import auth from './module/auth'

//import module donation


import donation from './module/donation'

//import module profile


import profile from './module/profile'

//create store vuex


export default createStore({

modules: {
auth, // <-- module auth
donation, // <-- module donation
profile, // <-- module profile
}

})

Di atas, pertama kita import file dari module profile terlebih dahulu.

//import module profile


import profile from './module/profile'

Setelah itu, kita register module tersebut di dalam store Vuex.

modules: {
auth, // <-- module auth
donation, // <-- module donation
profile, // <-- module profile
}

546
547
Menampilkan Data Profile

Pada tahap kali ini kita semua akan belajar bagaimana cara menampilkan data profile
sebelum dilakukan proses update. Disini kita akan menampilkan foto profile, alamat email
dan nama.

Langkah 1 - View/Component Profile


Sekarang kita lanjutkan untuk melakukan perubahan di dalam file view/component profile.
Silahkan buka file src/views/profile/Index.vue kemudian ubah kode-nya menjadi
seperti berikut ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">

<form method="POST" enctype="multipart/form-data">

<div class="bg-white p-5 rounded-md shadow-md mb-5">


<div class="flex flex-col justify-center items-
center relative">
<div>
<img :src="profile.avatar" class="rounded-
full w-28 h-28 object-cover">
</div>
<div class="mt-4">
<input type="file" class="rounded bg-
gray-300 p-2 w-full shadow-sm">
</div>
</div>
</div>

<div class="bg-white p-3 rounded-md shadow-md">


<div class="grid grid-cols-1 gap-4">

<div class="mb-2">
<label class="mt-2">Nama Lengkap</label>
<input type="text"
class="mt-2 appearance-none w-full bg-
gray-200 rounded h-7 shadow-sm placeholder-gray-700 focus:outline-none
focus:placeholder-gray-600 focus:bg-gray-300 focus-within:text-gray-600

548
p-5"
placeholder="Nama Lengkap" v-
model="profile.name">
</div>

<div class="mb-2">
<label class="mt-2">Alamat Email</label>
<input type="email"
class="mt-2 appearance-none w-full bg-
gray-200 opacity-70 rounded h-7 shadow-sm placeholder-gray-600
focus:outline-none focus:placeholder-gray-600 focus:bg-gray-300 focus-
within:text-gray-600 p-5"
v-model="profile.email" placeholder="Alamat
Email" disabled>
</div>

<div>
<button type="submit" class="mt-3 bg-
gray-700 text-white p-2 w-full rounded-md shadow-md focus:outline-none">
UPDATE PROFILE
</button>
</div>

</div>
</div>

</form>

</div>
</div>
</template>

<script>

//hook vue
import { computed, onMounted } from 'vue'
//hook vuex
import { useStore } from 'vuex'

export default {

setup() {

//store vuex
const store = useStore()

549
//onMounted akan menjalankan action getProfile di module
profile
onMounted(() => {
store.dispatch('profile/getProfile')
})

//profile
const profile = computed(() => {
return store.state.profile.profile
})

return {
profile, // <-- state profile
}

}
</script>

<style>

</style>

Di atas, pertama kita import hook yang bernama onMounted dan properti computed dari
Vue.

//hook vue
import { computed, onMounted } from 'vue'

Kemudian kita juga import hook yang bernama useStore dari Vuex. Ini digunakan agar
kita dapat memanggil store Vuex di dalam Composition API.

//hook vuex
import { useStore } from 'vuex'

Dan untuk mendefinisikan sebuah Composition API, maka kita membuat function yang
bernama setup dan kode yang akan kita tulis akan kita masukkan di dalam function
tersebut.

550
setup() {

//...
}

Di dalam function setup, pertama kita buat state yang bernama store dan di dalanya
berisi hook dari Vuex.

//store vuex
const store = useStore()

Dan saat component dalam proses mounted, maka kita akan menjalankan hook yang
bernama onMounted di dalamnya akan melakukan dispatch ke dalam sebuah action yang
bernama getProfile di dalam module profile Vuex.

//onMounted akan menjalankan action "getProfile" di module "profile"


onMounted(() => {
store.dispatch('profile/getProfile')
})

Selanjutnya kita buat state baru dengan nama profile dan state ini menggunakan jenis
properti computed. Dimana isinya akan mengambil sebuah nilai state yang bernama
profile di dalam module profile Vuex.

//profile
const profile = computed(() => {
return store.state.profile.profile
})

Agar state di atas dapat digunakan di dalam template, maka kita harus melakukan return
terlebih dahulu. Kurang lebih seperti berikut ini :

551
return {
profile, // <-- state profile
}

Dan di dalam template, untuk memanggil data, kurang lebih contohnya seperti berikut ini :

<img :src="profile.avatar" class="rounded-full w-28 h-28 object-cover">

Di atas, kita panggil nama state-nya dulu, yaitu profile kemudian disambung dengan
notas (.) dan dipanggil nama key-nya, yaitu avatar. Maka di atas akan menghasilkan foto
profile dari user/donatur.

Langkah 2 - Edit Module Profile Vuex


Sekarang kita akan menambahkan beberapa kode di dalam module profile di Vuex.
Silahkan buka file src/store/module/profile.js dan ubah kode-nya menjadi seperti
berikut ini :

//import global API


import Api from '../../api/Api'

const profile = {

//set namespace true


namespaced: true,

//state
state: {
//profile state
profile: {},

},

//mutations
mutations: {

//set state profile dengan data dari response


SET_PROFILE(state, data) {
state.profile = data
},

552
},

//actions
actions: {

//action getProfile
getProfile({ commit }) {

//get data token dan user


const token = localStorage.getItem('token')

//set axios header dengan type Authorization + Bearer token


Api.defaults.headers.common['Authorization'] = `Bearer
${token}`

//get data profile ke server


Api.get('/profile')
.then(response => {

//commit ke mutation SET_PROFILE dengan response data


commit('SET_PROFILE', response.data.data)

}).catch(error => {

//show error log dari response


console.log(error)

})
},

},

//getters
getters: {

export default profile

Di atas, pertama kita import konfigurasi global API, yang mana isinya adalah endpoint API
dari Laravel.

553
//import global API
import Api from '../../api/Api'

Selanjutnya, di dalam state, kita tambahkan 1 definisi state baru, yaitu :

//profile state
profile: {},

State di atas akan kita gunakan untuk menyimpan informasi data user yang di dapatkan dari
Rest API.

Kemudian pada bagian action, kita menambahkan 1 action baru dengan nama getProfile
dan di dalamnya melakukan fetching ke dalam API Laravel dengan endpoint
http://localhost:8000/api/profile.

//action getProfile
getProfile({ commit }) {

//...
//get data profile ke server
Api.get('/profile')

Selanjutnya, jika proses fetching data ke dalam Rest API berhasil, maka akan masuka ke
dalam callback then. Dan di dalamnya kita melakukan commit ke dalam mutation yang
bernama SET_PROFILE dan di dalamnya kita berikan parameter berupa data response
yang kita dapatkan dari Rest API.

.then(response => {

//commit ke mutation SET_PROFILE dengan response data


commit('SET_PROFILE', response.data.data)

})

Dan di dalam mutation SET_PROFILE kita melakukan assign data response yang dikirim
dari action ke dalam state yang bernama profile.

554
//set state profile dengan data dari response
SET_PROFILE(state, data) {
state.profile = data
},

Langkah 3 - Uji Coba Halaman Profile


Sekarang kita akan mencoba membuka halaman profile di link http://localhost:8080/profile
atau bisa dengan klik menu Profile Saya dan jika berhasil maka kurang lebih hasilnya
seperti berikut ini :

555
Update Profile Donatur

Pada tahap kali ini kita semua akan belajar bagaimana cara melakukan update data profile,
dimana data yang kita update hanya 2 attribute saja, yaitu foto profile dan nama. Dan disini
untuk foto profile akan kita set menjadi opsional, yang artinya tidak wajib diganti jika ingin
melakukan perubahan data profile.

Langkah 1 - View/Component Profile


Sekarang kita akan menambahkan beberapa kode di dalam view/component profile, disini
kita akan membuat sebuah function untuk proses update data profile.

Silahkan buka file src/views/profile/Index.vue, kemudian ubah semua kode-nya


menjadi seperti berikut ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">

<form @submit.prevent="updateProfile" method="POST"


enctype="multipart/form-data">

<div class="bg-white p-5 rounded-md shadow-md mb-5">


<div class="flex flex-col justify-center items-
center relative">
<div>
<img :src="profile.avatar" class="rounded-
full w-28 h-28 object-cover">
</div>
<div class="mt-4">
<input type="file" @change="onFileChange"
class="rounded bg-gray-300 p-2 w-full shadow-sm">
</div>
</div>
</div>

<div class="bg-white p-3 rounded-md shadow-md">


<div class="grid grid-cols-1 gap-4">

<div class="mb-2">
<label class="mt-2">Nama Lengkap</label>

556
<input type="text"
class="mt-2 appearance-none w-full bg-
gray-200 rounded h-7 shadow-sm placeholder-gray-700 focus:outline-none
focus:placeholder-gray-600 focus:bg-gray-300 focus-within:text-gray-600
p-5"
placeholder="Nama Lengkap" v-
model="profile.name">
</div>

<div class="mb-2">
<label class="mt-2">Alamat Email</label>
<input type="email"
class="mt-2 appearance-none w-full bg-
gray-200 opacity-70 rounded h-7 shadow-sm placeholder-gray-600
focus:outline-none focus:placeholder-gray-600 focus:bg-gray-300 focus-
within:text-gray-600 p-5"
v-model="profile.email" placeholder="Alamat
Email" disabled>
</div>

<div>
<button type="submit" class="mt-3 bg-
gray-700 text-white p-2 w-full rounded-md shadow-md focus:outline-none">
UPDATE PROFILE
</button>
</div>

</div>
</div>

</form>

</div>
</div>
</template>

<script>

//hook vue
import { computed, onMounted, ref } from 'vue'
//hook vuex
import { useStore } from 'vuex'

//hook vue router


import { useRouter } from 'vue-router'
//hook Toast

557
import { useToast } from "vue-toastification"

export default {

setup() {

//store vuex
const store = useStore()

//route
const router = useRouter()

//same interface as this.$toast


const toast = useToast()

//onMounted akan menjalankan action getProfile di module


profile
onMounted(() => {
store.dispatch('profile/getProfile')
})

//profile
const profile = computed(() => {
return store.state.profile.profile
})

//state for image avatar


const imageAvatar = ref(null)

//validation state
const validation = ref([])

//get file avatar onChange


function onFileChange(e){
//get image
imageAvatar.value = e.target.files[0]

//check fileType
if (!imageAvatar.value.type.match('image.*')) {

//if fileType not allowed, then clear value and set


null
e.target.value = ''
imageAvatar.value = null

//show toastr error

558
toast.error("Extensi File Tidak Diizinkan!")

//method update profile


function updateProfile() {

//formdata
let formData = new FormData();

formData.append('avatar', imageAvatar.value)
formData.append('name', profile.value.name)

//panggil actions "updateProfile" dari module "profile"


store.dispatch('profile/updateProfile', formData)
.then(() => {

router.push({name: 'dashboard'})

toast.success("Profile Berhasil Diupdate!")

//set imageAvatar to null


imageAvatar.value = null

}).catch(error => {
//assign validaation message
validation.value = error

//show validation name with toast


if(validation.value.name) {
toast.error(`${validation.value.name[0]}`)
}
})

return {
profile, // <-- state profile
toast, // <-- hook Toast
validation, // <-- state validation
onFileChange, // <-- method onFileChange
updateProfile, // <-- method updateProfile
}

559
}

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita import 2 hook baru, yaitu useRouter dari Vue
Router dan useToast dari Toastification.

//hook vue router


import { useRouter } from 'vue-router'
//hook Toast
import { useToast } from "vue-toastification"

Selanjutnya, kita juga menambahkan 2 state baru untuk menyimpan kedua hook di atas di
dalam Composition API.

//route
const router = useRouter()

//same interface as this.$toast


const toast = useToast()

Kemudian kita membuat state lagi yang bernama imageAvatar dan state ini menggunakan
Reactivity API dengan jenis ref. State ini akan kita gunakan untuk menampung file foto
yang diupload user.

//state for image avatar


const imageAvatar = ref(null)

CATATAN:
Jika menggunakan Reactivity API ref di dalam function setup, maka untuk set dan get
data harus menggunakan single object .value.
Tapi untuk menampilkan di template kita tidak perlu menggunakan single object .value.

560
Dan kita buat state lagi yang bernama validation dan state ini juga sama, yaitu
menggunakan Reactivity API dengan jenis ref. State ini akan kita gunakan untuk
menyimpan response validasi yang di dapatkan dari Rest API Laravel.

//validation state
const validation = ref([])

Kemudian kita membuat sebuah function yang bernama onFileChange, function ini akan
digunakan untuk menyimpan file foto saat user melakukan select file di aplikasi.

//get file avatar onChange


function onFileChange(e){

//...

Di dalam function di atas, pertama kita lakukan assign file foto yang di dapatkan dari form
ke dalam state yang kita buat di atas, yaitu imageAvatar.

//get image
imageAvatar.value = e.target.files[0]

Karena state imageAvatar menggunakan jenis ref, maka di atas untuk meng-set atau
assign data kita tambahkan single object .value.

Selanjutnya, di dalam function onFileChange kita juga membuat sebuah kondisi untuk
memeriksa file yang akan diupload, jika file yang diupload tidak berupa image, maka nilai
state imageAvatar di set ke null dan kita menampilkan Toast/Message Extensi File
Tidak Diizinkan!.

561
//check fileType
if (!imageAvatar.value.type.match('image.*')) {

//if fileType not allowed, then clear value and set null
e.target.value = ''
imageAvatar.value = null

//show toastr error


toast.error("Extensi File Tidak Diizinkan!")

Kemudian kita membuat lagi sebuah function yang bernama updateProfile.

//method update profile


function updateProfile() {

//...
}

Dimana di dalamnya pertama kita membuat sebuah inisialisasi formData. Yang isinya kita
melakukan append ke data dengan key avatar yang isinya di ambil dari state
imageAvatar dan key name yang isinya diambil dari state profile.

//formdata
let formData = new FormData();

formData.append('avatar', imageAvatar.value)
formData.append('name', profile.value.name)

Selanjutnya, kita melakukan dipstach ke dalam action yang bernama updateProfile di


module profile Vuex. Dan di dalamnya kita berikan parameter formData yang sudah
kita inisialisasi di atas.

//panggil actions "updateProfile" dari module "profile"


store.dispatch('profile/updateProfile', formData)

562
Jika proses update data profile berhasil, maka akan masuk ke dalam callback then dan di
dalamnya akan melakukan redirect ke sebuah route yang bernama dashboard. Dan
menampilkan Toast/Message Profile Berhasil Diupdate!. Kemudian nilai dari state
imageAvatar kita set menjadi null lagi.

.then(() => {

router.push({name: 'dashboard'})

toast.success("Profile Berhasil Diupdate!")

//set imageAvatar to null


imageAvatar.value = null

})

Jika proses update data profile gagal, maka akan masuk ke dalam callback catch. Dimana
di dalamnya kita melakukan assign response error ke dalam state validation.

//assign validaation message


validation.value = error

Kemudian, kita tampilkan ke dalam sebuah Toast/Message.

//show validation name with toast


if(validation.value.name) {
toast.error(`${validation.value.name[0]}`)
}

Langkah 2 - Edit Module Profile Vuex


Sekarang kita lanjutkan untuk menambahkan 1 action baru di dalam module profile Vuex.
Silahkan buka file src/store.module/profile.js kemudian ubah kode-nya menjadi
seperti berikut ini :

//import global API


import Api from '../../api/Api'

563
const profile = {

//set namespace true


namespaced: true,

//state
state: {
//profile state
profile: {},

},

//mutations
mutations: {

//set state profile dengan data dari response


SET_PROFILE(state, data) {
state.profile = data
},

},

//actions
actions: {

//action getProfile
getProfile({ commit }) {

//get data token dan user


const token = localStorage.getItem('token')

//set axios header dengan type Authorization + Bearer token


Api.defaults.headers.common['Authorization'] = `Bearer
${token}`

//get data profile ke server


Api.get('/profile')
.then(response => {

//commit ke mutation SET_PROFILE dengan response data


commit('SET_PROFILE', response.data.data)

}).catch(error => {

//show error log dari response


console.log(error)

564
})
},

//action updateProfile
updateProfile({commit}, formData) {

return new Promise((resolve, reject) => {

//get data token dan user


const token = localStorage.getItem('token')

//set axios header dengan type Authorization + Bearer


token
Api.defaults.headers.common['Authorization'] = `Bearer
${token}`

//get data profile ke server


Api.post('/profile', formData)
.then(response => {

//commit ke mutation SET_PROFILE dengan response


data
commit('SET_PROFILE', response.data.data)

resolve(response)

}).catch(error => {

//reject ke component dengan hasil response


reject(error.response.data)

})
})

},

},

//getters
getters: {

export default profile

565
Di atas, kita menambahkan action baru yang bernama updateProfile.

//action updateProfile
updateProfile({commit}, formData) {

//...

karena kita nanti akan mengembalikan sebuah callback ke dalam component, maka kita
harus definisikan di dalam sebuah Promise yang di dalamnya ada parameter resolve dan
reject. Jika proses berhasil, maka akan mengembalikan callback berupa resolve dan jika
proses gagal, maka akan mengembalikan callback reject.

//define callback promise


return new Promise((resolve, reject) => {
//...
}

Selanjutnya, kita melakukan send data berupa formData yang dikirim dari component ke
dalam Rest API dengan endpoint http://localhost:8000/api/profile dan untuk method-nya
adalah POST.

//get data profile ke server


Api.post('/profile', formData)

Jika proses update data berhasil, maka kita akan melakukan commit ke dalam mutation
yang bernama SET_PROFILE dan isiny adalah response data yang di dapatkan dari Rest
API.

//commit ke mutation SET_PROFILE dengan response data


commit('SET_PROFILE', response.data.data)

Dan di dalam mutation SET_PROFILE kita melakukan perubahan nilai state profile
dengan hasil response data dari Rest API di atas.

566
//set state profile dengan data dari response
SET_PROFILE(state, data) {
state.profile = data
},

Agar di component bisa menerima callback dari action updateProfile, maka kita lakukan
resolve.

resolve(response)

Langkah 3 - Uji Coba Update Profile


Sekarang kita bisa melakukan uji coba update data profile, seperti mengganti nama atau
memperbaru foto, jika berhasil kurang lebih hasilnya seperti berikut ini :

567
Konfigurasi Router untuk Update Password

Pada tahap kali ini kita semua akan belajar bagaimana cara menambahkan route baru untuk
menampilkan halaman update password user. Dan tentu saja kita akan meng-set route ini
dengan jenis private, yang artinya bisa diakses jika user sudah melakukan proses otentikasi.

Langkah 1 - Membuat View/Component Update Password


Sekarang, silahkan buat file baru dengan nama Password.vue di dalam folder
src/views/profile dan masukkan kode berikut ini :

<template>
<div class="pb-20 pt-20 text-center">
HALAMAN UPDATE PASSWORD
</div>
</template>

<script>
export default {

}
</script>

568
Di atas kita memerikan sample template terlebih dahulu, ini digunakan agar kita dapat
memastikan bahwa route yang akan kita buat sudah bisa menampilkan halaman update
password.

Langkah 2 - Membuat Route Update Password


Sekarang kita akan lanjutkan untuk mendefinisikan route baru untuk halaman update
password. Silahkan buka file src/router/index.js dan ubah kode-nya menjadi seperti
berikut ini :

//import vue router


import { createRouter, createWebHistory } from 'vue-router'

//import store vuex


import store from '@/store'

//define a routes
const routes = [
{
path: '/register',
name: 'register',
component: () => import( /* webpackChunkName: "register" */
'@/views/auth/Register.vue')
},
{
path: '/login',
name: 'login',
component: () => import( /* webpackChunkName: "login" */
'@/views/auth/Login.vue')
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import( /* webpackChunkName: "dashboard" */
'@/views/dashboard/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/donation',
name: 'donation.index',
component: () => import( /* webpackChunkName: "donationIndex" */

569
'@/views/donation/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/profile',
name: 'profile',
component: () => import( /* webpackChunkName: "profile" */
'@/views/profile/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/profile/password',
name: 'profile.password',
component: () => import( /* webpackChunkName: "profilePassword"
*/ '@/views/profile/Password.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
]

//create router
const router = createRouter({
history: createWebHistory(),
routes // <-- routes,
})

//define route for handle authentication


router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
//cek nilai dari getters isLoggedIn di module auth
if (store.getters['auth/isLoggedIn']) {
next()
return
}
next('/login')
} else {
next()
}

570
})

export default router

Di atas, kita membuat definisi route baru untuk menampilkan halaman update password,
kurang lebih seperti berikut ini :

{
path: '/profile/password',
name: 'profile.password',
component: () => import( /* webpackChunkName: "profilePassword" */
'@/views/profile/Password.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},

path - akan digunakan untuk membuat URL dari route update password.
name - adalah nama dari route, dalam contoh di atas namanya adalah
profile.password. Dengan menggunakan name, maka akan mempermudah kita
dalam pemanggilan route di dalam component.
component - merupakan file view/component yang akan di render jika route ini
dijalankan.
meta - merupakan properti yang bisa kita gunakan untuk banyak hal, dalam kasus ini
kita gunakan untuk memerika auth dengan object requiresAuth.

Langkah 3 -Mengaktifkan Link Menu


Setelah berhasil membuat route untuk halaman update password, sekarang kita lanjutkan
untuk menagaktifkan link menu untuk halaman update password. Silahkan buka file
src/views/dashboard/Index.vue kemudian cari kode berikut ini :

571
<a href="#">
<div class="grid grid-cols-5 gap-4 bg-gray-300 p-3 rounded-md shadow-
sm mb-3">
<div class="col-span-5">
<i class="fa fa-key" aria-hidden="true"></i> Ubah Password
</div>
</div>
</a>

Dan ubah menjadi seperti berikut ini :

<router-link :to="{name: 'profile.password'}">


<div class="grid grid-cols-5 gap-4 bg-gray-300 p-3 rounded-md shadow-
sm mb-3">
<div class="col-span-5">
<i class="fa fa-key" aria-hidden="true"></i> Ubah Password
</div>
</div>
</router-link>

Di atas, kita ubah yang semula masih menggunakan <a href="#" menjadi <router-link
:to="{name: 'profile.password'}">, jadi kita arahkan ke dalam route yang bernama
profile.password.

Sekarang, silahkan buka atau klik menu Ubah Password yang ada di halaman dashboard
dan jika berhasil maka kita akan mendapatkan tampilan seperti berikut ini :

572
573
Update Password Donatur

Dimateri sebelumnya kita sudah belajar bagaimana cara membuat route baru untuk
menampilkan halaman update password, maka sekarang kita lanjutkan untuk membuat
fungsi dari proses update password itu sendiri.

Langkah 1 - View/Component Update Password


Sekarang kita lanjutkan untuk menambahkan beberapa kode di dalam view/component
update password. Silahkan buka file src/views/profile/Password.vue kemudian ubah
semua kode-nya menjadi seperti berikut ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">

<form @submit.prevent="updatePassword" method="POST">

<div class="bg-white p-3 rounded-md shadow-md">


<div class="grid grid-cols-1 gap-4">

<div class="mb-2">
<label class="mt-2">Password Baru</label>
<input type="password"
class="mt-2 appearance-none w-full bg-
gray-200 rounded h-7 shadow-sm placeholder-gray-700 focus:outline-none
focus:placeholder-gray-600 focus:bg-gray-300 focus-within:text-gray-600
p-5"
placeholder="Password Baru" v-
model="user.password">
</div>

<div class="mb-2">
<label class="mt-2">Konfirmasi Password
Baru</label>
<input type="password"
class="mt-2 appearance-none w-full bg-
gray-200 rounded h-7 shadow-sm placeholder-gray-700 focus:outline-none
focus:placeholder-gray-600 focus:bg-gray-300 focus-within:text-gray-600
p-5"
placeholder="Konfirmasi Password Baru"

574
v-model="user.password_confirmation">
</div>

<div>
<button type="submit" class="mt-3 bg-
gray-700 text-white p-2 w-full rounded-md shadow-md focus:outline-none">
UPDATE PASSWORD
</button>
</div>

</div>
</div>

</form>

</div>
</div>
</template>

<script>

//hook vue
import { reactive, ref } from 'vue'
//hook vuex
import { useStore } from 'vuex'

//hook vue router


import { useRouter } from 'vue-router'
//hook Toast
import { useToast } from "vue-toastification"

export default {

setup() {

//store vuex
const store = useStore()

//route
const router = useRouter()

//same interface as this.$toast


const toast = useToast()

//state user
const user = reactive({

575
password: '',
password_confirmation: ''
})

//validation state
const validation = ref([])

//method update password


function updatePassword() {

let password = user.password


let password_confirmation = user.password_confirmation

//panggil actions "updatePassword" dari module "profile"


store.dispatch('profile/updatePassword', {
password,
password_confirmation
})
.then(() => {

router.push({name: 'dashboard'})

toast.success("Password Berhasil Diupdate!")

}).catch(error => {
//assign validaation message
validation.value = error

//show validation password with toast


if(validation.value.password) {
toast.error(`${validation.value.password[0]}`)
}
})

return {
user, // <-- state password
validation, // <-- state validation
updatePassword, // <-- method updateProfile
}

576
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita import 2 hook dari Reactivity API, yaitu ref
dan reactive.

//hook vue
import { reactive, ref } from 'vue'

Selanjutnya, kita import 3 hook, yaitu useStore dari Vuex, kemudian useRouter dari Vue
Router dan useToast dari Toastification. ketiga hook tersebut kita gunakan agar library-
library di atas bisa di panggil di dalam Composition API.

//hook vuex
import { useStore } from 'vuex'

//hook vue router


import { useRouter } from 'vue-router'
//hook Toast
import { useToast } from "vue-toastification"

kemudian untuk mendefinisikan sebuah Composition API, maka kita buat function yang
bernama setup dan di dalamnya nanti kita bisa menuliskan kode aplikasi kita.

setup() {

//...
}

Di dalam function setup, pertama kita mendefinisikan 3 state, yang isinya merupakan hook
yang kita import di atas, yaitu Vuex, Vue Router dan Toastification.

577
//store vuex
const store = useStore()

//route
const router = useRouter()

//same interface as this.$toast


const toast = useToast()

Selanjutnya, kita buat state lagi dengan nama user dan state ini menggunakan Reactivity
API dengan jenis reactive, dimana di dalamnya kita mendefinisikan 2 object, yaitu
password dan password_confirmation. Kedua object tersebut akan kita gunakan untuk
menampung data yang dikirim dari form.

//state user
const user = reactive({
password: '',
password_confirmation: ''
})

Untuk menampung validasi dari server, kita membuat state baru dengan nama
validation, dimana state ini akan kita set menggunakan Reactivity API dengan jenis
ref.

//validation state
const validation = ref([])

Dan kita juga membuat function yang bernama updatePassword, dimana function ini akan
dijalankan jika form di dalam template dilakukan submit.

<form @submit.prevent="updatePassword" method="POST">

Saat form di atas di submit, maka akan memanggil function yang bernama
updatePassword.

578
//method update password
function updatePassword() {

//...
}

Di dalam function di atas, pertama kita buat 2 variable baru yaitu password dan
password_confirmation. Dimana isinya merupakan state user yang kita buat di atas.
Dan value-nya akan mengambil data dari form input.

let password = user.password


let password_confirmation = user.password_confirmation

Setelah itu, kita melakukan dispatch ke dalam sebuah action yang bernama
updatePassword di dalam module profile Vuex. Dan kita juga berikan parameter
berupa data password dan password_confirmation.

//panggil actions "updatePassword" dari module "profile"


store.dispatch('profile/updatePassword', {
password,
password_confirmation
})

Jika proses dispatch di atas berhasil, maka akan masuk ke dalam callback then. Dimana
di dalamnya akan melakukan redirect ke dalam sebuah route yang bernama dashboard
dan menampilkan Toast/Message Password Berhasil Diupdate!.

.then(() => {

router.push({name: 'dashboard'})

toast.success("Password Berhasil Diupdate!")

})

Tapi jika proses dispatch gagal, maka akan masuk ke dalam callback catch dan di
dalamnya kita assign sebuah response error ke dalam state validation.

579
//assign validaation message
validation.value = error

Kemudian kita tampilkan error response tersebut menggunakan Toast/Mesage.

//show validation password with toast


if(validation.value.password) {
toast.error(`${validation.value.password[0]}`)
}

Dan agar semua state dan function dapat digunakan di dalam Composition API, maka kita
perlu melakukan return terlebih dahulu.

return {
user, // <-- state password
validation, // <-- state validation
updatePassword, // <-- method updateProfile
}

Langkah 2 - Edit Module Profile Vuex


Sekarang kita lanjutkan untuk menambahkan 1 action baru di dalam module profile Vuex
untuk melakukan proses update password. Silahkan buka file
src/store.module/profile.js kemudian ubah kode-nya menjadi seperti berikut ini :

//import global API


import Api from '../../api/Api'

const profile = {

//set namespace true


namespaced: true,

//state
state: {
//profile state
profile: {},

580
},

//mutations
mutations: {

//set state profile dengan data dari response


SET_PROFILE(state, data) {
state.profile = data
},

},

//actions
actions: {

//action getProfile
getProfile({ commit }) {

//get data token dan user


const token = localStorage.getItem('token')

//set axios header dengan type Authorization + Bearer token


Api.defaults.headers.common['Authorization'] = `Bearer
${token}`

//get data profile ke server


Api.get('/profile')
.then(response => {

//commit ke mutation SET_PROFILE dengan response data


commit('SET_PROFILE', response.data.data)

}).catch(error => {

//show error log dari response


console.log(error)

})
},

//action updateProfile
updateProfile({commit}, formData) {

return new Promise((resolve, reject) => {

//get data token dan user

581
const token = localStorage.getItem('token')

//set axios header dengan type Authorization + Bearer


token
Api.defaults.headers.common['Authorization'] = `Bearer
${token}`

//get data profile ke server


Api.post('/profile', formData)
.then(response => {

//commit ke mutation SET_PROFILE dengan response


data
commit('SET_PROFILE', response.data.data)

resolve(response)

}).catch(error => {

//reject ke component dengan hasil response


reject(error.response.data)

})
})

},

//action updatePassword
updatePassword({commit}, user) {

return new Promise((resolve, reject) => {

//get data token dan user


const token = localStorage.getItem('token')

//set axios header dengan type Authorization + Bearer


token
Api.defaults.headers.common['Authorization'] = `Bearer
${token}`

//get data profile ke server


Api.post('/profile/password', {
password: user.password,
password_confirmation: user.password_confirmation
})
.then(response => {

582
//commit ke mutation SET_PROFILE dengan response
data
commit('SET_PROFILE', response.data.data)

resolve(response)

}).catch(error => {

//reject ke component dengan hasil response


reject(error.response.data)

})
})

},

//getters
getters: {

export default profile

Di atas, kita menambahkan 1 action yang bernama updatePassword dan di dalamnya ada
parameter user, parameter tersebut berisi data password dan password_confirmation
yang dikirim dari component.

//action updatePassword
updatePassword({commit}, user) {

//...

karena kita nanti akan mengembalikan sebuah callback ke dalam component, maka kita
harus definisikan di dalam sebuah Promise yang di dalamnya ada parameter resolve dan
reject. Jika proses berhasil, maka akan mengembalikan callback berupa resolve dan jika
proses gagal, maka akan mengembalikan callback reject.

583
//define callback promise
return new Promise((resolve, reject) => {
//...
}

Selanjutnya, kita melakukan send data ke dalam endpoint


http://localhost:8000/api/profile/password dan method yang kita gunakan adalah POST.

//get data profile ke server


Api.post('/profile/password', {
password: user.password,
password_confirmation: user.password_confirmation
})

Di atas saat melakukan send ke dalam Rest API Laravel, kita juga mengirim data yaitu
password dan password_confirmation. Dan jika berhasil maka akan masuk ke dalam
callback then dan di dalamnya kita melakukan commit ke dalam mutation yang bernama
SET_PROFILE.

.then(response => {

//commit ke mutation SET_PROFILE dengan response data


commit('SET_PROFILE', response.data.data)

resolve(response)

})

Di atas, kita juga lakukan resolve agar di dalam component dapat menerima callback dari
action ini.

Langkah 3 - Uji Coba Update Password


Sekarang kita bisa untuk melakukan uji coba update password, silahkan klik menu Ubah
Password di dalam halaman dashboard. Maka kurang lebih seperti berikut ini :

584
Dan sekarang kita coba untuk mengisi input password dan input konfirmasi password
dengan data yang berbeda. Maka kita akan mednapatkan error response seperti berikut ini :

Jika kita input data password dan konfirmasi password yang sama, maka proses update
password akan berhasil dan kita akan medapatkan response seperti berikut ini :

585
586
HALAMAN FRONTEND

587
Konfigurasi Module Slider Vuex

Pada tahap kali ini kita semua akan belajar bagaimana cara menambahkan module Vuex
baru untuk mengelola data slider. Mungkin kita akan menggunakan module ini hanya untuk
mendapaatkan data slider dari Rest API Laravel.

Langkah 1 - Membuat Module Slider Vuex


Sekarang, silahkan buat file baru dengan nama slider.js di dalam folder
src/store/module dan masukkan kode berikut ini :

const slider = {

//set namespace true


namespaced: true,

//state
state: {
},

//mutations
mutations: {

},

//actions
actions: {

},

//getters
getters: {

export default slider

588
Di dalam module slider di atas, kita menambahkan beberapa store, yaitu state,
mutations, actions dan getters. Dan untuk namespace digunakan karena kita
menggunakan teknik module, ini bertujuan agar module kita dapat diakses langsung melalui
component.

Langkah 2 - Import Module Slider di Store Vuex


Sekarang, kita akan import dan register module slider di atas di dalam store induk,
silahkan buka file src/store/index.js kemudian ubah kode-nya menjadi seperti berikut
ini :

589
//import vuex
import { createStore } from 'vuex'

//import module auth


import auth from './module/auth'

//import module donation


import donation from './module/donation'

//import module profile


import profile from './module/profile'

//import module slider


import slider from './module/slider'

//create store vuex


export default createStore({

modules: {
auth, // <-- module auth
donation, // <-- module donation
profile, // <-- module profile
slider, // <-- module slider
}

})

590
Di atas, pertama kita import module slider dari folder module terlebih dahulu.

//import module slider


import slider from './module/slider'

Setelah itu, kita register module slider tersebut di dalam store modules Vuex.

modules: {
auth, // <-- module auth
donation, // <-- module donation
profile, // <-- module profile
slider, // <-- module slider
}

591
Menampilkan Data Category

Pada tahap kali ini kita semua akan belajar bagaimana cara menampilkan data category di
dalam halaman index category. Karena sebelumnya kita sudah membuat view/component
untuk halaman ini, maka kita tinggal menambahkan kode di dalamnya saja.

Langkah 1 - View/Component Category


Silahkan buka file src/views/category/Index.vue kemudian ubah kode-nya menjadi
seperti berikut ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-5 sm:w-full
md:w-5/12">

<div v-if="categories.length > 0">


<div class="grid grid-cols-4 gap-4 md:gap-4 text-center
items-center">
<div v-for="category in categories" :key="category.id"
class="col-span-2 md:col-span-2 lg:col-span-2 bg-white rounded-md
shadow-md p-4 text-center text-xs">
<router-link :to="{name: 'category.show',
params:{slug: category.slug}}">
<div>
<img :src="category.image" width="40"
class="inline-block mb-2">
</div>
{{ category.name.toUpperCase() }}
</router-link>
</div>
</div>
</div>
<div v-else>
<div class="mt-5 grid grid-cols-4 gap-4 md:gap-4 text-
center items-center">
<div v-for="index in 4" :key="index" class="sm:col-
span-2 md:col-span-1 lg:col-span-1 bg-white rounded-md shadow-md text-
center text-xs">
<ContentLoader />
</div>
</div>

592
</div>

</div>
</div>
</template>

<script>

//hook vue
import { onMounted, computed } from 'vue'
//hook vuex
import { useStore } from 'vuex'

//content loader
import { ContentLoader } from 'vue-content-loader'

export default {

components: {
ContentLoader // <-- register content loader
},

setup() {

//store vuex
const store = useStore()

//onMounted akan menjalankan action "getCategory" di module


"category"
onMounted(() => {
store.dispatch('category/getCategory')
})

//digunakan untuk get data state "categories" di module


"category"
const categories = computed(() => {
return store.state.category.categories
})

return {
categories // <-- state categories
}
}

}
</script>

593
<style>

</style>

Dari penambahan kode di atas, pertama kita impor properti computed dan hook
onMounted dari Vue.

//hook vue
import { computed, onMounted } from 'vue'

Setelah itu, kita juga import hook yang bernama useStore dari Vuex.

//vuex
import { useStore } from 'vuex'

Kemudian kita juga import libraray yang digunakan untuk menampilkan loading block jika
data slider tidak ada, yaitu Vue Content Loader.

//content loader
import { ContentLoader } from 'vue-content-loader'

Setelah itu kita register component Vue Content Loader di atas di dalam properti
components. Ini digunakan agar kita dapat memanggil component tersebut di dalam
template.

components: {
ContentLoader // <-- register content loader
},

Setelah itu, kita mendefinisikan sebuah Composition API menggunakan function setup.

594
setup() {

//...
}

Di dalam function setup, pertama kita buat sebuah state baru dengan nama store dan
isinya merupakan hook dari Vuex, yaitu useStore.

//store vuex
const store = useStore()

Saat component mounted, maka akan melakukan dispatch atau memanggil sebuah action
yang bernama getCategory di dalam module category Vuex.

/onMounted akan menjalankan action "getCategory" di module "category"


onMounted(() => {
store.dispatch('category/getCategory')
})

Setelah itu kita buat state baru dengan nama categories dan state ini menggunaka jenis
properti computed dan di dalamnya memanggil sebuah state yang bernama categories
di dalam module category Vuex.

//digunakan untuk get data state "categories" di module "category"


const categories = computed(() => {
return store.state.category.categories
})

Agar state categories di atas dapat digunakan di dalam template, maka kita perlu
melakukan return terlebih dahulu.

return {
categories // <-- state categories
}

595
Di dalam template kita membuat kondisi untuk mengecek nilai state categories di atas.
Jika nilai di state di atas 0, maka kita akan menampilkan data menggunakan directive v-
for.

<div v-if="categories.length > 0">

//...

<div v-for="category in categories" :key="category.id"


class="col-span-2 md:col-span-2 lg:col-span-2 bg-white rounded-md
shadow-md p-4 text-center text-xs"></div>
//...

</div>

Tapi, jika nilai state categories sama dengan 0, maka akan menampilkan loading block
dari Vue Content Loader.

<ContentLoader />

Langkah 2 - Edit Module Category Vuex


Sekarang kita akan menambahkan 1 action untuk melakukan fetching data ke dalam Rest
API Laravel untuk mendapatkan data category. Silahkan buka file
src/store/module/category.js kemudian ubah semua kode-nya menjadi seperti
berikut ini :

//import global API


import Api from '../../api/Api'

const category = {

//set namespace true


namespaced: true,

//state
state: {
//index categories
categories: [],

596
},

//mutations
mutations: {

//set state categories dengan data dari response


SET_CATEGORIES(state, data) {
state.categories = data
},

},

//actions
actions: {

//action getCategoryHome
getCategoryHome({ commit }) {

//get data sliders ke server


Api.get('/categoryHome')
.then(response => {

//commit ke mutation SET_CATEGORIES dengan response data


commit('SET_CATEGORIES', response.data.data)

}).catch(error => {

//show error log dari response


console.log(error)

})
},

//action getCategory
getCategory({ commit }) {

//get data sliders ke server


Api.get('/category')
.then(response => {

//commit ke mutation SET_CATEGORIES dengan response data


commit('SET_CATEGORIES', response.data.data.data)

}).catch(error => {

//show error log dari response

597
console.log(error)

})
},

},

//getters
getters: {

export default category

Di atas kita menambahkan 1 action yang bernama getGategory dimana di dalamnya kita
melakukan fetching ke dalam Rest API Laravel dengan endpoint
http://localhost:8000/api/category dan untuk method-nya adalah GET.

//get data sliders ke server


Api.get('/category')

Jika proses fetching berhasil, maka akan masuk ke dalam callback then dan di dalamnya
kita melakukan commit ke dalam sebuah mutation yang bernama SET_CATEGORIES dan
isinya adalah response data yang di dapatkan dari Rest API Laravel.

//commit ke mutation SET_CATEGORIES dengan response data


commit('SET_CATEGORIES', response.data.data.data)

Dan di dalam mutation SET_CATEGORIES kita melakukan assign data response tersebut ke
dalam state yang bernama categories.

//set state categories dengan data dari response


SET_CATEGORIES(state, data) {
state.categories = data
},

598
Langkah 3 - Uji Coba Halaman Category
Sekarang silahkan buka halaman category melalui menu di halaman homepage atau bisa
melalui link berikut ini http://localhost:8080/category dan jika berhasil maka kurang lebih
tampilannya seperti berikut ini :

599
Menampilkan Detail Category

Pada tahap kali ini kita semua akan belajar membuat halaman detail category dan di dalam
halaman ini sebenarnya kita akan menampilkan data-data campaign sesuai dengan
category yang sedang dibuka. Jika teman-teman lihat di dalam Postman untuk response API
dari detail category, maka kita bisa melihat data-data campaign tersebut.

Langkah 1 - View/Component Detail Category


Sekarang kita akan melakukan perubahan di dalam view/component detail category.
Silahkan buka file tersebut di dalam src/views/category/Show.vue kemudian ubah
semua kode-nya menjadi seperti berikut ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-5 sm:w-full
md:w-5/12">

<div v-if="campaignCategory.length > 0">

<h3> <i class="fa fa-list-ul"></i> KATEGORI <strong>{{


category.name.toUpperCase() }}</strong></h3>

<div class="mt-5 grid grid-cols-4 gap-4" v-for="campaign


in campaignCategory" :key="campaign.id">
<div class="col-span-4">
<div class="bg-white rounded-md shadow-md p-2">
<div class="md:flex rounded-xl md:p-0">
<img class="w-full h-34 md:w-56 rounded
object-cover"
:src="campaign.image" width="384"
height="512">
<div class="pt-6 p-5 md:p-3 text-center
md:text-left space-y-4">
<a href="#">
<p class="text-sm font-
semibold">
{{ campaign.title }}
</p>
</a>
<div class="font-medium">
<div class="mt-3 text-gray-500

600
text-xs">
{{ campaign.user.name }}
</div>
<div v-
if="campaign.sum_donation.length > 0">
<div v-for="donation in
campaign.sum_donation" :key="donation">

<div class="relative
pt-1">
<div
class="overflow-hidden h-2 mb-4 text-xs flex rounded bg-blue-200">
<div
:style="{width: percentage(donation.total, campaign.target_donation) +
'%'}" class="shadow-none flex flex-col text-center whitespace-nowrap
text-white justify-center bg-blue-500"></div>
</div>
</div>

<p class="text-xs text-


gray-500">
<span class="font-
bold text-blue-400">Rp. {{ formatPrice(donation.total) }} </span>
terkumpul dari
<span class="font-
bold">Rp. {{ formatPrice(campaign.target_donation) }}</span>
</p>
</div>
</div>
<div v-else>

<div class="relative pt-1">


<div class="overflow-
hidden h-2 mb-4 text-xs flex rounded bg-blue-200">
<div :style="{width:
percentage(0, campaign.target_donation) + '%'}" class="shadow-none flex
flex-col text-center whitespace-nowrap text-white justify-center bg-
blue-500"></div>
</div>
</div>

<p class="text-xs text-


gray-500">
<span class="font-bold
text-blue-400">Rp. 0 </span> terkumpul dari
<span class="font-

601
bold">Rp. {{ formatPrice(campaign.target_donation) }}</span>
</p>
</div>
<div class="mt-3 text-xs">
<strong>{{
countDay(campaign.max_date) }}</strong> hari lagi
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-else>

<div class="mb-3 bg-red-500 text-white p-4 rounded-md">


Data Campaign Berdasarkan Kategori <strong>{{
category.name }}</strong> Belum Tersedia!
</div>

</div>

</div>
</div>
</template>

<script>

//hook vue
import { onMounted, computed } from 'vue'

//hook vuex
import { useStore } from 'vuex'

//hook vue router


import { useRoute } from 'vue-router'

export default {

setup() {

//store vuex
const store = useStore()

//const route

602
const route = useRoute()

//onMounted akan menjalankan action "getDetailCategory" di


module "category"
onMounted(() => {
store.dispatch('category/getDetailCategory',
route.params.slug)
})

//digunakan untuk get data state "category" di module


"category"
const category = computed(() => {
return store.state.category.category
})

//digunakan untuk get data campaign di satate


"campaignCategory" di module "category"
const campaignCategory = computed(() => {
return store.state.category.campaignCategory
})

return {
category, // <-- state category
campaignCategory // <-- state campaignCategory
}
}

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita impor properti computed dan hook
onMounted dari Vue.

//hook vue
import { computed, onMounted } from 'vue'

Setelah itu, kita juga import hook yang bernama useStore dari Vuex.

603
//vuex
import { useStore } from 'vuex'

Kemudian kita import hook yang bernama useRoute dari Vue Router. Hook ini akan kita
gunakan untuk menangkap parameter yang ada di dalam URL browser.

//hook vue router


import { useRoute } from 'vue-router'

Setelah itu, kita mendefinisikan sebuah Composition API menggunakan function setup.

setup() {

//...
}

Di dalam function setup, pertama kita buat sebuah state baru dengan nama store dan
isinya merupakan hook dari Vuex, yaitu useStore.

//store vuex
const store = useStore()

Selanjutnya kita buat state lagi dengan nama route dan di dalamnya kita masukkan hook
dari Vue Router yaitu useRoute.

//const route
const route = useRoute()

Saat component dalam proses mounted, maka kita akan melakukan dispatch atau
memanggil sebuah action yang bernama getDetailCategory di dalam module category
Vuex. Dimana kita juga parsing sebuah parameter berupa data slug yang di dapatkan
daari URL browser.

604
//onMounted akan menjalankan action "getDetailCategory" di module
"category"
onMounted(() => {
store.dispatch('category/getDetailCategory', route.params.slug)
})

Setelah itu, kita akan membuat 2 state baru dengan jenis properti computed, yaitu :

category
campaignCategory

Di dalam state category kita akan mengambil data berupa detail category di dalam
sebuah state yang bernama category di module category Vuex.

//digunakan untuk get data state "category" di module "category"


const category = computed(() => {
return store.state.category.category
})

Dan untuk state campaignCategory kita akan mengambil data campaign berdasarkan
data category yang dipilih. Dan kita mendapatkan data tersebut dari state yang bernama
campaignCategory yang berada di dalam module category Vuex.

//digunakan untuk get data campaign di satate "campaignCategory" di


module "category"
const campaignCategory = computed(() => {
return store.state.category.campaignCategory
})

Agar kedua state di atas dapat kita gunakan di dalam template, maka kita perlu melakukan
return terlebih daahulu. Kurang lebih seperti berikut ini :

return {
category, // <-- state category
campaignCategory // <-- state campaignCategory
}

605
Dan di dalam template kita membuat sebuah kondisi untuk mengecek nilai dari state
campaignCategory. Jika state tersebut memiliki nilai di atas 0, maka kita akan
menampilkan data campaign berdasarkan category yang dipilih.

<div v-if="campaignCategory.length > 0">


<div class="mt-5 grid grid-cols-4 gap-4" v-for="campaign in
campaignCategory" :key="campaign.id">
//...
</div>

</div>

Di dalam perulangan data campaign di atas, kita menampilkan banyak sekali data, misalnya
untuk menampilkan judul campaign, kita bisa menggunakan sintaks seperti berikut ini :

{{ campaign.title }}

Untuk gambar campaign :

<img class="w-full h-34 md:w-56 rounded object-cover"


:src="campaign.image" width="384" height="512">

Kemudian di dalamnya kita juga melakukan proses menampilkan donasi yang terkumpul
menggunakan progressbar. Kurang lebih seperti berikut ini :

<div :style="{width: percentage(donation.total,


campaign.target_donation) + '%'}" class="shadow-none flex flex-col text-
center whitespace-nowrap text-white justify-center bg-blue-500"></div>

Di atas, kita panggil helpers/mixins yang bernama percentage dan di dalamnya ada 2
parameter, yang pertama adalah total donasi yang terkumpul dan yang kedua merupakan
total dari target donasi. Dengan menggunakan helpers/mixins tersebut kita bisa
mendapatkan nilai persentasinya atau persen-nya.

kemudian untuk menampilkan jumlah donasi kita menggunakan helpers/mixins yang


bernama formatPrice dan di dalamnyaa kita parsing angka yang akan di format.
Contohnya seperti berikut ini :

606
<span class="font-bold">Rp. {{ formatPrice(campaign.target_donation)
}}</span>

Dan untuk menghitung jumlah hari secara countdown, kita juga menggunakan
helpers/mixins yang bernama countDay dan di dalamnya kita berikan parameter dari
tanggal ditentukannya donasi selesai.

<strong>{{ countDay(campaign.max_date) }}</strong> hari lagi

Helpers/mixins di atas akan menghitung jumlah hari antara tanggal sekarang dengan
tanggal yang ditentukan.

Kemudian Jika nilai state campaignCategory sama dengan 0, maka kita akan
menampilkaan sebuah message kurang lebih seperti berikut ini :

<div v-else>

<div class="mb-3 bg-red-500 text-white p-4 rounded-md">


Data Campaign Berdasarkan Kategori <strong>{{ category.name
}}</strong> Belum Tersedia!
</div>

</div>

Langkah 2 - Edit Module Category Vuex


Sekarang kita lanjutkan untuk menambahkan sebuah state, mutation dan action di dalam
module category Vuex. Silahkan buka file src/store/module/category.js kemudian
ubah semua kode-nya menjadi seperti berikut ini :

//import global API


import Api from '../../api/Api'

const category = {

//set namespace true


namespaced: true,

607
//state
state: {
//index categories
categories: [],

//detail category
category: {},

//campaign category
campaignCategory: []

},

//mutations
mutations: {

//set state categories dengan data dari response


SET_CATEGORIES(state, data) {
state.categories = data
},

//set state category dengan data dari response


DETAIL_CATEGORY(state, data) {
state.category = data
},

//set state campaignCategory dengan data dari response


CAMPAIGN_CATEGORY(state, data) {
state.campaignCategory = data
},

},

//actions
actions: {

//action getCategoryHome
getCategoryHome({ commit }) {

//get data category Home ke server


Api.get('/categoryHome')
.then(response => {

//commit ke mutation SET_CATEGORIES dengan response data


commit('SET_CATEGORIES', response.data.data)

608
}).catch(error => {

//show error log dari response


console.log(error)

})
},

//action getCategory
getCategory({ commit }) {

//get data category ke server


Api.get('/category')
.then(response => {

//commit ke mutation SET_CATEGORIES dengan response data


commit('SET_CATEGORIES', response.data.data.data)

}).catch(error => {

//show error log dari response


console.log(error)

})
},

//action getCategory
getDetailCategory({ commit }, slug) {

//get data detail category ke server


Api.get(`/category/${slug}`)
.then(response => {

//commit ke mutation DETAIL_CATEGORY dengan response


data
commit('DETAIL_CATEGORY', response.data.data)

//commit ke mutation CAMPAIGN_CATEGORY dengan response


data
commit('CAMPAIGN_CATEGORY',
response.data.data.campaigns)

}).catch(error => {

//show error log dari response


console.log(error)

609
})
},

},

//getters
getters: {

export default category

Dari penambahan kode di atas, pertama kita menambahkan 2 state baru yaitu :

//detail category
category: {},

//campaign category
campaignCategory: []

Untuk state category akan kita gunakan untuk menyimpan informasi dari detail data
category, seperti nama category, gambar, dan lain-lain.

Kemudian untuk state campaignCategory akan kita gunakan untuk menyimpan data
campaign yang sesuai dengan category yang sedang dibuka.

Setelah itu, kita menambahkan action baru dengan nama getDetailCategory dan di
dalamnya terdapat parameter slug yang dikirim melalui component.

//action getCategory
getDetailCategory({ commit }, slug) {

//....
}

Di dalam action getDetailCategory kita melakukan fetching ke dalam Rest API dengan
endpoint http://localhost:8000/category/slug. Dan untuk method-nya adalah GET.

610
slug di atas adalah nilai dinamis yang akan berubah-ubah sesuai data dengan yang dikirim
dari component.

//get data detail category ke server


Api.get(`/category/${slug}`)

Jika proses fetching data berhasil, maka akan masuk ke dalam callback then dan di
dalamnya kita melakukan commit ke dalam 2 mutation, yaitu DETAIL_CATEGORY dan
CAMPAIGN_CATEGORY.

.then(response => {

//commit ke mutation DETAIL_CATEGORY dengan response data


commit('DETAIL_CATEGORY', response.data.data)

//commit ke mutation CAMPAIGN_CATEGORY dengan response data


commit('CAMPAIGN_CATEGORY', response.data.data.campaigns)

})

Dan di dalam mutation DETAIL_CATEGORY dan CAMPAIGN_CATEGORY kita melakukan


assign data dari response Rest API ke dalam state category dan campaignCategory.

//set state category dengan data dari response


DETAIL_CATEGORY(state, data) {
state.category = data
},

//set state campaignCategory dengan data dari response


CAMPAIGN_CATEGORY(state, data) {
state.campaignCategory = data
},

Langkah 3 - Uji Coba Detail Category


Serakang silahkan klik category yang ada di dalam homepage, dan jika berhasil maka kita
akan mendapatkan data-data campaign yang sesuai dengan category tersebut.

611
612
Konfigurasi Router untuk Campaign

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat route baru untuk
menampilkan halaman campaign dan detail campaign. Dan tentu saja route ini bersifat
public, yang artinya semua orang bisa mengaksesnya tanpa perlu melakukan sebuah
otentikasi.

Langkah 1 - Membuat View/Component Campaign


Sekarang kita akan membuat view/component terlebuih dahulu sebelum membuat definisi
dari route-nya. Silahkan buat folder baru dengan nama campaign di dalam folder
src/views dan di dalam folder campaign silahkan buat 2 file baru dengan nama
Index.vue dan Show.vue.

Sekarang kita lanjutkan untuk memberikan sample template di kedua file tersebut. Ini
bertujuan untuk memastikan apakah route yang akan kita buta nanti sudah berjalan atau
belum.

Silahkan buka file src/views/campaign/Index.vue kemudian masukkan kode berikut


ini :

613
<template>
<div class="pb-20 pt-20 text-center">
HALAMAN CAMPAIGN
</div>
</template>

<script>
export default {

}
</script>

Selanjutnya, silahkan buka file src/views/campaign/Show.vue dan masukkan kode


berikut ini :

<template>
<div class="pb-20 pt-20 text-center">
HALAMAN DETAIL CAMPAIGN
</div>
</template>

<script>
export default {

}
</script>

Langkah 2 - Membuat Route Campaign


ekarang kita akan lanjutkan untuk mendefinisikan route baru untuk halaman campaign.
Silahkan buka file src/router/index.js dan ubah kode-nya menjadi seperti berikut ini :

//import vue router


import { createRouter, createWebHistory } from 'vue-router'

//import store vuex


import store from '@/store'

//define a routes
const routes = [

614
{
path: '/register',
name: 'register',
component: () => import( /* webpackChunkName: "register" */
'@/views/auth/Register.vue')
},
{
path: '/login',
name: 'login',
component: () => import( /* webpackChunkName: "login" */
'@/views/auth/Login.vue')
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import( /* webpackChunkName: "dashboard" */
'@/views/dashboard/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/donation',
name: 'donation.index',
component: () => import( /* webpackChunkName: "donationIndex" */
'@/views/donation/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/profile',
name: 'profile',
component: () => import( /* webpackChunkName: "profile" */
'@/views/profile/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/profile/password',
name: 'profile.password',
component: () => import( /* webpackChunkName: "profilePassword"

615
*/ '@/views/profile/Password.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/',
name: 'home',
component: () => import( /* webpackChunkName: "home" */
'@/views/home/Index.vue')
},
{
path: '/category',
name: 'category.index',
component: () => import( /* webpackChunkName: "categoryIndex" */
'@/views/category/Index.vue')
},
{
path: '/category/:slug',
name: 'category.show',
component: () => import( /* webpackChunkName: "categoryShow" */
'@/views/category/Show.vue')
},
{
path: '/campaign',
name: 'campaign.index',
component: () => import( /* webpackChunkName: "campaignIndex" */
'@/views/campaign/Index.vue')
},
{
path: '/campaign/:slug',
name: 'campaign.show',
component: () => import( /* webpackChunkName: "campaignShow" */
'@/views/campaign/Show.vue')
},
]

//create router
const router = createRouter({
history: createWebHistory(),
routes // <-- routes,
})

//define route for handle authentication


router.beforeEach((to, from, next) => {

616
if (to.matched.some(record => record.meta.requiresAuth)) {
//cek nilai dari getters isLoggedIn di module auth
if (store.getters['auth/isLoggedIn']) {
next()
return
}
next('/login')
} else {
next()
}
})

export default router

Dari penambahan kode di atas, kita mendefinisikan 2 route baru, yaitu untuk menampilkan
index campaign dan untuk menampilkan detail campaign.

{
path: '/campaign',
name: 'campaign.index',
component: () => import( /* webpackChunkName: "campaignIndex" */
'@/views/campaign/Index.vue')
},
{
path: '/campaign/:slug',
name: 'campaign.show',
component: () => import( /* webpackChunkName: "campaignShow" */
'@/views/campaign/Show.vue')
},

Langkah 3 - Mengaktifkan Link Campaign


Setelah berhasil membuat route untuk halaman campaign, maka sekarang kita akan
mengaktifkan atau memanggil route tersebut di dalam component.

Pertama, kita akan mengaktifkan link campaign tersebut di dalam component Footer.
Silahkan buka file src/components/Footer.vue kemudian cari kode berikut ini :

617
<div>
<a href="#"
class="w-full focus:text-teal-500 hover:text-teal-500 justify-
center inline-block text-center">
<img width="25" height="25" class="inline-block mb-1"
src="@/assets/images/flag.png">
<span class="tab tab-kategori block text-xs">Campaign</span>
</a>
</div>

Setelah itu, silahkan ubah kode-nya menjadi seperti berikut ini :

<div>
<router-link :to="{name: 'campaign.index'}"
class="w-full focus:text-teal-500 hover:text-teal-500 justify-
center inline-block text-center">
<img width="25" height="25" class="inline-block mb-1"
src="@/assets/images/flag.png">
<span class="tab tab-kategori block text-xs">Campaign</span>
/router-link>
</div>

Di atas, yang semula masih menggunakan a href="#" kita ubah menjadi router-link
dan kita arahkan ke dalam sebuah route yang bernama campaign.index.

Kedua, kita akan mengaktifkan link untuk detail campaign di dalam halaman homepage.
SIlahkan buka file src/views/home/Index.vue kemudian cari kode berikut ini :

<a href="#">
<p class="text-sm font-semibold">
{{ campaign.title }}
</p>
</a>

Kemudian kita ubah menjadi seperti berikut ini :

618
<router-link :to="{name: 'campaign.show', params:{slug: campaign.slug
}}">
<p class="text-sm font-semibold">
{{ campaign.title }}
</p>
</router-link>

Di atas kita ubah yang semula menggunakan a href=# ke dalam router-link dan kita
arahkan ke dalam route yang bernama campaign.show dan kita juga berikan parameter
data slug dari campaign tersebut.

Terakhir, sama seperti langkah di atas, kita akan mengaktifkan link detail campaign di
dalam component detail category. Silahkan buka file src/views/category/Show.vue
kemudian cari kode berikut ini :

<a href="#">
<p class="text-sm font-semibold">
{{ campaign.title }}
</p>
</a>

Dan ubah menjadi seperti berikut ini :

<router-link :to="{name: 'campaign.show', params:{slug: campaign.slug


}}">
<p class="text-sm font-semibold">
{{ campaign.title }}
</p>
</router-link>

Langkah 4 - Uji Coba Halaman Campaign


Sekarang silahkan klik menu Campaign dan jika berhasil maka kita akan menampilkan
halaman index dari campaign. Kurang lebih seperti berikut ini :

619
Dan sekarang kita coba juga untuk halaman detail campaign, silahkan klik salah satu data
campaign yang ada di halaman homepage dan jika berhasil maka kurang lebih hasilnya
seperti berikut ini :

620
Menampilkan Data Campaign

Pada tahap kali ini kita semua akan belajar bagaimana cara menampilkan data campaign di
halaman index. Ini sama seperti kita menampilkan data campaign di halaman homapage
sebelumnya. Dan kali ini kita hanya akan melakukan penambahan kode disisi
view/component, karena untuk module Vuex-nya sudah kita buat saat kita menampilkan
data campaign di halaman homepage.

Langkah 1 - View/Component Campaign


Silahkan buka file src/views/campaign/Index.vue kemudian ubah kode-nya menjadi
seperti berikut ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">

<div v-if="campaigns.length > 0">


<div class="mt-5 grid grid-cols-4 gap-4" v-for="campaign
in campaigns" :key="campaign.id">
<div class="col-span-4">
<div class="bg-white rounded-md shadow-md p-2">
<div class="md:flex rounded-xl md:p-0">
<img class="w-full h-34 md:w-56 rounded
object-cover"
:src="campaign.image" width="384"
height="512">
<div class="w-full pt-6 p-5 md:p-3 text-
center md:text-left space-y-4">
<router-link :to="{name:
'campaign.show', params:{slug: campaign.slug }}">
<p class="text-sm font-
semibold">
{{ campaign.title }}
</p>
</router-link>
<div class="font-medium">
<div class="mt-3 text-gray-500
text-xs">
{{ campaign.user.name }}
</div>

621
<div v-
if="campaign.sum_donation.length > 0">
<div v-for="donation in
campaign.sum_donation" :key="donation">

<div class="relative
pt-1">
<div
class="overflow-hidden h-2 mb-4 text-xs flex rounded bg-blue-200">
<div
:style="{width: percentage(donation.total, campaign.target_donation) +
'%'}" class="shadow-none flex flex-col text-center whitespace-nowrap
text-white justify-center bg-blue-500"></div>
</div>
</div>

<p class="text-xs text-


gray-500">
<span class="font-
bold text-blue-400">Rp. {{ formatPrice(donation.total) }} </span>
terkumpul dari
<span class="font-
bold">Rp. {{ formatPrice(campaign.target_donation) }}</span>
</p>
</div>
</div>
<div v-else>

<div class="relative pt-1">


<div class="overflow-
hidden h-2 mb-4 text-xs flex rounded bg-blue-200">
<div :style="{width:
percentage(0, campaign.target_donation) + '%'}" class="shadow-none flex
flex-col text-center whitespace-nowrap text-white justify-center bg-
blue-500"></div>
</div>
</div>

<p class="text-xs text-


gray-500">
<span class="font-bold
text-blue-400">Rp. 0 </span> terkumpul dari
<span class="font-
bold">Rp. {{ formatPrice(campaign.target_donation) }}</span>
</p>
</div>

622
<div class="mt-3 text-xs">
<strong>{{
countDay(campaign.max_date) }}</strong> hari lagi
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-else>

<div v-for="index in 2" :key="index" class="grid grid-


cols-1 bg-white rounded shadow-md p-3 text-sm mt-4 mb-4">
<FacebookLoader class="h-24"/>
</div>

</div>

</div>

</div>
</template>

<script>

//hook vue
import { computed, onMounted } from 'vue'

//vuex
import { useStore } from 'vuex'

//vue content loader


import { FacebookLoader } from 'vue-content-loader'

export default {

components: {
FacebookLoader // <-- register component FacebooLoader dari
Vue Content Loader
},

setup() {

//store vuex

623
const store = useStore()

//onMounted akan menjalankan action "getCampaign" di module


"campaign"
onMounted(() => {
store.dispatch('campaign/getCampaign')
})

//digunakan untuk get data state "campaigns" di module


"campaign"
const campaigns = computed(() => {
return store.state.campaign.campaigns
})

return {
campaigns, // <-- return campaigns
}

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita import properti computed dan hook
onMounted.

//hook vue
import { computed, onMounted } from 'vue'

Kemudian kita import juga hook yang bernama useStore dari Vuex. Hook ini akan
mempermudah kita dalam pemanggilan store Vuex di dalam Composition API.

//vuex
import { useStore } from 'vuex'

Kemudian kita juga import libraray yang digunakan untuk menampilkan loading block jika

624
data slider tidak ada, yaitu Vue Content Loader. Dan jenis component yang akan kita
gunakan adalah FacebookLoader.

/content loader
import { FacebookLoader } from 'vue-content-loader'

Setelah itu kita register component FacebookLoader dari Vue Content Loader di dalam
properti components.

components: {
FacebookLoader // <-- register component FacebooLoader dari Vue
Content Loader
},

Setelah itu, kita mendefinisikan sebuah Composition API menggunakan function setup.

setup() {

//...
}

Di dalam function setup, pertama kita buat sebuah state baru dengan nama store dan
isinya merupakan hook dari Vuex, yaitu useStore.

//store vuex
const store = useStore()

Saat component dalam proses mounted, maka di dalamnya akan melakukan dispatch
atau memanggil sebuah action yang bernama getCampaign yang berada di dalam module
campaign Vuex.

625
//onMounted akan menjalankan action "getCampaign" di module "campaign"
onMounted(() => {
store.dispatch('campaign/getCampaign')
})

Setelah itu, kita membuat state baru dengan nama campaigns dan state ini menggunakan
jenis proeperti computed. Dan di dalamnya kita melakukan get untuk mendapatkan data
dari state yang bernama campaigns yang berada di dalam module campaign Vuex.

//digunakan untuk get data state "campaigns" di module "campaign"


const campaigns = computed(() => {
return store.state.campaign.campaigns
})

Agar state campaigns di atas dapat kita gunakan di dalam template, maka kita perlu
melakukan return terlebih dahulu. Kurang lebih seperti berikut ini :

return {
campaigns, // <-- return campaigns
}

Kemudian kita lanjutkan dibagian template untuk menampilkan data campaign. Pertama
kita melakuakn sebuah kondisi untuk mengecek nilai dari state campaigns.

Jika state campaigns memiliki nilai di atas 0, maka akan melakukan perulangan data
campaign menggunakan directive v-for.

<div v-if="campaigns.length > 0">


<div class="mt-5 grid grid-cols-4 gap-4" v-for="campaign in
campaigns" :key="campaign.id">
//...
</div>
</div>

Di dalam perulangan data di atas, kita menampilkan banyak sekali data, misalnya untuk

626
menampilkan judul campaign, kita bisa menggunakan sintaks seperti berikut ini :

{{ campaign.title }}

Untuk gambar campaign :

<img class="w-full h-34 md:w-56 rounded object-cover"


:src="campaign.image" width="384" height="512">

Kemudian di dalamnya kita juga melakukan proses menampilkan donasi yang terkumpul
menggunakan progressbar. Kurang lebih seperti berikut ini :

<div :style="{width: percentage(donation.total,


campaign.target_donation) + '%'}" class="shadow-none flex flex-col text-
center whitespace-nowrap text-white justify-center bg-blue-500"></div>

Di atas, kita panggil helpers/mixins yang bernama percentage dan di dalamnya ada 2
parameter, yang pertama adalah total donasi yang terkumpul dan yang kedua merupakan
total dari target donasi. Dengan menggunakan helpers/mixins tersebut kita bisa
mendapatkan nilai persentasinya atau persen-nya.

kemudian untuk menampilkan jumlah donasi kita menggunakan helpers/mixins yang


bernama formatPrice dan di dalamnyaa kita parsing angka yang akan di format.
Contohnya seperti berikut ini :

<span class="font-bold">Rp. {{ formatPrice(campaign.target_donation)


}}</span>

Dan untuk menghitung jumlah hari secara countdown, kita juga menggunakan
helpers/mixins yang bernama countDay dan di dalamnya kita berikan parameter dari
tanggal ditentukannya donasi selesai.

627
<strong>{{ countDay(campaign.max_date) }}</strong> hari lagi

Helpers/mixins di atas akan menghitung jumlah hari antara tanggal sekarang dengan
tanggal yang ditentukan.

Langkah 2 - Uji Coba Halaman Campaign


Sekarang silahkan refresh halaman campaign atau bisa buka link
http://localhost:8080/campaign, jika berhasil maka kita akan mendapatkan tampilan seperti
berikut ini :

Langkah 3 - menambahkan Fitur Loadmore


Sekarang kita lanjutkan untuk menambahkan fitur loadmore di halaman campaign. Silahkan
buka file src/views/campaign/Index.vue kemudian ubah semua kode-nya menjadi
seperti berikut ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">

<div v-if="campaigns.length > 0">

628
<div class="mt-5 grid grid-cols-4 gap-4" v-for="campaign
in campaigns" :key="campaign.id">
<div class="col-span-4">
<div class="bg-white rounded-md shadow-md p-2">
<div class="md:flex rounded-xl md:p-0">
<img class="w-full h-34 md:w-56 rounded
object-cover"
:src="campaign.image" width="384"
height="512">
<div class="w-full pt-6 p-5 md:p-3 text-
center md:text-left space-y-4">
<router-link :to="{name:
'campaign.show', params:{slug: campaign.slug }}">
<p class="text-sm font-
semibold">
{{ campaign.title }}
</p>
</router-link>
<div class="font-medium">
<div class="mt-3 text-gray-500
text-xs">
{{ campaign.user.name }}
</div>
<div v-
if="campaign.sum_donation.length > 0">
<div v-for="donation in
campaign.sum_donation" :key="donation">

<div class="relative
pt-1">
<div
class="overflow-hidden h-2 mb-4 text-xs flex rounded bg-blue-200">
<div
:style="{width: percentage(donation.total, campaign.target_donation) +
'%'}" class="shadow-none flex flex-col text-center whitespace-nowrap
text-white justify-center bg-blue-500"></div>
</div>
</div>

<p class="text-xs text-


gray-500">
<span class="font-
bold text-blue-400">Rp. {{ formatPrice(donation.total) }} </span>
terkumpul dari
<span class="font-
bold">Rp. {{ formatPrice(campaign.target_donation) }}</span>

629
</p>
</div>
</div>
<div v-else>

<div class="relative pt-1">


<div class="overflow-
hidden h-2 mb-4 text-xs flex rounded bg-blue-200">
<div :style="{width:
percentage(0, campaign.target_donation) + '%'}" class="shadow-none flex
flex-col text-center whitespace-nowrap text-white justify-center bg-
blue-500"></div>
</div>
</div>

<p class="text-xs text-


gray-500">
<span class="font-bold
text-blue-400">Rp. 0 </span> terkumpul dari
<span class="font-
bold">Rp. {{ formatPrice(campaign.target_donation) }}</span>
</p>
</div>
<div class="mt-3 text-xs">
<strong>{{
countDay(campaign.max_date) }}</strong> hari lagi
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-else>

<div v-for="index in 2" :key="index" class="grid grid-


cols-1 bg-white rounded shadow-md p-3 text-sm mt-4 mb-4">
<FacebookLoader class="h-24"/>
</div>

</div>

</div>

<div class="text-center mt-4 mb-4" v-show="nextExists">

630
<a @click="loadMore"
class="bg-gray-700 text-white p-2 px-3 rounded-md
shadow-md focus:outline-none focus:bg-gray-900 cursor-pointer">LIHAT
SEMUA <i class="fa fa-long-arrow-alt-right"></i></a>
</div>

</div>
</template>

<script>

//hook vue
import { computed, onMounted } from 'vue'

//vuex
import { useStore } from 'vuex'

//vue content loader


import { FacebookLoader } from 'vue-content-loader'

export default {

components: {
FacebookLoader // <-- register component FacebooLoader dari
Vue Content Loader
},

setup() {

//store vuex
const store = useStore()

//onMounted akan menjalankan action "getCampaign" di module


"campaign"
onMounted(() => {
store.dispatch('campaign/getCampaign')
})

//digunakan untuk get data state "campaigns" di module


"campaign"
const campaigns = computed(() => {
return store.state.campaign.campaigns
})

//get status NextExists


const nextExists = computed(() => {

631
return store.state.campaign.nextExists
})

//get nextPage
const nextPage = computed(() => {
return store.state.campaign.nextPage
})

//loadMore function
function loadMore() {
store.dispatch('campaign/getLoadMore', nextPage.value)
}

return {
campaigns, // <-- return campaigns
nextExists, // <-- return nextExists,
nextPage, // <-- return nextPage
loadMore, // <-- return loadMore
}

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita buat 2 state baru dengan nama nextExists
dan nextPage. Kedua state tersebut mengambil data dari sebuah state yang bernama
nextExists dan nextPage yang ada di dalam module campaign Vuex.

632
//get status NextExists
const nextExists = computed(() => {
return store.state.campaign.nextExists
})

//get nextPage
const nextPage = computed(() => {
return store.state.campaign.nextPage
})

State nextExists akan berisi nilai bolean antara true dan false. State ini akan
digunakan untuk memberikan kondisi button loadmore, jika bernilai true maka button
loadmore akan ditampilkan dan begitu juga sebaliknya.

Dan untuk state nextPage akan berisi angka halaman page yang akan kita kirim ke server
untuk dijadikan sebuah parameter mendapatkan data.

Selanjutnya kita membuat 1 function baru yang bernama loadMore, function ini akan
dijalankan ketika kita menekan button loadmore yang ada di template. Di dalamnya kita
melakukan dispatch atau memanggil sebuah action yang bernama getLoadMore dan di
dalam parameternya kita berikan nilai page yang diambil dari state nextPage.

//loadMore function
function loadMore() {
store.dispatch('campaign/getLoadMore', nextPage.value)
}

Agar state dan function di atas dapat digunakan di dalam template, maka kita perlu
menambahkannya di dalam return Composition API.

return {
campaigns, // <-- return campaigns
nextExists, // <-- return nextExists,
nextPage, // <-- return nextPage
loadMore, // <-- return loadMore
}

Di dalam template kita bisa lihat, disini kita menambahkan kode seperti berikut ini untuk
menampilkan button loadmore :

633
<div class="text-center mt-4 mb-4" v-show="nextExists">
<a @click="loadMore" class="bg-gray-700 text-white p-2 px-3 rounded-
md shadow-md focus:outline-none focus:bg-gray-900 cursor-pointer">LIHAT
SEMUA <i class="fa fa-long-arrow-alt-right"></i></a>
</div>

Di atas, jika nilai nextExists adalah true. Maka button loadmore di dalamnya akan
ditampilkan dan jika kita perhatikan di dalam button loadmore, kita arahkan ke dalam
sebuah function yang bernama loadMore dan function tersebut merupakan function yang
kita buat di atas sebelumnya.

Langkah 4 - Uji Coba Fitur Loadmore


Sekarang silahkan refresh halaman campaign dan dengan catatan kita harus punya data
campaign yaang banyak agar bisa melihat fitur ini digunakan. Kurang lebih hasilnya seperti
berikut ini :

634
Menampilkan Detail Campaign

Pada tahap kali ini semua akan belajar bagaimana cara menampilkan halaman detail
campaign, di dalam halaman detail campaign kita akan mendampilkan banyak data, yaitu :

data campaign itu sendiri.


data penggalang dana atau user yang membuat campaign tersebut.
jumlah donasi
data donatur

Dari ke-empat data tersebut akan di ambil dari 1 endpoint Rest API, dimana nanti kita akan
pecah-pecah kedalam state yang berbeda di module Vuex.

Langkah 1 - View/Component Detail Campaign


Sekarang silahkan buka file src/views/campaign/Show.vue kemudian ubah semua
kode-nya menjadi seperti berikut ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">

<div class="bg-white rounded-md shadow-md p-3">


<img class="rounded-md w-full" :src="campaign.image">

<div class="mt-5">
<p class="text-lg font-semibold">
{{ campaign.title }}
</p>
</div>

<div v-if="sumDonation.length > 0">


<div v-for="donation in sumDonation"
:key="donation">

<p class="mt-4 text-base text-gray-500">


<span class="font-bold text-blue-400">Rp. {{
formatPrice(donation.total) }} </span>
terkumpul dari
<span class="font-bold">Rp. {{
formatPrice(campaign.target_donation) }}</span>
</p>

635
<div class="relative pt-1 mt-2">
<div class="overflow-hidden h-2 mb-4 text-
base flex rounded bg-blue-200">
<div :style="{width:
percentage(donation.total, campaign.target_donation) + '%'}"
class="shadow-none flex flex-col
text-center whitespace-nowrap text-white justify-center bg-blue-500">
</div>
</div>
</div>

</div>
</div>
<div v-else>

<p class="mt-4 text-base text-gray-500">


<span class="font-bold text-blue-400">Rp. 0
</span> terkumpul dari
<span class="font-bold">Rp. {{
formatPrice(campaign.target_donation) }}</span>
</p>

<div class="relative pt-1 mt-2">


<div class="overflow-hidden h-2 mb-4 text-xs
flex rounded bg-blue-200">
<div :style="{width: percentage(0,
campaign.target_donation) + '%'}"
class="shadow-none flex flex-col text-
center whitespace-nowrap text-white justify-center bg-blue-500">
</div>
</div>
</div>

</div>

<div class="mt-3">
<span class="font-bold">{{ donations.length
}}</span> Donasi
<span class="float-right"> <strong>{{
countDay(campaign.max_date) }}</strong> hari lagi</span>
</div>

<div v-if="maxDate(campaign.max_date) == true">


<div class="mt-5">
<button
class="bg-yellow-500 py-3 rounded-md shadow-

636
md text-xl w-full uppercase font-bold focus:outline-none opacity-50
cursor-not-allowed">Donasi Ditutup!</button>
</div>
</div>
<div v-else>
<div class="mt-5">
<a href="#">
<button
class="bg-yellow-500 py-3 rounded-md
shadow-md text-xl w-full uppercase font-bold focus:outline-none
focus:bg-yellow-600">Donasi
Sekarang!</button>
</a>
</div>
</div>

</div>

</div>

<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full


md:w-5/12">

<div class="bg-white rounded-md shadow-md p-3">


<div class="text-lg font-semibold">
Penggalang Dana
</div>
<div class="border-2 border-gray-200 mt-3 mb-2"></div>

<div class="bg-gray-200 p-3 rounded shadow-md mb-3">

<div class="grid grid-cols-10 gap-4">


<div class="col-span-2">
<img :src="user.avatar" class="w-15 h-15
rounded-full shadow">
</div>
<div class="col-span-8 text-lg font-bold mt-6">
{{ user.name }}
</div>
</div>
</div>

</div>

</div>

637
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">

<div class="bg-white rounded-md shadow-md p-3">


<div class="text-lg font-semibold">
Cerita
</div>
<div class="border-2 border-gray-200 mt-3 mb-2"></div>
<div v-html="campaign.description"></div>
</div>

</div>

<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full


md:w-5/12">

<div class="bg-white rounded-md shadow-md p-3">


<div class="text-lg font-semibold">
Donasi ({{ donations.length }})
</div>
<div class="border-2 border-gray-200 mt-3 mb-2"></div>

<div v-for="donation in donations" :key="donation.id"


class="bg-gray-200 p-3 rounded shadow-md mb-3">

<div class="grid grid-cols-10 gap-4">

<div class="col-span-1">
<img :src="donation.donatur.avatar"
class="w-15 h-15 rounded-full">
</div>
<div class="col-span-9 mt-1">
<div class="text-base font-bold">
{{ donation.donatur.name }}
</div>
<div class="text-sm mt-2 text-gray-500">
Berdonasi sebesar <span class="font-
bold">Rp. {{ formatPrice(donation.amount) }}</span>
</div>
</div>

</div>

<div class="grid grid-cols-1 gap-4 mt-3">


<div class="text-gray-600 text-sm">
{{ donation.pray }}

638
</div>
<div class="text-gray-500 text-sm italic text-
right">
{{ donation.created_at }}
</div>
</div>

</div>

</div>

</div>

</div>
</template>

<script>

//hook vue
import { computed, onMounted } from 'vue' // computed dan onMounted
//hook vuex
import { useStore } from 'vuex'

//hook vue router


import { useRoute } from 'vue-router'

export default {

setup() {

//vue route
const route = useRoute()

//store vuex
const store = useStore()

//onMounted akan menjalankan action "getDetailCampaign" di


module "campaign"
onMounted(() => {
store.dispatch('campaign/getDetailCampaign',
route.params.slug)
})

//digunakan untuk mendapatkan data detail campaign dari


state "campaign" di module "campaign" Vuex
const campaign = computed(() => {

639
return store.state.campaign.campaign
})

//digunakan untuk mendapatkan data detail user dari state


"user" di module "campaign" Vuex
const user = computed(() => {
return store.state.campaign.user
})

//digunakan untuk mendapatkan data jumlah donasi state


"sumDonation" di module "campaign" Vuex
const sumDonation = computed(() => {
return store.state.campaign.sumDonation
})

//digunakan untuk mendapatkan data donation dari state


"donations" di module "campaign" Vuex
const donations = computed(() => {
return store.state.campaign.donations
})

return {
campaign, // <-- campaign
user, // <-- user
sumDonation, // <-- sumDonation
donations, // <-- donations
}
}

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita melakukan import properti computed dan
hook onMounted dari Vue.

//hook vue
import { computed, onMounted } from 'vue' // computed dan onMounted

640
Setelah itu, kita import juga hook yang bernama useStore dari Vuex. Dan hook useRoute
dari Vue Router.

//hook vuex
import { useStore } from 'vuex'

//hook vue router


import { useRoute } from 'vue-router'

Setelah itu, kita mendefinisikan sebuah Composition API menggunakan function setup.

setup() {

//...
}

Di dalam function setup, pertama kita buat sebuah state baru dengan nama store dan
isinya merupakan hook dari Vuex. Dan kita juga membuat state baru dengan nama route,
dimana state ini digunakan untuk menyimpan hook dari Vue Router.

//vue route
const route = useRoute()

//store vuex
const store = useStore()

Selanjutnya saat component melakukan mounted, maka kita akan melakukan dispatch
atau memanggil sebuah action yang bernama getDetailCampaign yang berada di dalam
module campaign Vuex.

//onMounted akan menjalankan action "getDetailCampaign" di module


"campaign"
onMounted(() => {
store.dispatch('campaign/getDetailCampaign', route.params.slug)
})

Setelah itu, kita akan membuat 4 state baru yang akan kita gunakan untuk menampilkan

641
data di dalam component, dimana di dalam ke-emapat state tersebut akan meangambil nilai
dari state yang ada di dalam module campaign Vuex.

state campaign

//digunakan untuk mendapatkan data detail campaign dari state "campaign"


di module "campaign" Vuex
const campaign = computed(() => {
return store.state.campaign.campaign
})

State di atas akan kita gunakan untuk menampilkan detail data campaign, dan untuk
datanya kita mengambil dari sebuah state yang bernama campaign yang berada di dalam
module campaign.

state user

//digunakan untuk mendapatkan data detail user dari state "user" di


module "campaign" Vuex
const user = computed(() => {
return store.state.campaign.user
})

State tersebut akan kita gunakan untuk menampilkan data penggalang dana/user yang
membuat campaign. Dan kita akan mendapatkan datanya dari sebuah state yang bernama
user yang berada di dalam module campaign.

state sumDonation

//digunakan untuk mendapatkan data jumlah donasi state "sumDonation" di


module "campaign" Vuex
const sumDonation = computed(() => {
return store.state.campaign.sumDonation
})

State di atas akan kita gunakan untuk menampilkan jumlah donasi yang sudah terkumpul
berdasarkan campaign yang sedang dibuka. Dan untuk data-nya kita mengambil dari
sebuah state yaang bernama sumDonation yang berada di dalam module campaign
Vuex.

642
state donations

//digunakan untuk mendapatkan data donation dari state "donations" di


module "campaign" Vuex
const donations = computed(() => {
return store.state.campaign.donations
})

State di atas akan kita gunakan untuk menampilkan data para donatur yang sudah
melakukan donasi di dalam campaign yang sedang dibuka. Dan untuk mendapatkan data
tersebut kita mengambil dari sebuah state yang bernama donations yang berada di dalam
module campaign Vuex.

Dan agar ke-empat state tersebut dapat digunakan di dalam template, maka kita perlu
melakukan return terlebih dahulu di dalam Composition API.

return {
campaign, // <-- campaign
user, // <-- user
sumDonation, // <-- sumDonation
donations, // <-- donations
}

Dan di dalam template kita akan menampilkan beberapa data dari state yang sudah kita
buat di atas. Contohnya untuk menampilkan gambar dari campaign, kita bisa menggunakan
kode seperti berikut ini :

<img class="rounded-md w-full" :src="campaign.image">

Untuk title atau judul dari campaign, kita menggunakan kode seperti berikut ini :

{{ campaign.title }}

Kemudian untuk sumDonation kita akan membuat kondisi untuk mengecek nilainya. Jika
nilainya di atas 0, maka kita akan menampilkan percentage dari total donasi yang

643
terkumpul.

<div v-if="sumDonation.length > 0">


<div v-for="donation in sumDonation" :key="donation">

//...
<div :style="{width: percentage(donation.total,
campaign.target_donation) + '%'}" class="shadow-none flex flex-col text-
center whitespace-nowrap text-white justify-center bg-blue-500">
</div>
//...
</div>
</div>

Di atas, kita panggil helpers/mixins yang bernama percentage dan di dalamnya ada 2
parameter, yang pertama adalah total donasi yang terkumpul dan yang kedua merupakan
total dari target donasi. Dengan menggunakan helpers/mixins tersebut kita bisa
mendapatkan nilai persentasinya atau persen-nya.

Tapi jika nilai sumDonation sama dengan 0, maka kita akan menampilkan percentage
dengan default 0. Kurang lebih seperti berikut ini :

<div v-else>

//...
<div :style="{width: percentage(0, campaign.target_donation) +
'%'}"
class="shadow-none flex flex-col text-center whitespace-nowrap
text-white justify-center bg-blue-500">
</div>
//...

</div>

Di atas, kita set untuk parameter pertama adalah angka 0 sebagai default-nya.

Selanjutnya, untuk menghitung jumlah donasi yang masuk, kita bisa menghitung length
dari state donations. Kurang lebih seperti berikut ini :

644
<span class="font-bold">{{ donations.length }}</span> Donasi

Dan untuk menghitung jumlah hari secara countdown, kita juga menggunakan
helpers/mixins yang bernama countDay dan di dalamnya kita berikan parameter dari
tanggal ditentukannya donasi selesai.

<span class="float-right"> <strong>{{ countDay(campaign.max_date)


}}</strong> hari lagi</span>

Helpers/mixins di atas akan menghitung jumlah hari antara tanggal sekarang dengan
tanggal yang ditentukan.

Kemudian kita membuat kondisi untuk mengaktifkan dan menon-aktifkan button DONASI
SEKARANG, yaitu dengan menggunakan helper/mixins yang bernama maxDate. Jika nilainya
adalah true artinya tanggal campaign sudah melebihi batas dan button akan dinon-
aktifkan.

<div v-if="maxDate(campaign.max_date) == true">


<div class="mt-5">
<button class="bg-yellow-500 py-3 rounded-md shadow-md text-xl w-
full uppercase font-bold focus:outline-none opacity-50 cursor-not-
allowed">Donasi Ditutup!</button>
</div>
</div>

Tapi jika bernilai false, maka kita akan menampilkan button DONASI SEKARANG, yang
artinya tanggal yang ditentukan belum melebihi batas.

645
<div v-else>
<div class="mt-5">
<a href="#">
<button class="bg-yellow-500 py-3 rounded-md shadow-md text-
xl w-full uppercase font-bold focus:outline-none focus:bg-
yellow-600">Donasi Sekarang!</button>
</a>
</div>
</div>

Di atas, untuk link donasi sekarang akan kita buat di langkah selanjutnya, untuk sementara
kita set menggunakan a href="#".

Kemudian untuk menampilkan penggalang dana atau user yang membuat campaign, kita
bisa menggunakan kode seperti berikut ini :

<div class="grid grid-cols-10 gap-4">


<div class="col-span-2">
<img :src="user.avatar" class="w-15 h-15 rounded-full shadow">
</div>
<div class="col-span-8 text-lg font-bold mt-6">
{{ user.name }}
</div>
</div>

Di atas kita akan menampilkan foto penggalang dana atau user dan menampilkan nama-
nya.

Selanjutnya, untuk menampilkan data para donatur, kita bisa melakukan perulangan
menggunakan directive v-for, kurang lebih seperti berikut ini :

646
<div v-for="donation in donations" :key="donation.id" class="bg-gray-200
p-3 rounded shadow-md mb-3">

//...

</div>

Di dalam perulangan data donatur di atas, kita menampilkan banyak data, seperti nama,
jumlah donasi-nya, foto donaturnya, doa dari donatur dan lain-lain.

Misalnya untuk menampilkan foto donatur, kita menggunakan kode seperti berikut ini :

<img :src="donation.donatur.avatar" class="w-15 h-15 rounded-full">

Untuk menampilkan nama donatur, kita bisa menggunakan kode seperti berikut ini :

{{ donation.donatur.name }}

Langkah 2 - Edit Module Campaign Vuex


Sekarang kita lanjutkan untuk menambahkan sebuah state, mutation dan action di dalam
module campaign Vuex. Silahkan buka file src/store/module/category.js kemudian
ubah semua kode-nya menjadi seperti berikut ini :

//import global API


import Api from '../../api/Api'

const campaign = {

//set namespace true


namespaced: true,

//state
state: {
//index campaigns
campaigns: [],

//loadmore

647
nextExists: false,
nextPage: 0,

//detail campaign
campaign: {},

//detail user
user: {},

//total donation
sumDonation: [],

//data donations
donations: []

},

//mutations
mutations: {

//set state campaigns dengan data dari response


SET_CAMPAIGNS(state, campaigns) {
state.campaigns = campaigns
},

//set state nextExists


SET_NEXTEXISTS(state, nextExists) {
state.nextExists = nextExists
},

//set state nextPage


SET_NEXTPAGE(state, nextPage) {
state.nextPage = nextPage
},

//set state campaigns dengan data dari response loadmore


SET_LOADMORE(state, data) {
data.forEach(row => {
state.campaigns.push(row);
});
},

//set state campaign dengan data dari response


DETAIL_CAMPAIGN(state, data) {
state.campaign = data
},

648
//set state donatur dengan data dari response
DETAIL_USER(state, data) {
state.user = data
},

//set state sumDonation dengan data dari response


DETAIL_SUMDONATION(state, data) {
state.sumDonation = data
},

//set state donations dengan data dari response


SET_DONATIONS(state, data) {
state.donations = data
},

},

//actions
actions: {

//action getCampaign
getCampaign({ commit }) {

//get data campaign ke server


Api.get('/campaign')
.then(response => {

//commit ke mutation SET_CAMPAIGNS dengan response data


commit('SET_CAMPAIGNS', response.data.data.data)

if (response.data.data.current_page <
response.data.data.last_page) {
//commit ke mutation SET_NEXTEXISTS dengan true
commit('SET_NEXTEXISTS', true)
//commit ke mutation SET_NEXTPAGE dengan current
page + 1
commit('SET_NEXTPAGE',
response.data.data.current_page + 1)

} else {

//commit ke mutation SET_NEXTEXISTS dengan false


commit('SET_NEXTEXISTS', false)
}

}).catch(error => {

649
//show error log dari response
console.log(error)

})
},

//action getLoadMore
getLoadMore({ commit }, nextPage) {

//get data campaign dengan page ke server


Api.get(`/campaign?page=${nextPage}`)
.then(response => {

//commit ke mutation SET_LOADMORE dengan response data


commit('SET_LOADMORE', response.data.data.data)

//console.log(response.data.data.data)

if (response.data.data.current_page <
response.data.data.last_page) {
//commit ke mutation SET_NEXTEXISTS dengan true
commit('SET_NEXTEXISTS', true)
//commit ke mutation SET_NEXTPAGE dengan current
page + 1
commit('SET_NEXTPAGE',
response.data.data.current_page + 1)

} else {

//commit ke mutation SET_NEXTEXISTS dengan false


commit('SET_NEXTEXISTS', false)
}

}).catch(error => {

//show error log dari response


console.log(error)

})
},

//action getDetailCampaign
getDetailCampaign({ commit }, slug) {

//get data detail campaign ke server


Api.get(`/campaign/${slug}`)

650
.then(response => {

//commit ke mutation DETAIL_CAMPAIGN dengan response


data
commit('DETAIL_CAMPAIGN', response.data.data)

//commit ke mutation DETAIL_USER dengan response data


commit('DETAIL_USER', response.data.data.user)

//commit ke mutation DETAIL_SUMDONATION dengan response


data
commit('DETAIL_SUMDONATION',
response.data.data.sum_donation)

//commit ke mutation SET_DONATIONS dengan response data


commit('SET_DONATIONS', response.data.donations)

}).catch(error => {

//show error log dari response


console.log(error)

})
},

},

//getters
getters: {

export default campaign

Dari perubahan kode di atas, pertama kita menambahkan 4 state baru yang mana akan kita
gunakan untuk menyimpan data yang di dapatkan dari Rest API.

651
//detail campaign
campaign: {},

//detail user
user: {},

//total donation
sumDonation: [],

//data donations
donations: []

Setelah itu, kita menambahkan 1 action baru dengan nama getDetailCampaign dan
untuk parameternya terdapat sebuah slug yang dikirim dari component.

//action getDetailCampaign
getDetailCampaign({ commit }, slug) {

//...

Dimana di dalam action di atas, kita melakukan fetching ke dalam Rest API dengan endpoint
http://localhost:8000/api/campaign/slug. Dan untuk method-nya adalah GET.

slug di atas adalah nilai dinamis yang akan berubah-ubah sesuai dengan data yang dikirim
dari component.

Jika proses fetching berhasil, maka akan masuk ke dalam callback then dan di dalamnya
kita melakukan commit ke dalam 4 mutation, yaitu :

652
.then(response => {

//commit ke mutation DETAIL_CAMPAIGN dengan response data


commit('DETAIL_CAMPAIGN', response.data.data)

//commit ke mutation DETAIL_USER dengan response data


commit('DETAIL_USER', response.data.data.user)

//commit ke mutation DETAIL_SUMDONATION dengan response data


commit('DETAIL_SUMDONATION', response.data.data.sum_donation)

//commit ke mutation SET_DONATIONS dengan response data


commit('SET_DONATIONS', response.data.donations)

})

Dan di dalam mutation kita melakukan assign data yang dikirim dari action di atas ke
dalam sebuah state. Kurang lebih seperti berikut ini :

//set state campaign dengan data dari response


DETAIL_CAMPAIGN(state, data) {
state.campaign = data
},

//set state donatur dengan data dari response


DETAIL_USER(state, data) {
state.user = data
},

//set state sumDonation dengan data dari response


DETAIL_SUMDONATION(state, data) {
state.sumDonation = data
},

//set state donations dengan data dari response


SET_DONATIONS(state, data) {
state.donations = data
},

653
Langkah 3 - Uji Coba Detail Campaign
Sekarang silahkan klik salah satu data campaign yang ada di halaman homepage atau
halaman campaign dan jika berhasil maka kurang lebih hasilnya seperti berikut ini :

654
Konfigurasi Router untuk Proses Donasi

Pada tahap kali ini kita akan membuat route untuk menampilkan halaman donasi, dimana di
dalam halaman ini kita akan membuat sebuah form yang nantinya akan digunakan untuk
menginput nilai donasi yang ingin kita berikan dan kita juga tambahkan sebuah input untuk
menulis kata-kata atau doa.

Langkah 1 - Membuat View/Component Create Donation


Sekarang kita akan membuat file view/component terlebih dahulu sebelum mendefinisikan
route-nya. SIlahkan buat file baru dengan nama Create.vue di dalam folder
src/views/donation, kemudian masukkan kode berikut ini :

<template>
<div class="pb-20 pt-20 text-center">
HALAMAN BUAT DONASI
</div>
</template>

<script>
export default {

}
</script>

655
Di atas kita berikan sample template dulu untuk memastikan route yang akan kita buat
nanti bisa berjalan sesuai dengan keinginan kita atau tidak.

Langkah 2 - Membuat Route Create Donation


Sekarang kita lanjutkan untuk membuat route untuk menampilkan halaman tambah donasi,
silahkan buka file src/router/index.js kemudia ubah semua kode-nya menjadi seperti
berikut ini :

//import vue router


import { createRouter, createWebHistory } from 'vue-router'

//import store vuex


import store from '@/store'

//define a routes
const routes = [
{
path: '/register',
name: 'register',
component: () => import( /* webpackChunkName: "register" */
'@/views/auth/Register.vue')
},
{
path: '/login',
name: 'login',

656
component: () => import( /* webpackChunkName: "login" */
'@/views/auth/Login.vue')
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import( /* webpackChunkName: "dashboard" */
'@/views/dashboard/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/donation',
name: 'donation.index',
component: () => import( /* webpackChunkName: "donationIndex" */
'@/views/donation/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/profile',
name: 'profile',
component: () => import( /* webpackChunkName: "profile" */
'@/views/profile/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/profile/password',
name: 'profile.password',
component: () => import( /* webpackChunkName: "profilePassword"
*/ '@/views/profile/Password.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/',
name: 'home',

657
component: () => import( /* webpackChunkName: "home" */
'@/views/home/Index.vue')
},
{
path: '/category',
name: 'category.index',
component: () => import( /* webpackChunkName: "categoryIndex" */
'@/views/category/Index.vue')
},
{
path: '/category/:slug',
name: 'category.show',
component: () => import( /* webpackChunkName: "categoryShow" */
'@/views/category/Show.vue')
},
{
path: '/campaign',
name: 'campaign.index',
component: () => import( /* webpackChunkName: "campaignIndex" */
'@/views/campaign/Index.vue')
},
{
path: '/campaign/:slug',
name: 'campaign.show',
component: () => import( /* webpackChunkName: "campaignShow" */
'@/views/campaign/Show.vue')
},
{
path: '/donation/create/:slug',
name: 'donation.create',
component: () => import( /* webpackChunkName: "donationCreate"
*/ '@/views/donation/Create.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
]

//create router
const router = createRouter({
history: createWebHistory(),
routes // <-- routes,
})

//define route for handle authentication

658
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
//cek nilai dari getters isLoggedIn di module auth
if (store.getters['auth/isLoggedIn']) {
next()
return
}
next('/login')
} else {
next()
}
})

export default router

Di atas kita menambahkan konfigurasi route baru dan route tersebut bersifat private, yang
artinya hanya bisa diakses jika user sudah melakukan proses otentikasi.

{
path: '/donation/create/:slug',
name: 'donation.create',
component: () => import( /* webpackChunkName: "donationCreate" */
'@/views/donation/Create.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},

Dari definisi route di atas, kurang lebih seperti ini penjelasannya :

path - akan digunakan untuk membuat URL dari route tambah donasi.
name - adalah nama dari route, dalam contoh di atas namanya adalah
donation.create. Dengan menggunakan name, maka akan mempermudah kita
dalam pemanggilan route di dalam component.
component - merupakan file view/component yang akan di render jika route ini
dijalankan.
meta - merupakan properti yang bisa kita gunakan untuk banyak hal, dalam kasus ini
kita gunakan untuk memerika auth dengan object requiresAuth.

659
Langkah 3 - Mengaktifkan Link Create Donation
Sekarang kita lanjutkan untuk mengaktifkan link untuk proses donasi dan link tersebut akan
kita arahkan ke dalam route yang sudah kita buat di atas. SIlahkan buka file
src/views/campaign/Show.vue kemudian cari kode berikut ini :

<a href="#">
<button class="bg-yellow-500 py-3 rounded-md shadow-md text-xl
w-full uppercase font-bold focus:outline-none focus:bg-
yellow-600">Donasi
Sekarang! </button>
</a>

Kemudian kita ubah menjadi seperti berikut ini :

<router-link :to="{name: 'donation.create', params:{slug:


campaign.slug}}">
<button class="bg-yellow-500 py-3 rounded-md shadow-md text-xl
w-full uppercase font-bold focus:outline-none focus:bg-
yellow-600">Donasi
Sekarang! </button>
</router-link>

Di atas kita ubah yang semula menggunakan a href="#" menjadi router-link dan kita
arahkan ke dalam route yang bernama donation.create dan di dalamnya kita berikan
parameter berupa slug dari data campaign.

Langkah 4 - Uji Coba Route Halaman Create Donation


Sekarang silahkan buka salah satu data campaign, kemudian klik button DONASI
SEKARAANG. Jika teman-teman belum melakukan otentikasi, maka akan diarahkan ke
halaman login, tapi jika sudah melakukan otentikasi, maka akan menampilkan halaman
tambah donasi. Kurang lebih seperti berikut ini :

660
661
Membuat Proses Donasi

Pada tahap kali ini kita semua akan belajar bagaimana cara menampilkan halaman tambah
donasi dengan sebuah form yang akan kita gunakan untuk menginput nilai angka donasi
dan menuliskan doa.

Langkah 1 - View/Component Create Donation


Sekarang, silahkan buka file src/views/donation/Create.vue kemudian ubah semua
kode-nya menjadi seperti berikut ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">

<div class="bg-white rounded-md shadow-md p-5">


<div class="text-xl">
MASUKKAN NOMINAL DONASI
</div>
<div class="border-2 border-gray-200 mt-3 mb-2"></div>

<div class="mb-2">
<label class="mt-2 font-bold text-lg">Rp.</label>
<input type="number"
class="mt-2 appearance-none w-full bg-gray-200
border border-gray-200 rounded h-15 shadow-sm placeholder-gray-600
focus:outline-none focus:placeholder-gray-600 focus:bg-white focus-
within:text-gray-600 p-2 text-right text-xl"
placeholder="0" v-model="donation.amount">
</div>

<div class="mb-2">
<label class="mt-2 font-bold text-lg">Do'a</label>
<textarea rows="3" v-model="donation.pray"
class="mt-2 appearance-none w-full bg-gray-200
border border-gray-200 rounded shadow-sm placeholder-gray-600
focus:outline-none focus:placeholder-gray-600 focus:bg-white focus-
within:text-gray-600 p-5" placeholder="Tulis Do'a/Ucapan">
</textarea>
</div>

662
<button @click="storeDonation" class="mt-4 bg-yellow-500
py-2 rounded-md shadow-md text-base w-full uppercase font-bold
focus:outline-none focus:bg-yellow-600">LANJUT PEMBAYARAN</button>

</div>

</div>
</div>
</template>

<script>

//hook vue
import { reactive } from 'vue'
//hook vuex
import { useStore } from 'vuex'
//hook vue router
import { useRoute, useRouter } from 'vue-router'
//hook Toast
import { useToast } from "vue-toastification"

export default {

setup() {

//store vuex
const store = useStore()

//route
const route = useRoute()

//router
const router = useRouter()

//toast
const toast = useToast()

//state donation
const donation = reactive({
amount: 0, // <-- data nilai donasi
pray: '', // <-- data kata-
kata/doa
campaignSlug: route.params.slug // <-- data "slug" dari
campaign
})

663
//method store donation
function storeDonation() {

//check minimal donasi


if(donation.amount < 10000) {
toast.error('Donasi Minimal Rp. 10.000')
return false
}

store.dispatch('donation/storeDonation', donation)
.then(() => {

toast.success('Transaksi Berhasil Dibuat!')

//redirect ke dashboard
router.push({name: 'donation.index'})

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

return {
donation, // <-- state donation
storeDonation // <-- method storeDonation
}

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita melakukan import reactive dari Reactivity
API.

664
//hook vue
import { reactive } from 'vue'

Setelah itu kita juga import hook yang bernama useStore dari Vuex.

//hook vuex
import { useStore } from 'vuex'

Kemudian kita import 2 hook dari Vue Router, yaitu useRoute dan useRouter. Untuk
hook yang bernama useRoute akan kita gunakan untuk mengambil sebuah parameter dari
URL browser. Dan untuk hook yang bernama useRouter akan kita gunakan untuk
melakukan redirect sebuah halaman.

//hook vue router


import { useRoute, useRouter } from 'vue-router'

karena nanti kita juga akan menampilkan sebuah Toast/Message, maka kita juga akan
melkukan import hook yang bernama useToast dari Toastification.

//hook Toast
import { useToast } from "vue-toastification"

Seperti sebelum-sebelumnya, untuk mendefinisikan sebuah Composition API kita


menggunakan function yang bernama setup.

setup() {

//...

Di dalam function setup, pertama kita membuat 4 state baru yang isinya merupakan hook
yang sudah kita import di atas, yaitu Vue Router, Vuex Dan Toastification.

665
//store vuex
const store = useStore()

//route
const route = useRoute()

//router
const router = useRouter()

//toast
const toast = useToast()

Setelah itu kita buat 1 state lagi dengan nama donation dan state ini menggunakan jenis
Reactivity API reactive dan di dalamnya kita membuat beberapa object.

//state donation
const donation = reactive({
amount: 0, // <-- data nilai donasi
pray: '', // <-- data kata-kata/doa
campaignSlug: route.params.slug // <-- data "slug" dari campaign
})

kemudian kita buat 1 function yang bernama storeDonation, function ini akan dijalankan
ketika kita melakukan sumbit di dalam form yang ada di template.

//method store donation


function storeDonation() {

//...

Dan di dalamnya pertama kita membuat sebuah kondisi untuk memastikan angka donasi
yang di input lebih dari 10 ribu. Jika kurang dari angka tersebut, maka akan menampilkan
sebuah Toas/Message.

666
//check minimal donasi
if(donation.amount < 10000) {
toast.error('Donasi Minimal Rp. 10.000')
return false
}

Jika nilai donasi yang diinput lebih dari 10 ribu, maka akan menjalankan dispatch atau
memanggil sebuah action yang bernama storeDonation yang berada di dalam module
donation Vuex. Dan kita kirim juga state donation yang isinya adalah data yang diambil
dari form.

store.dispatch('donation/storeDonation', donation)

Jika proses dispatc atau input data donasi berhasil, maka akan masuk ke dalam callback
then dan di dalamnya kita menampilkan sebuah Toast/Message Transaksi Berhasil
Dibuat! dan akan diarahkan ke dalam route yang bernama donation.index.

.then(() => {

toast.success('Transaksi Berhasil Dibuat!')

//redirect ke dashboard
router.push({name: 'donation.index'})

})

Agar state dan function yang kita buat di dalam Composition API dapat digunakan di
dalam template, maka kita perlu melakukan return terlebih dahulu.

return {
donation, // <-- state donation
storeDonation // <-- method storeDonation
}

667
Langkah 2 - Edit Module Donation Vuex
Sekarang kita lanjutkan untuk menambahkan 1 action baru di dalam module donation
Vuex. Silahkan buka file src/store/module/donation.js kemudian ubah semua kode-
nya menjadi seperti berikut ini :

//import global API


import Api from '../../api/Api'

const donation = {

//set namespace true


namespaced: true,

//state
state: {

//donations
donations: [],
//loadmore
nextExists: false,
nextPage: 0,

},

//mutations
mutations: {

//set state donations dengan data dari response


SET_DONATIONS(state, donations) {
state.donations = donations
},

//set state nextExists


SET_NEXTEXISTS(state, nextExists) {
state.nextExists = nextExists
},

//set state nextPage


SET_NEXTPAGE(state, nextPage) {
state.nextPage = nextPage
},

//set state campaigns dengan data dari response loadmore


SET_LOADMORE(state, data) {

668
data.forEach(row => {
state.donations.push(row);
});
},

},

//actions
actions: {

//action getDonation
getDonation({ commit }) {

//get data token dan user


const token = localStorage.getItem('token')

//set axios header dengan type Authorization + Bearer token


Api.defaults.headers.common['Authorization'] = `Bearer
${token}`

//get data donations ke server


Api.get('/donation')
.then(response => {

//commit ke mutation SET_DONATIONS dengan response data


commit('SET_DONATIONS', response.data.data.data)

if (response.data.data.current_page <
response.data.data.last_page) {
//commit ke mutation SET_NEXTEXISTS dengan true
commit('SET_NEXTEXISTS', true)
//commit ke mutation SET_NEXTPAGE dengan current
page + 1
commit('SET_NEXTPAGE',
response.data.data.current_page + 1)

} else {

//commit ke mutation SET_NEXTEXISTS dengan false


commit('SET_NEXTEXISTS', false)
}

}).catch(error => {

//show error log dari response


console.log(error)

669
})
},

//action getLoadMore
getLoadMore({ commit }, nextPage) {

//get data token dan user


const token = localStorage.getItem('token')

//set axios header dengan type Authorization + Bearer token


Api.defaults.headers.common['Authorization'] = `Bearer
${token}`

//get data donations dengan parameter page ke server


Api.get(`/donation?page=${nextPage}`)
.then(response => {

//commit ke mutation SET_LOADMORE dengan response data


commit('SET_LOADMORE', response.data.data.data)

//console.log(response.data.data.data)

if (response.data.data.current_page <
response.data.data.last_page) {
//commit ke mutation SET_NEXTEXISTS dengan true
commit('SET_NEXTEXISTS', true)
//commit ke mutation SET_NEXTPAGE dengan current
page + 1
commit('SET_NEXTPAGE',
response.data.data.current_page + 1)

} else {

//commit ke mutation SET_NEXTEXISTS dengan false


commit('SET_NEXTEXISTS', false)
}

}).catch(error => {

//show error log dari response


console.log(error)

})
},

//storeDonation

670
storeDonation({commit}, data) {

//define callback promise


return new Promise((resolve, reject) => {

//get data token dan user


const token = localStorage.getItem('token')

//set axios header dengan type Authorization + Bearer


token
Api.defaults.headers.common['Authorization'] = `Bearer
${token}`

//send data donatiion ke server


Api.post('/donation', data)
.then(response => {

commit('')
resolve(response)

}).catch(error => {

//show error log dari response


reject(error.response.data)

})

})

},

//getters
getters: {

export default donation

Dari perubahan kode di atas, kita hanya menambahkan 1 action baru yang bernama
storeDonation dan di dalamnya ada parameter data. Parameter tersebut merupakan
data yang dikirim dari component yang berisi nilai donasi dan doa.

671
//storeDonation
storeDonation({commit}, data) {

//...
}

karena kita nanti akan mengembalikan sebuah callback ke dalam component, maka kita
harus definisikan di dalam sebuah Promise yang di dalamnya ada parameter resolve dan
reject. Jika proses berhasil, maka akan mengembalikan callback berupa resolve dan jika
proses gagal, maka akan mengembalikan callback reject.

//define callback promise


return new Promise((resolve, reject) => {
//...
}

Selanjutnya kita akan melakukan send data ke dalam Rest API Laravel dengan endpoint
http://localhost:8000/api/donation dan untuk method-nya adalah POST.

//send data donatiion ke server


Api.post('/donation', data)

Jika proses sending data berhasil, maka kita akan masuk ke dalam callback then dan di
dalamnya kita melakukan commit secara kosong, yang artinya memang tidak perlu
melakukan commit ke mutation manapun dan kita melakukan resolve agar di dalam
component dapat menerma callback dari action ini.

.then(response => {

commit('')
resolve(response)

})

672
Langkah 3 - Uji Coba Proses Donasi
Sekarang kita akan uji coba proses donasi, silahkan klik salah satu data campaign dan klik
DONASI SEKARANG dan jika berhasil maka kita akan mendapatkan tampilan seperti berikut
ini :

Sekarang, silahkan masukkan nominal donasi sesuai dengan keinginan kita dan jangan lupa
tulis sebuah doa. Jika berhasil kita akan di arahkan ke dalam halaman donation. Dan di
dalam halaman ini kita bisa melakukan pembayaran untuk donasi yang kita buat.

Di atas, donasi kita dalam status PENDING dan untuk melakukan pembayaran kita bisa klik

673
button BAYAR SEKARANG. Dan tentu saja kita tidak akan mendapatkan response apapun,
karena kita memang belum mengaktifkan pembayarannya.

674
Menampilkan Snap Pay Midtrans

Pata tahap kali ini kita akan menampilkan popup pembayaran dari Midtrans atau yang
biasanya disebut dengan SNAP PAY. Jika sebelumnya kita sudah melakukan proses tambah
donasi, maka sekarang kita lanjutkan untuk melakukan pembayarannya menggunakan
SNAP PAY dari Midtrans tersebut.

Langkah 1 - View/Component Donation


Sekarang kita akan menambahkan beberapa kode yang nanti akan difungsikan untuk
menampilkan popup SNAP PAY dari Midtrans. SIlahkan buka file
src/views/donation/Index.vue kemudian ubah kode-nya menjadi seperti berikut ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">

<div class="bg-white rounded-md shadow-md p-5">

<div class="text-xl">
RIWAYAT DONASI SAYA
</div>
<div class="border-2 border-gray-200 mt-3 mb-2"></div>

<div v-if="donations.length > 0">


<div class="mt-5 grid grid-cols-4 gap-4">

<div class="col-span-4" v-for="donation in


donations" :key="donation.id">
<div class="bg-gray-200 rounded-md shadow-sm
p-2">
<figure class="md:flex rounded-xl
md:p-0">
<img class="w-full h-34 md:w-48
rounded mx-auto object-cover"
:src="donation.campaign.image"
alt="" width="384" height="512">
<div class="w-full pt-6 p-5 md:p-3
text-center md:text-left space-y-4">
<router-link :to="{name:
'campaign.show', params:{slug: donation.campaign.slug}}">

675
<p class="text-sm font-
semibold">
{{
donation.campaign.title }}
</p>
</router-link>
<figcaption class="font-medium">
<p class="text-xs text-
gray-500 mt-5">
<span class="font-bold
text-gray-500 mr-3">{{ donation.created_at }}</span>
<span class="font-bold
text-blue-900">Rp. {{ formatPrice(donation.amount) }}</span>
</p>
</figcaption>
<div v-if="donation.status ==
'pending'">
<button
@click="payment(donation.snap_token)" class="w-full bg-yellow-600
rounded shadow-sm text-xs py-1 px-2 focus:outline-none">BAYAR
SEKARANG</button>
</div>
</div>
<div class="ml-auto text-sm text-
gray-500 underline">
<div v-if="donation.status ==
'success'">
<button class="bg-green-500
border-2 border-green-500 rounded shadow-sm text-xs py-1 px-2 text-black
focus:outline-none">Berhasil</button>
</div>
<div v-else-if="donation.status
== 'pending'">
<button class="bg-yellow-500
border-2 border-yellow-500 rounded shadow-sm text-xs py-1 px-2 text-
black focus:outline-none">Pending</button>
</div>
<div v-else-if="donation.status
== 'expired'">
<button class="bg-red-500
border-2 border-red-500 rounded shadow-sm text-xs py-1 px-2 text-black
focus:outline-none">Dibatalkan</button>
</div>
<div v-else-if="donation.status
== 'failed'">
<button class="bg-red-500

676
border-2 border-red-500 rounded shadow-sm text-xs py-1 px-2 text-black
focus:outline-none">Dibatalkan</button>
</div>
</div>
</figure>
</div>
</div>

</div>

<div class="text-center mt-7" v-show="nextExists">


<a @click="loadMore"
class="bg-gray-700 text-white p-1 px-3
rounded-md shadow-md focus:outline-none focus:bg-gray-900 cursor-
pointer">LIHAT
SEMUA <i class="fa fa-long-arrow-alt-
right"></i></a>
</div>

</div>
<div v-else>

<div class="mb-3 bg-red-500 text-white p-4 rounded-


md">
Anda Belum Memiliki Transaksi Donasi Saat ini!
</div>

</div>
</div>

</div>
</div>
</template>

<script>

//hook vue
import { computed, onMounted } from 'vue'

//hook vuex
import { useStore } from 'vuex'

//hook vue router


import { useRouter } from 'vue-router'

export default {

677
setup() {

//store vuex
const store = useStore()

//router
const router = useRouter()

//onMounted akan menjalankan action "getDonation" di module


"donation"
onMounted(() => {
store.dispatch('donation/getDonation')
})

//digunakan untuk get data state "donations" di module


"donation"
const donations = computed(() => {
return store.state.donation.donations
})

//digunakan untuk get data state "nextExists" di module


"donation"
const nextExists = computed(() => {
return store.state.donation.nextExists
})

//digunakan untuk get data state "nextPage" di module


"donation"
const nextPage = computed(() => {
return store.state.donation.nextPage
})

//loadMore function
function loadMore() {
store.dispatch('donation/getLoadmore', nextPage.value)
}

//function payment "Midtrans"


function payment(snap_token) {

window.snap.pay(snap_token, {

onSuccess: function () {
router.push({name: 'donation.index'})
},
onPending: function () {

678
router.push({name: 'donation.index'})
},
onError: function () {
router.push({name: 'donation.index'})
}
})

return {
donations, // <-- return donations
nextExists, // <-- return nextExists
nextPage, // <-- return nextPage
loadMore, // <-- return loadMore
payment, // <-- return payment Midtrans Snap
}

}
</script>

<style>

</style>

Dari perubahan kode di atas, pertama kita memberikan sebuah event @click di dalam
button BAYAR SEKARANG, yang mana event click tersebut akan di arahkan ke sebuah
function yang bernama payment dan di dalamnya kita parsing data snap_token.

<div v-if="donation.status == 'pending'">


<button @click="payment(donation.snap_token)" class="w-full bg-
yellow-600 rounded shadow-sm text-xs py-1 px-2 focus:outline-none">BAYAR
SEKARANG</button>
</div>

kemudian di dalam JavaScript, pertama kita import hook yang bernama useRouter dari
Vue Router.

679
//hook vue router
import { useRouter } from 'vue-router'

Setelah itu, kita membuat state baru yang digunakan untuk menampung dari hook di atas.

//router
const router = useRouter()

Kemudian kita membuat sebuah function yang bernama payment dan function ini akan
dijalankan saat kita klik button BAYAR SEKARANG.

//function payment "Midtrans"


function payment(snap_token) {

//...

Dimana di dalamnya kita memanggil function yang bernama snap.pay. Yang mana
function ini sudah disediakan oleh Midtrans, yaitu kurang lebih seperti berikut ini :

window.snap.pay(snap_token, {

onSuccess: function () {
router.push({name: 'donation.index'})
},
onPending: function () {
router.push({name: 'donation.index'})
},
onError: function () {
router.push({name: 'donation.index'})
}
})

Di atas, jika status pembayaran berhasil akan masuk ke dalam method onSuccess dan di
dalamnya akan di arahkan ke dalam route yang bernama donaation.index. Begitu juga
dengan status yang lainnya.

680
Langkah 2 - Uji Coba Pembayaran
Sekarang kita akan lanjutkan untuk proses uji coba pembayaran donasi. Silahkan klik button
BAYAR SEKARANG di halaman Donasi Saya dan kurang lebih akan menampilkan popup
SNAP PAY seperti berikut ini :

Kemudian, silahkan klik CONTINUE atau LANJUTKAN (jika menggunakan basaha Indonesia).

Di atas kita mendapatkan banyak sekali metode pembayaran yang bisa kita pilih untuk
membayar, disini kita akan simulasi menggunakan BCA Virtual Account. Silahkan klik

681
ATM/BANK Transfer.

kemudian silahkan pilih BANK BCA.

Klik SEE ACCOUNT NUMBER

682
Di atas kita berhasil mendapatkan nomor Virtual Account dari BANK BCA. Sekarang
silahkan buka link berikut ini untuk melakukan simulasi pembayaran.

https://simulator.sandbox.midtrans.com/bca/va/index

Kemudian masukkan nomor Virtual Account. Dan klik Inquire.

Selanjutnya, silahkan klik Pay.

683
Maka pembayaran sudah berhasil dilakukan. Dan jika kita lihat di halaman website kita
Kurang lebih seperti berikut ini :

Jika belum muncul seperti itu, silahkan tutup popup SNAP PAY dan buka kembali.

CATATAN! : status pembayaran di database tidak akan bisa berubah menjadi success jika
masih di dalam localhost.
Setelah proses deployment project Laravel ke online, maka kita nanti akan lakukan setting
di dashboard Midtrans dengan mengisi URL dari notifikasi Handler yang sebelumnya pernah
kita buat. Setelah kita mengatur notifikasi handler tersebut, maka status pembayaran bisa
otomatis berubah.

684
Konfigurasi Router untuk Search

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat route baru untuk
menampilkan halaman pencarian. Dan tentu saja route yang akan kita buat ini bersifat
global, yang artinya dapat diakses semua user tanpa perlu proses otentikasi.

Langkah 1 - Membuat View/Component Search


Pertama, silahkan buat folder baru dengan nama search di dalam folder src/views/ dan
di dalam folder search tersebut silahkan buat file baru dengan nama Index.vue dan
masukkan kode berikut ini :

<template>
<div class="pb-20 pt-20 text-center">
HALAMAN SEARCH
</div>
</template>

<script>
export default {

}
</script>

685
Di atas kita berikan sample template yang nanti akan kita gunakan untuk memastikan
apakah route halaman search berjalan sesuai dengan yang diharapkan.

Langkah 2 - Membuat Route Search


Sekarang kita lanjutkan untuk membuat definisi route baru untuk menampilkan halaman
search. Silahkan buka file src/router/index.js kemudian ubah semua kode-nya
menjadi seperti berikut ini :

//import vue router


import { createRouter, createWebHistory } from 'vue-router'

//import store vuex


import store from '@/store'

//define a routes
const routes = [
{
path: '/register',
name: 'register',
component: () => import( /* webpackChunkName: "register" */
'@/views/auth/Register.vue')
},
{
path: '/login',
name: 'login',
component: () => import( /* webpackChunkName: "login" */
'@/views/auth/Login.vue')
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import( /* webpackChunkName: "dashboard" */
'@/views/dashboard/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/donation',
name: 'donation.index',
component: () => import( /* webpackChunkName: "donationIndex" */
'@/views/donation/Index.vue'),

686
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/profile',
name: 'profile',
component: () => import( /* webpackChunkName: "profile" */
'@/views/profile/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/profile/password',
name: 'profile.password',
component: () => import( /* webpackChunkName: "profilePassword"
*/ '@/views/profile/Password.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/',
name: 'home',
component: () => import( /* webpackChunkName: "home" */
'@/views/home/Index.vue')
},
{
path: '/category',
name: 'category.index',
component: () => import( /* webpackChunkName: "categoryIndex" */
'@/views/category/Index.vue')
},
{
path: '/category/:slug',
name: 'category.show',
component: () => import( /* webpackChunkName: "categoryShow" */
'@/views/category/Show.vue')
},
{
path: '/campaign',
name: 'campaign.index',

687
component: () => import( /* webpackChunkName: "campaignIndex" */
'@/views/campaign/Index.vue')
},
{
path: '/campaign/:slug',
name: 'campaign.show',
component: () => import( /* webpackChunkName: "campaignShow" */
'@/views/campaign/Show.vue')
},
{
path: '/donation/create/:slug',
name: 'donation.create',
component: () => import( /* webpackChunkName: "donationCreate"
*/ '@/views/donation/Create.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/search',
name: 'search',
component: () => import( /* webpackChunkName: "search" */
'@/views/search/Index.vue')
},
]

//create router
const router = createRouter({
history: createWebHistory(),
routes // <-- routes,
})

//define route for handle authentication


router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
//cek nilai dari getters isLoggedIn di module auth
if (store.getters['auth/isLoggedIn']) {
next()
return
}
next('/login')
} else {
next()
}
})

688
export default router

Di atas kita menambahkan definisi route baru untuk menampilkan halaman search, kurang
lebih seperti berikut ini :

{
path: '/search',
name: 'search',
component: () => import( /* webpackChunkName: "search" */
'@/views/search/Index.vue')
},

Dari definisi route di atas, kurang lebih seperti ini penjelasannya :

path - akan digunakan untuk membuat URL dari route search


name - adalah nama dari route, dalam contoh di atas namanya adalah search.
Dengan menggunakan name, maka akan mempermudah kita dalam pemanggilan
route di dalam component.
component - merupakan file view/component yang akan di render jika route ini
dijalankan.

Langkah 3 - Mengaktifkan Link Search


Sekarang kita akan membuat event di dalam component Header, jadi ketika form di klik,
maka akan kita arahkan ke dalam halaman search ini.

Silahkan buka file src/components/Header.vue, kemudian ubah kode-nya menjadi


seperti berikut ini :

<template>
<div>
<!-- header -->
<header>
<div class="bg-gray-700 text-white text-center fixed inset-
x-0 top-0 z-10">
<div class="container mx-auto grid grid-cols-10 p-3
sm:w-full md:w-5/12">
<div class="col-span-2 bg-white rounded-full h-10 w-10
p-1 mr-3 shadow-sm">
<router-link :to="{name: 'home'}">
<img src="@/assets/images/muslim.png"

689
class="inline-block">
</router-link>
</div>
<div class="col-span-8">
<input type="text" @click="linkToSearch"
class="appearance-none w-full bg-gray-500 rounded-full h-7 shadow-md
placeholder-white focus:outline-none focus:placeholder-gray-600
focus:bg-white focus-within:text-gray-600 p-5"
placeholder="Cari yang ingin kamu bantu">
</div>
</div>
</div>
</header>
</div>
</template>

<script>
//hook vue router
import { useRouter } from 'vue-router'
export default {

setup() {

//router
const router = useRouter()

//redirect to route search


function linkToSearch() {
router.push({
name: 'search'
})
}

return {
linkToSearch, // <-- method linkToSearch
}
}

}
</script>

<style>

</style>

690
Dari perubahan kode di atas, pertama kita melakukan import hook yang bernama
useRouter dari Vue Router.

//hook vue router


import { useRouter } from 'vue-router'

Setelah itu, kita mendefinisikan sebuah Composition API menggunakan function setup.

setup() {

//...
}

Di dalam function setup, pertama kita buat sebuah state baru dengan nama router dan
isinya merupakan hook dari Vue Router, yaitu useRouter.

//router
const router = useRouter()

Setelah itu kita membuat sebuah function yang bernama linkToSearch yang mana di
dalamnya akan melakukan redirect ke dalam route yang bernama search. Function ini
akan di jalankan ketika kita melakukan klik di form pencarian navbar.

//redirect to route search


function linkToSearch() {
router.push({
name: 'search'
})
}

Dimana di dalam input form kita memberikan event @click yang mengarah ke dalam
function di atas.

691
<input type="text" @click="linkToSearch" class="appearance-none w-full
bg-gray-500 rounded-full h-7 shadow-md placeholder-white focus:outline-
none focus:placeholder-gray-600 focus:bg-white focus-within:text-
gray-600 p-5" placeholder="Cari yang ingin kamu bantu">

Dan agar function di atas dapat digunakan di dalam template, maka kita perlu melakukan
return terlebih dahulu.

return {
linkToSearch, // <-- method linkToSearch
}

Langkah 4 - Uji Coba Halaman Search


Sekarang silahkan klik form pencarian yang ada di navbar dan jika berhasil maka kita akan
diarahkan ke dalam halaman search. Kurang lebih seperti berikut ini :

692
Membuat Realtime Search

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat proses pencarian data
di dalam Vue Js secara realtime. Dimana data yang akan cari nanti adalah campaign.

Langkah 1 - Edit Component Header


Pertama kita akan menambahkan beberapa kode di dalam component Header. Jadi
konsepnya nanti untuk proses dispatch akan kita lakukan di dalam component Header
dan untuk menampilkan data ada di dalam view/component search.

Silahkan buka file src/components/Header.vue kemudian ubah kode-nya menjadi


seperti berikut ini :

<template>
<div>
<!-- header -->
<header>
<div class="bg-gray-700 text-white text-center fixed inset-
x-0 top-0 z-10">
<div class="container mx-auto grid grid-cols-10 p-3
sm:w-full md:w-5/12">
<div class="col-span-2 bg-white rounded-full h-10 w-10
p-1 mr-3 shadow-sm">
<router-link :to="{name: 'home'}">
<img src="@/assets/images/muslim.png"
class="inline-block">
</router-link>
</div>
<div class="col-span-8">
<input type="text" @click="linkToSearch" v-
model="search" @keyup="searchQuery" class="appearance-none w-full bg-
gray-500 rounded-full h-7 shadow-md placeholder-white focus:outline-none
focus:placeholder-gray-600 focus:bg-white focus-within:text-gray-600
p-5"
placeholder="Cari yang ingin kamu bantu">
</div>
</div>
</div>
</header>
</div>
</template>

693
<script>
//hook vue
import { ref } from 'vue'
//hook vue router
import { useRouter } from 'vue-router'
//hook vuex
import { useStore } from 'vuex'
export default {

setup() {

//router
const router = useRouter()

//store
const store = useStore()

//state seacrh
const search = ref(null)

//queryString
function searchQuery() {
store.dispatch('campaign/searchCampaign', search.value)
}

//redirect to route search


function linkToSearch() {
router.push({
name: 'search'
})
}

return {
search, // <-- state search
linkToSearch, // <-- method linkToSearch
searchQuery // <-- method searchQuery
}
}

}
</script>

<style>

</style>

694
Dari perubahan kode di atas, pertama kita import Reactivity API dengan jenis ref.

//hook vue
import { ref } from 'vue'

Selanjutnya kita import hook yang bernama useStore dari Vuex.

//hook vuex
import { useStore } from 'vuex'

Kemudian di dalam function setup kita membuat state untuk menyimpan hook yang store
dari Vuex di atas.

//store
const store = useStore()

Kemudian kita buat state lagi dengan nama search dan di dalam state ini kita
menggunakan Reactivity API ref. State ini akan kita gunakan untuk menampung nilai
yang diinputkan dari form.

//state seacrh
const search = ref(null)

Kemudian kita buat function yang bernama searchQuery, function ini akan dijalankan
ketika kita melakukan input di dalam form. Dan di dalamnya kita melakukan dispatch atau
memanggil sebuah action yang bernama searchCampaign yang berada di dalam module
campaign.

//queryString
function searchQuery() {
store.dispatch('campaign/searchCampaign', search.value)
}

Agar state dan function di atas dapat digunakan di dalam template, maka kita perlu
melakukan return terlebih dahulu. Kurang lebih seperti berikut ini :

695
return {
search, // <-- state search
linkToSearch, // <-- method linkToSearch
searchQuery // <-- method searchQuery
}

Kemudian di dalam template bisa kita perhatikan, kita menambahkan event @keyup dan
akan diarahkan ke dalam function yang bernama searchQuery. Event tersebut akan
dijalankan ketika kita melakukan input apapun di dalam form.

<input type="text" @click="linkToSearch" v-model="search"


@keyup="searchQuery" class="appearance-none w-full bg-gray-500 rounded-
full h-7 shadow-md placeholder-white focus:outline-none
focus:placeholder-gray-600 focus:bg-white focus-within:text-gray-600
p-5" placeholder="Cari yang ingin kamu bantu">

Langkah 2 - Menambahkan Action di Module Campaign Vuex


Sekarang kita akan menambahkan 1 action baru di dalam module campaign Vuex untuk
proses pencarian data. Silahkan buka file src/store/module/campaign.js kemudian
ubah kode-nya menjadi seperti berikut ini :

//import global API


import Api from '../../api/Api'

const campaign = {

//set namespace true


namespaced: true,

//state
state: {
//index campaigns
campaigns: [],

//loadmore
nextExists: false,
nextPage: 0,

//detail campaign

696
campaign: {},

//detail user
user: {},

//total donation
sumDonation: [],

//data donations
donations: []

},

//mutations
mutations: {

//set state campaigns dengan data dari response


SET_CAMPAIGNS(state, campaigns) {
state.campaigns = campaigns
},

//set state nextExists


SET_NEXTEXISTS(state, nextExists) {
state.nextExists = nextExists
},

//set state nextPage


SET_NEXTPAGE(state, nextPage) {
state.nextPage = nextPage
},

//set state campaigns dengan data dari response loadmore


SET_LOADMORE(state, data) {
data.forEach(row => {
state.campaigns.push(row);
});
},

//set state campaign dengan data dari response


DETAIL_CAMPAIGN(state, data) {
state.campaign = data
},

//set state donatur dengan data dari response


DETAIL_USER(state, data) {
state.user = data

697
},

//set state sumDonation dengan data dari response


DETAIL_SUMDONATION(state, data) {
state.sumDonation = data
},

//set state donations dengan data dari response


SET_DONATIONS(state, data) {
state.donations = data
},

},

//actions
actions: {

//action getCampaign
getCampaign({ commit }) {

//get data campaign ke server


Api.get('/campaign')
.then(response => {

//commit ke mutation SET_CAMPAIGNS dengan response data


commit('SET_CAMPAIGNS', response.data.data.data)

if (response.data.data.current_page <
response.data.data.last_page) {
//commit ke mutation SET_NEXTEXISTS dengan true
commit('SET_NEXTEXISTS', true)
//commit ke mutation SET_NEXTPAGE dengan current
page + 1
commit('SET_NEXTPAGE',
response.data.data.current_page + 1)

} else {

//commit ke mutation SET_NEXTEXISTS dengan false


commit('SET_NEXTEXISTS', false)
}

}).catch(error => {

//show error log dari response


console.log(error)

698
})
},

//action getLoadMore
getLoadMore({ commit }, nextPage) {

//get data campaign dengan page ke server


Api.get(`/campaign?page=${nextPage}`)
.then(response => {

//commit ke mutation SET_LOADMORE dengan response data


commit('SET_LOADMORE', response.data.data.data)

//console.log(response.data.data.data)

if (response.data.data.current_page <
response.data.data.last_page) {
//commit ke mutation SET_NEXTEXISTS dengan true
commit('SET_NEXTEXISTS', true)
//commit ke mutation SET_NEXTPAGE dengan current
page + 1
commit('SET_NEXTPAGE',
response.data.data.current_page + 1)

} else {

//commit ke mutation SET_NEXTEXISTS dengan false


commit('SET_NEXTEXISTS', false)
}

}).catch(error => {

//show error log dari response


console.log(error)

})
},

//action getDetailCampaign
getDetailCampaign({ commit }, slug) {

//get data detail campaign ke server


Api.get(`/campaign/${slug}`)
.then(response => {

//commit ke mutation DETAIL_CAMPAIGN dengan response

699
data
commit('DETAIL_CAMPAIGN', response.data.data)

//commit ke mutation DETAIL_USER dengan response data


commit('DETAIL_USER', response.data.data.user)

//commit ke mutation DETAIL_SUMDONATION dengan response


data
commit('DETAIL_SUMDONATION',
response.data.data.sum_donation)

//commit ke mutation SET_DONATIONS dengan response data


commit('SET_DONATIONS', response.data.donations)

}).catch(error => {

//show error log dari response


console.log(error)

})
},

//action searchCampaign
searchCampaign({ commit }, querySearch='') {

//get data token dan user


const token = localStorage.getItem('token')

//set axios header dengan type Authorization + Bearer token


Api.defaults.headers.common['Authorization'] = `Bearer
${token}`

//get data campaign ke server


Api.get(`/campaign?q=${querySearch}`)
.then(response => {

//commit ke mutation SET_CAMPAIGNS dengan response data


commit('SET_CAMPAIGNS', response.data.data.data)

}).catch(error => {

//show error log dari response


console.log(error)

})
},

700
},

//getters
getters: {

export default campaign

Dari perubahan kode di atas, kita hanya menambahkan 1 action baru yang bernama
searchCampaign dan di dalam action tersebut ada parameter yang bernama
querySearch, dimana datanya di dapatkan dari component Header berupa keyword untuk
pencarian data.

//action searchCampaign
searchCampaign({ commit }, querySearch='') {

//...

Di dalam action searchCampaign di atas kita melakukan fetching ke dalam Rest API
Laravel dengan endpoint http://localhost:8000/api/campaign?q=querySearch dan untuk
method-nya adalah GET.

Jika proses fetching berhasil, maka akan melakukan commit ke dalam mutation yang
bernama SET_CAMPAIGNS dan isinya adalah response data yang di dapatkan dari Rest API.

//commit ke mutation SET_CAMPAIGNS dengan response data


commit('SET_CAMPAIGNS', response.data.data.data)

Dan tentunya di dalam mutation SET_CAMPAIGNS kita melakukan assign data dari response
di atas ke dalam state yang bernama campaigns.

701
/set state campaigns dengan data dari response
SET_CAMPAIGNS(state, campaigns) {
state.campaigns = campaigns
},

Langkah 3 - Menampilkan Data Hasil Pencarian


Terakhir, kita akan belajar menampilkan data dari hasil pencarian. Silahkan buka file
src/views/search/Index.vue Kemudian ubah semua kode-nya menjadi seperti berikut
ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">

<div v-if="campaigns.length > 0">

<div class="mt-5 grid grid-cols-4 gap-4" v-for="campaign


in campaigns" :key="campaign.id">
<div class="col-span-4">
<div class="bg-white rounded-md shadow-md p-2">
<div class="md:flex rounded-xl md:p-0">
<img class="w-full h-34 md:w-56 rounded
object-cover" :src="campaign.image" width="384"
height="512">
<div class="pt-6 p-5 md:p-3 text-center
md:text-left space-y-4">
<router-link :to="{name:
'campaign.show', params:{slug: campaign.slug }}">
<p class="text-sm font-
semibold">
{{ campaign.title }}
</p>
</router-link>
<div class="font-medium">
<div class="mt-3 text-gray-500
text-xs">
{{ campaign.user.name }}
</div>
<div v-
if="campaign.sum_donation.length > 0">

702
<div v-for="donation in
campaign.sum_donation" :key="donation">

<div class="relative
pt-1">
<div
class="overflow-hidden h-2 mb-4 text-xs flex rounded bg-blue-200">
<div
:style="{width: percentage(donation.total, campaign.target_donation) +
'%'}"
class="shadow-none flex flex-col text-center whitespace-nowrap text-
white justify-center bg-blue-500">
</div>
</div>
</div>

<p class="text-xs text-


gray-500">
<span class="font-
bold text-blue-400">Rp.
{{
formatPrice(donation.total) }} </span> terkumpul dari
<span class="font-
bold">Rp.
{{
formatPrice(campaign.target_donation) }}</span>
</p>
</div>
</div>
<div v-else>

<div class="relative pt-1">


<div class="overflow-
hidden h-2 mb-4 text-xs flex rounded bg-blue-200">
<div :style="{width:
percentage(0, campaign.target_donation) + '%'}"
class="shadow-
none flex flex-col text-center whitespace-nowrap text-white justify-
center bg-blue-500">
</div>
</div>
</div>

<p class="text-xs text-


gray-500">
<span class="font-bold

703
text-blue-400">Rp. 0 </span> terkumpul dari
<span class="font-
bold">Rp.
{{
formatPrice(campaign.target_donation) }}</span>
</p>
</div>
<div class="mt-3 text-xs">
<strong>{{
countDay(campaign.max_date) }}</strong> hari lagi
</div>
</div>
</div>
</div>
</div>
</div>
</div>

</div>
<div v-else>
<div class="mb-3 bg-red-500 text-white p-4 rounded-md">
Data Campaign Tidak Ditemukan!
</div>
</div>

</div>
</div>
</template>

<script>

//hook vue
import { computed } from 'vue'

//hook vuex
import { useStore } from 'vuex'

export default {

setup() {

//vuex
const store = useStore()

//digunakan untuk get data state "campaigns" di module


"campaign"

704
const campaigns = computed(() => {
return store.state.campaign.campaigns
})

return {
campaigns, // <-- state campaigns
}

}
</script>

<style>

</style>

Dari perubahan kode di atas, pertama kita melakukan import properti computed dari Vue.

//hook vue
import { computed } from 'vue'

Selanjutnya import hook yang bernama useStore dari Vuex.

//hook vuex
import { useStore } from 'vuex'

Kemudian di dalam Composition API, kita membuat state baru untuk menginisialisasi store
dari Vuex.

//vuex
const store = useStore()

Setelah itu, kita membuat state lagi dengan nama campaigns dan menggunakan jenis
properti computed dan di dalamnya kita memanggil sebuah state yang bernama
campaigns dari module campaign Vuex.

705
//digunakan untuk get data state "campaigns" di module "campaign"
const campaigns = computed(() => {
return store.state.campaign.campaigns
})

Terakhir, agar state di atas dapat digunakan di dalam template, maka kita perlu melakukan
return terlebih dahulu.

return {
campaigns, // <-- state campaigns
}

Kemudian di dalam template kita membuat kondisi untuk menentukan nilai dari state
campaigns. Jika nilainya lebih dari 0, maka data campaign akan ditampilkan menggunakan
perulangan v-for.

<div v-if="campaigns.length > 0">

<div class="mt-5 grid grid-cols-4 gap-4" v-for="campaign in


campaigns" :key="campaign.id">

//...

</div>
</div>

Tapi, jika nilai state campaigns sama dengan 0, maka akan menampilkan pesan Data
Campaign Tidak Ditemukan! atau hasil pencarian tidak menemukan data apapun.

<div v-else>
<div class="mb-3 bg-red-500 text-white p-4 rounded-md">
Data Campaign Tidak Ditemukan!
</div>
</div>

706
Langkah 4 - Uji Coba Pencarian
Sekarang kita bisa lakukan uji coba untuk pencarian data secara realtime. Kurang lebih
seperti berikut ini :

Dan jika kita lihat di dalam tab Network > XHR, maka setiap kita mengetikkan sesuatu
akan melakukan fetching ke dalam endpoint Rest API Laravel. Kurang lebih seperti berikut
ini :

707
Membuat Component Slider

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat component slider,
component ini akan kita panggil dan tampilkan di halaman homepage nantinya. Tapi disini
kita membuatnya di dalam file yang terpisah dengan homepage tersebut.

Langkah 1 - Membuat Component Slider


Sekarang, silahkan buat file baru dengan nama Slider.vue di dalam folder
src/components dan masukkan kode berikut ini :

<template>
<div>
<div v-if="sliders.length > 0">
<vueper-slides slide-image-inside autoplay>
<template v-slot:arrow-left>
<i class="icon icon-arrow-left" />
</template>
<vueper-slide v-for="(slider, i) in sliders"
:key="i" :image="slider.image" />
<template v-slot:arrow-right>
<i class="icon icon-arrow-right" />
</template>
</vueper-slides>
</div>
<div v-else>
<ContentLoader />
</div>
</div>
</template>

<script>
//hook vue
import { computed, onMounted } from 'vue'

//vuex
import { useStore } from 'vuex'

//content loader
import { ContentLoader } from 'vue-content-loader'
//vueper slider
import { VueperSlides, VueperSlide } from 'vueperslides'

708
import 'vueperslides/dist/vueperslides.css'

export default {

components: {
ContentLoader,
VueperSlides,
VueperSlide
},

setup() {

//store vuex
const store = useStore()

//onMounted akan menjalankan action "getSlider" di module


"slider"
onMounted(() => {
store.dispatch('slider/getSlider')
})

//digunakan untuk get data state "sliders" di module


"slider"
const sliders = computed(() => {
return store.state.slider.sliders
})

return {
sliders, // <-- sliders
}

}
</script>

<style scoped>
.vueperslide__image {
transform: scale(1.5) rotate(-10deg);
}

.vueperslide__title {
font-size: 7em;
opacity: 0.7;
}
</style>

709
Dari penambahan kode di atas, pertama kita impor properti computed dan hook
onMounted dari Vue.

//hook vue
import { computed, onMounted } from 'vue'

Setelah itu, kita juga import hook yang bernama useStore dari Vuex.

//vuex
import { useStore } from 'vuex'

Kemudian kita juga import libraray yang digunakan untuk menampilkan loading block jika
data slider tidak ada, yaitu Vue Content Loader.

/content loader
import { ContentLoader } from 'vue-content-loader'

Dan kita import library untuk menampilkan gambar slider nantinya, yaitu VueperSlides.
Disini kita akan import 2 file, yaitu component dari VueperSlides dan file CSS-nya.

//vueper slider
import { VueperSlides, VueperSlide } from 'vueperslides'
import 'vueperslides/dist/vueperslides.css'

kemudian kita register component dari Vue Content Loader dan VueperSlides di dalam
properti components Vue. Ini bertujuan agar kita dapat memanggil component tersebut di
dalam template.

710
components: {
ContentLoader,
VueperSlides,
VueperSlide
},

Setelah itu, kita mendefinisikan sebuah Composition API menggunakan function setup.

setup() {

//...
}

Di dalam function setup, pertama kita buat sebuah state baru dengan nama store dan
isinya merupakan hook dari Vuex, yaitu useStore.

//store vuex
const store = useStore()

Dan saat component melakukan proses mounted, maka hook onMounted akan dijalankan
dan di dalamnya kita melakukan sebuah dispatch ke dalam sebuah action yang bernama
getSlider yang mana action tersebut berada di dalam module slider Vuex.

//onMounted akan menjalankan action "getSlider" di module "slider"


onMounted(() => {
store.dispatch('slider/getSlider')
})

Setelah itu, kita buat state baru dengan nama sliders dan state ini menggunakan jenis
properti computed dan di dalamnya akan memanggil sebuah state yang bernama sliders
yang berada di dalam module slider Vuex.

711
//digunakan untuk get data state "sliders" di module "slider"
const sliders = computed(() => {
return store.state.slider.sliders
})

Agara state sliders di atas dapat digunakan di dalam template, maka kita perlu
melakukan return terlebih dahulu.

return {
sliders, // <-- sliders
}

Kemudian kita menambahkan CSS yang kita set dengan scoped yang artinya CSS ini hanya
berlaku di halaman ini saja. Dan CSS ini merupakan tambahan untuk mempercantik
tampilan slider.

<style scoped>
.vueperslide__image {
transform: scale(1.5) rotate(-10deg);
}

.vueperslide__title {
font-size: 7em;
opacity: 0.7;
}
</style>

kemudian kita lanjutkan dibagian template. Disini kita membuat sebuah kondisi untuk
mengecek nilai dari state sliders, jika nilai state terssebut di atas 0, maka akan
menampilkan data dengan perulangan v-for. Kurang lebih seperti berikut ini :

712
<div v-if="sliders.length > 0">
//...
<vueper-slide v-for="(slider, i) in sliders" :key="i"
:image="slider.image" />

//...
</div>

Tapi, jika state sliders bernilai 0, maka akan menampilkan sebuah loading block
menggunakan Vue Content Loader. Kurang lebih seperti berikut ini :

<div v-else>
<ContentLoader />
</div>

Langkah 2 - Edit Module Slider Vuex


Sekarang kita lanjutkan untuk menambahkan sebuah state, mutation dan action baru di
dalam module slider Vuex. Silahkan buka file src/store/module/slider.js
kemudian ubah semua kode-nya menjadi seperti berikut ini :

//import global API


import Api from '../../api/Api'

const slider = {

//set namespace true


namespaced: true,

//state
state: {

//index sliders
sliders: [],
},

//mutations
mutations: {

//set state sliders dengan data dari response

713
SET_SLIDERS(state, data) {
state.sliders = data
},
},

//actions
actions: {

//action getSlider
getSlider({ commit }) {

//get data sliders ke server


Api.get('/slider')
.then(response => {

//commit ke mutation SET_SLIDERS dengan response data


commit('SET_SLIDERS', response.data.data)

}).catch(error => {

//show error log dari response


console.log(error)

})
}
},

//getters
getters: {

export default slider

Di atas, pertama kita import konfigurasi global API untuk memanggil baseURL dari endpoint
API Laravel kita.

//import global API


import Api from '../../api/Api'

714
Kemudian kita tambahkan 1 state baru dengan nama sliders. State ini akan kita gunakan
untuk menyimpan data sliders yang di dapatkan dari response Rest API.

//index sliders
sliders: [],

Selanjutnya, pada bagian action kita tambahkan 1 action baru dengan nama getSlider,
dimana di dalamnya akan melakukan fetching ke dalam endpoint API
http://localhost:8000/api/slider.

//get data sliders ke server


Api.get('/slider')

Jika berhasil, maka akan masuk ke dalam callback then dan di dalamnya kita melakukan
commit ke dalam sebuah mutation yang bernama SET_SLIDERS dan isinya adalah
response data yang di dapatkan dari Rest API.

/commit ke mutation SET_SLIDERS dengan response data


commit('SET_SLIDERS', response.data.data)

Dan di dalam mutation SET_SLIDERS kita melakukan assign dari data yang di dapatkan
tersebut ke dalam state yang bernama sliders.

//set state sliders dengan data dari response


SET_SLIDERS(state, data) {
state.sliders = data
},

715
Konfigurasi Module Category Vuex

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat module Vuex baru
untuk mengelola data category. Di dalam module ini nantinya akan kita gunakan untuk
mendapatkan data category dan detail data category yang berisi data campaign.

Langkah 1 - Membuat Module Category Vuex


Sekarang, silahkan buat file baru dengan nama category.js di dalam folder
src/store/module dan masukkan kode berikut ini :

const category = {

//set namespace true


namespaced: true,

//state
state: {
},

//mutations
mutations: {

},

//actions
actions: {

},

//getters
getters: {

export default category

716
Di dalam module category di atas, kita menambahkan beberapa store, yaitu state,
mutations, actions dan getters. Dan untuk namespace digunakan karena kita
menggunakan teknik module, ini bertujuan agar module kita dapat diakses langsung melalui
component.

Langkah 2 - Import Module Category di Store Vuex


Sekarang, kita akan import dan register module category di atas di dalam store induk,
silahkan buka file src/store/index.js kemudian ubah kode-nya menjadi seperti berikut
ini :

717
//import vuex
import { createStore } from 'vuex'

//import module auth


import auth from './module/auth'

//import module donation


import donation from './module/donation'

//import module profile


import profile from './module/profile'

//import module slider


import slider from './module/slider'

//import module category


import category from './module/category'

//create store vuex


export default createStore({

modules: {
auth, // <-- module auth
donation, // <-- module donation
profile, // <-- module profile
slider, // <-- module slider
category, // <-- module category
}

})

![](/home/maulayyacyber/Pictures/Ebook - Website Donasi Online/vuex-import-module-


category.png)

Dari penambahan kode di atas, pertama kita import module category daari folder module.

//import module category


import category from './module/category'

Selanjutnya, kita register module tersebut di dalam store modules Vuex.

718
modules: {
auth, // <-- module auth
donation, // <-- module donation
profile, // <-- module profile
slider, // <-- module slider
category, // <-- module category
}

719
Membuat Component Category Home

Pada tahap kali ini kita semua belajar bagaimana cara membuat component untuk
menampilkan data category, dimana component ini akan kita tampilkan di halaman
homepage nantinya. Jadi sama seperti component slider, yaitu kita membuat file yang
terpisah dan akan kita import nantinya di halaman homepage untuk ditampilkan.

Langkah 1 - Membuat Component Category Home


Silahkan buat file baru dengan nama CategoryHome.vue di dalam folder
src/components dan masukkan kode berikut ini :

<template>
<div>
<div v-if="categories.length > 0">
<div class="mt-5 grid grid-cols-4 gap-4 md:gap-4 text-
center items-center">
<div v-for="category in categories" :key="category.id"
class="col-span-2 md:col-span-2 lg:col-span-1 bg-white rounded-md
shadow-md p-4 text-center text-xs">
<a href="#">
<div>
<img :src="category.image" width="40"
class="inline-block mb-2">
</div>
{{ category.name.toUpperCase() }}
</a>
</div>
<div class="col-span-2 md:col-span-1 lg:col-span-1 bg-
white rounded-md shadow-md p-4 text-center text-xs">
<a href="#">
<div>
<img src="@/assets/images/menu.png"
width="40" class="inline-block mb-2">
</div>
LAINNYA
</a>
</div>
</div>
</div>
<div v-else>
<div class="mt-5 grid grid-cols-4 gap-4 md:gap-4 text-

720
center items-center">
<div v-for="index in 4" :key="index" class="sm:col-
span-2 md:col-span-2 lg:col-span-2 bg-white rounded-md shadow-md text-
center text-xs">
<ContentLoader />
</div>
</div>
</div>
</div>
</template>

<script>
//hook vue
import { computed, onMounted } from 'vue'
//vuex
import { useStore } from 'vuex'
//vue content loader
import { ContentLoader } from 'vue-content-loader'

export default {

components: {
ContentLoader // <-- register content loader
},

setup() {

//store vuex
const store = useStore()

//onMounted akan menjalankan action "getCategoryHome" di


module "category"
onMounted(() => {
store.dispatch('category/getCategoryHome')
})

//digunakan untuk get data state "categories" di module


"category"
const categories = computed(() => {
return store.state.category.categories
})

return {
categories // <-- categories
}

721
}

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita impor properti computed dan hook
onMounted dari Vue.

//hook vue
import { computed, onMounted } from 'vue'

Setelah itu, kita juga import hook yang bernama useStore dari Vuex.

//vuex
import { useStore } from 'vuex'

Kemudian kita juga import libraray yang digunakan untuk menampilkan loading block jika
data slider tidak ada, yaitu Vue Content Loader.

/content loader
import { ContentLoader } from 'vue-content-loader'

Setelah itu kita register component Vue Content Loader di atas di dalam properti
components. Ini digunakan agar kita dapat memanggil component tersebut di dalam
template.

components: {
ContentLoader // <-- register content loader
},

Setelah itu, kita mendefinisikan sebuah Composition API menggunakan function setup.

722
setup() {

//...
}

Di dalam function setup, pertama kita buat sebuah state baru dengan nama store dan
isinya merupakan hook dari Vuex, yaitu useStore.

//store vuex
const store = useStore()

Saat component dalam proses mounted, maka akan menjalankan hook yang bernama
onMounted dan di dalamnya akan melakukan dispatch ke dalam sebuah action yang
bernama getCategoryHome yang berada di dalam module category Vuex.

//onMounted akan menjalankan action "getCategoryHome" di module


"category"
onMounted(() => {
store.dispatch('category/getCategoryHome')
})

Selanjutnya, kita membuat state yang bernama categories dan state ini menggunakan
jenis properti computed dan di dalamnya kita memanggil sebuah state dari module
category Vuex yang bernama categories.

//digunakan untuk get data state "categories" di module "category"


const categories = computed(() => {
return store.state.category.categories
})

Agar state di atas dapat digunakan di dalam template, maka kita perlu melakukan return
terlebih dahulu.

723
return {
categories // <-- categories
}

Dan di dalam template, kita membuat kondisi untuk mengecek nilai dari state categories,
jika bernilai lebih dari 0 maka akan melakukan perulangan data yang diambil dari state
categories menggunakan directive v-for.

<div v-if="categories.length > 0">

//...
<div v-for="category in categories" :key="category.id"
class="col-span-2 md:col-span-2 lg:col-span-1 bg-white rounded-md
shadow-md p-4 text-center text-xs">
</div>
//...

</div>

Tapi, jika nilai state tersebut sama dengan 0, maka akan menampilkan loading block dengan
Vue Content Loader sejumlah 4 item.

<div v-else>

//...
<div v-for="index in 4" :key="index" class="sm:col-span-2
md:col-span-2 lg:col-span-2 bg-white rounded-md shadow-md text-center
text-xs">
<ContentLoader />
</div>
//...

</div>

Langkah 2 - Edit Module Category Vuex


Sekarang kita lanjutkan untuk menambahkan sebuah state, mutation dan action baru di

724
dalam module category Vuex. Silahkan buka file src/store/module/category.js
kemudian ubah semua kode-nya menjadi seperti berikut ini :

//import global API


import Api from '../../api/Api'

const category = {

//set namespace true


namespaced: true,

//state
state: {
//index categories
categories: [],

},

//mutations
mutations: {

//set state categories dengan data dari response


SET_CATEGORIES(state, data) {
state.categories = data
},

},

//actions
actions: {

//action getCategoryHome
getCategoryHome({ commit }) {

//get data sliders ke server


Api.get('/categoryHome')
.then(response => {

//commit ke mutation SET_CATEGORIES dengan response data


commit('SET_CATEGORIES', response.data.data)

}).catch(error => {

//show error log dari response


console.log(error)

725
})
},

},

//getters
getters: {

export default category

Di atas, pertama kita import konfigurasi global API untuk memanggil baseURL dari endpoint
API Laravel kita.

//import global API


import Api from '../../api/Api'

Kemudian kita tambahkan 1 state baru dengan nama categories. State ini akan kita
gunakan untuk menyimpan data categories yang di dapatkan dari response Rest API.

//index categories
categories: [],

Setelah itu, kita juga menambahkan action baru dengan nama getCategoryHome dimana
di dalamnya kita melakukan fethcing ke dalam Rest API Laravel dengan endpoint
http://localhost:8000/api/categoryHome.

//get data sliders ke server


Api.get('/categoryHome')

Jika proses fecthing berhasil, maka akan masuk ke dalam callback then dan di dalamnya
kita melakukan commit ke dalam mutation yang bernama SET_CATEGORIES dan isinya
adalah response data yang kita dapatkan dari Rest API.

726
//commit ke mutation SET_CATEGORIES dengan response data
commit('SET_CATEGORIES', response.data.data)

Dan di dalam mutation SET_CATEGORIES, kita melakukan assign data response tersebut ke
dalam state yang bernama categories.

//set state categories dengan data dari response


SET_CATEGORIES(state, data) {
state.categories = data
},

727
Konfigurasi Router untuk Homepage

Pada tahap kali ini kita semua akan belajar bagaiman cara membuat sebuah route baru
untuk menampilkan halaman homepage dan tentu saja route ini bersifat public, yang artinya
semua orang bisa mengaksesnya tanpa perlu melakukan sebuah otentikasi.

Langkah 1 - Membuat View/Component Homepage


Sekarang, silahkan buat folder baru dengan nama home di dalam folder src/views dan di
dalam folder home tersebut silahkan buat file baru dengan nama Index.vue kemudian
masukkan kode berikut ini :

<template>
<div class="pb-20 pt-20 text-center">
HALAMAN HOMEPAGE
</div>
</template>

<script>
export default {

}
</script>

728
Di atas kita memerikan sample template terlebih dahulu, ini digunakan agar kita dapat
memastikan bahwa route yang akan kita buat sudah bisa menampilkan halaman homepage.

Langkah 2 - Membuat Route Homepage


Sekarang kita akan lanjutkan untuk mendefinisikan route baru untuk halaman homepage.
Silahkan buka file src/router/index.js dan ubah kode-nya menjadi seperti berikut ini :

//import vue router


import { createRouter, createWebHistory } from 'vue-router'

//import store vuex


import store from '@/store'

//define a routes
const routes = [
{
path: '/register',
name: 'register',
component: () => import( /* webpackChunkName: "register" */
'@/views/auth/Register.vue')
},
{
path: '/login',
name: 'login',
component: () => import( /* webpackChunkName: "login" */
'@/views/auth/Login.vue')
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import( /* webpackChunkName: "dashboard" */
'@/views/dashboard/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/donation',
name: 'donation.index',
component: () => import( /* webpackChunkName: "donationIndex" */
'@/views/donation/Index.vue'),
meta: {

729
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/profile',
name: 'profile',
component: () => import( /* webpackChunkName: "profile" */
'@/views/profile/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/profile/password',
name: 'profile.password',
component: () => import( /* webpackChunkName: "profilePassword"
*/ '@/views/profile/Password.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/',
name: 'home',
component: () => import( /* webpackChunkName: "home" */
'@/views/home/Index.vue')
},
]

//create router
const router = createRouter({
history: createWebHistory(),
routes // <-- routes,
})

//define route for handle authentication


router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
//cek nilai dari getters isLoggedIn di module auth
if (store.getters['auth/isLoggedIn']) {
next()
return
}

730
next('/login')
} else {
next()
}
})

export default router

Di atas, kita membuat definisi route baru untuk menampilkan halaman homepage, kurang
lebih seperti berikut ini :

{
path: '/',
name: 'home',
component: () => import( /* webpackChunkName: "home" */
'@/views/home/Index.vue')
},

path - akan digunakan untuk membuat URL dari route homepage.


name - adalah nama dari route, dalam contoh di atas namanya adalah home. Dengan
menggunakan name, maka akan mempermudah kita dalam pemanggilan route di
dalam component.
component - merupakan file view/component yang akan di render jika route ini
dijalankan.

Langkah 3 -Mengaktifkan Link Menu


Setelah berhasil membuat route untuk halaman homepage, sekarang kita lanjutkan untuk
menagaktifkan link menu untuk halaman homepage tersebut. Silahkan buka file
src/components/Header.vue kemudian cari kode berikut ini :

<a href="">
<img src="@/assets/images/muslim.png" class="inline-block">
</a>

Kemudian kita ubah menjadi seperti berikut ini :

731
<router-link :to="{name: 'home'}">
<img src="@/assets/images/muslim.png" class="inline-block">
</router-link>

Di atas kita ubah yang semula menggunakan a href menjadi router-link dan kita
arahkan ke dalam route yang bernama home.

Selanjutnya, silahkan buka file src/components/Footer.vue dan cari kode berikut ini :

<a href="#"
class="w-full focus:text-teal-500 hover:text-teal-500 justify-
center inline-block text-center">
<img class="inline-block mb-1" width="25" height="25"
src="@/assets/images/home.png">
<span class="block text-xs">Beranda</span>
</a>

Kemudian kita ubah menjadi seperti berikut ini :

<router-link :to="{name: 'home'}"


class="w-full focus:text-teal-500 hover:text-teal-500 justify-
center inline-block text-center">
<img class="inline-block mb-1" width="25" height="25"
src="@/assets/images/home.png">
<span class="block text-xs">Beranda</span>
</router-link>

Sama seperti sebelumnya, kita mengubah yang semula menggunakan a href menjadi
router-link.

Sekarang silahkan buka website donasi dan klik logo di bagian header atau bisa klik menu
Beranda di bagian footer, maka kita akan mendapatkan hasil seperti berikut ini :

732
733
Menampilkan Component Slider dan Category di
Halaman Homepage

Pada tahap kali ini kita semua akan belajar bagaimana cara menampilkan component slider
dan juga component category home di dalam halaman homepage.

Langkah 1 - View/Component Homepage


Silahkan buka file src/views/home/Index.vue kemudian ubah semua kode-nya menjadi
seperti berikut ini :

734
<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">

<!-- slider -->


<div class="grid grid-cols-1 bg-white rounded shadow-md p-1
text-sm">
<Slider />
</div>

<!-- categoryHome -->


<CategoryHome />

<!-- campaign -->

</div>

</div>
</template>

<script>

//component slider
import Slider from '@/components/Slider.vue'

//component categoryHome
import CategoryHome from '@/components/CategoryHome.vue'

export default {

components: {
Slider, // <-- register component slider
CategoryHome, // <-- register component CategoryHome
},

}
</script>

Di atas, pertama kita import 2 component, yaitu Header.vue dan CategoryHome.vue

735
/component slider
import Slider from '@/components/Slider.vue'

//component categoryHome
import CategoryHome from '@/components/CategoryHome.vue'

Setelah itu, kita register kedua component tersebut di dalam properti components.

components: {
Slider, // <-- register component slider
CategoryHome, // <-- register component CategoryHome
},

Dan untuk menampilkan di dalam template, kita bisa seperti berikut ini :

<Slider />

<CategoryHome />

Kita juga bisa menulis component di dalam template seperti berikut ini :

<slider></slider>

<category-home></category-home>

Langkah 2 - Uji Coba Halaman Homepage


Sekarang, silahkan refresh halaman homepage dan jika belum ada perubahan silahkan
lakukan restart server Vue Js-nya. Dan jika berhasil maka kurang lebih hasilnya seperti
berikut ini :

736
737
Konfigurasi Module Campaign Vuex

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat module Vuex baru
untuk mengelola data campaign. Module ini nantinya akan kita gunakan untuk proses
mendapatkan data, filter data dan lain-lain.

Langkah 1 - Membuat Module Campaign Vuex


Sekarang, silahkan buat file baru dengan nama campaign.js di dalam folder
src/store/module dan masukkan kode berikut ini :

const campaign = {

//set namespace true


namespaced: true,

//state
state: {
},

//mutations
mutations: {

},

//actions
actions: {

},

//getters
getters: {

export default campaign

738
Di dalam module campaign di atas, kita menambahkan beberapa store, yaitu state,
mutations, actions dan getters. Dan untuk namespace digunakan karena kita
menggunakan teknik module, ini bertujuan agar module kita dapat diakses langsung melalui
component.

Langkah 2 - Import Module Campaign di Store Vuex


Sekarang, kita akan import dan register module campaign di atas di dalam store induk,
silahkan buka file src/store/index.js kemudian ubah kode-nya menjadi seperti berikut
ini :

739
//import vuex
import { createStore } from 'vuex'

//import module auth


import auth from './module/auth'

//import module donation


import donation from './module/donation'

//import module profile


import profile from './module/profile'

//import module slider


import slider from './module/slider'

//import module category


import category from './module/category'

//import module campaign


import campaign from './module/campaign'

//create store vuex


export default createStore({

modules: {
auth, // <-- module auth
donation, // <-- module donation
profile, // <-- module profile
slider, // <-- module slider
category, // <-- module category
campaign, // <-- module campaign
}

})

740
Di atas, pertama kita import module campaign dari folder module.

//import module campaign


import campaign from './module/campaign'

Selanjutnya, kita register module tersebut di dalam store modules Vuex.

modules: {
auth, // <-- module auth
donation, // <-- module donation
profile, // <-- module profile
slider, // <-- module slider
category, // <-- module category
campaign, // <-- module campaign
}

741
Menampilkan Data Campaign di Homepage

Pada tahap kali ini kita semua akan belajar bagaimana cara menampilkan data campaign di
halaman homepage, di dalam campaign nanti kita akan menampilkan beberapa data
menggunakan bantuan helpers/mixins. Contohnya untuk menampilkan jumlah hari secara
countdown, jumlah donasi terkumpul dengan progressbar dan menampilkan format mata
uang. Dan kita juga akan membuat fitur loadmore untuk membatasi jumlah data yang akan
di tampilkan di halaman homepage.

Langkah 1 - View/Component Homepage


Sekarang kita akan menambahkan beberapa kode di dalam halaman homepage. Silahkan
buka file src/views/home/Index.vue, kemudian ubah semua kode-nya menjadi seperti
berikut ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">

<!-- slider -->


<div class="grid grid-cols-1 bg-white rounded shadow-md p-1
text-sm">
<Slider />
</div>

<!-- categoryHome -->


<CategoryHome />

<div v-if="campaigns.length > 0">


<div class="mt-5 grid grid-cols-4 gap-4" v-for="campaign
in campaigns" :key="campaign.id">
<div class="col-span-4">
<div class="bg-white rounded-md shadow-md p-2">
<div class="md:flex rounded-xl md:p-0">
<img class="w-full h-34 md:w-56 rounded
object-cover"
:src="campaign.image" width="384"
height="512">
<div class="w-full pt-6 p-5 md:p-3 text-
center md:text-left space-y-4">
<a href="#">

742
<p class="text-sm font-
semibold">
{{ campaign.title }}
</p>
</a>
<div class="font-medium">
<div class="mt-3 text-gray-500
text-xs">
{{ campaign.user.name }}
</div>
<div v-
if="campaign.sum_donation.length > 0">
<div v-for="donation in
campaign.sum_donation" :key="donation">

<div class="relative
pt-1">
<div
class="overflow-hidden h-2 mb-4 text-xs flex rounded bg-blue-200">
<div
:style="{width: percentage(donation.total, campaign.target_donation) +
'%'}" class="shadow-none flex flex-col text-center whitespace-nowrap
text-white justify-center bg-blue-500"></div>
</div>
</div>

<p class="text-xs text-


gray-500">
<span class="font-
bold text-blue-400">Rp. {{ formatPrice(donation.total) }} </span>
terkumpul dari
<span class="font-
bold">Rp. {{ formatPrice(campaign.target_donation) }}</span>
</p>
</div>
</div>
<div v-else>

<div class="relative pt-1">


<div class="overflow-
hidden h-2 mb-4 text-xs flex rounded bg-blue-200">
<div :style="{width:
percentage(0, campaign.target_donation) + '%'}" class="shadow-none flex
flex-col text-center whitespace-nowrap text-white justify-center bg-
blue-500"></div>
</div>

743
</div>

<p class="text-xs text-


gray-500">
<span class="font-bold
text-blue-400">Rp. 0 </span> terkumpul dari
<span class="font-
bold">Rp. {{ formatPrice(campaign.target_donation) }}</span>
</p>
</div>
<div class="mt-3 text-xs">
<strong>{{
countDay(campaign.max_date) }}</strong> hari lagi
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-else>

<div v-for="index in 2" :key="index" class="grid grid-


cols-1 bg-white rounded shadow-md p-3 text-sm mt-4 mb-4">
<FacebookLoader class="h-24"/>
</div>

</div>

</div>

</div>
</template>

<script>

//hook vue
import { computed, onMounted } from 'vue'

//vuex
import { useStore } from 'vuex'

//component slider
import Slider from '@/components/Slider.vue'

744
//component categoryHome
import CategoryHome from '@/components/CategoryHome.vue'

//vue content loader


import { FacebookLoader } from 'vue-content-loader'

export default {

components: {
Slider, // <-- register component slider
CategoryHome, // <-- register component CategoryHome
FacebookLoader // <-- register component FacebooLoader dari
Vue Content Loader
},

setup() {

//store vuex
const store = useStore()

//onMounted akan menjalankan action "getCampaign" di module


"campaign"
onMounted(() => {
store.dispatch('campaign/getCampaign')
})

//digunakan untuk get data state "campaigns" di module


"campaign"
const campaigns = computed(() => {
return store.state.campaign.campaigns
})

return {
campaigns, // <-- return campaigns
}

}
</script>

Dari penambahan kode di atas, pertama kita import properti computed dan hook
onMounted.

745
//hook vue
import { computed, onMounted } from 'vue'

Kemudian kita import juga hook yang bernama useStore dari Vuex. Hook ini akan
mempermudah kita dalam pemanggilan store Vuex di dalam Composition API.

//vuex
import { useStore } from 'vuex'

Kemudian kita juga import libraray yang digunakan untuk menampilkan loading block jika
data slider tidak ada, yaitu Vue Content Loader. Dan jenis component yang akan kita
gunakan adalah FacebookLoader.

/content loader
import { FacebookLoader } from 'vue-content-loader'

Setelah itu kita register component FacebookLoader dari Vue Content Loader di dalam
properti components.

components: {
Slider, // <-- register component slider
CategoryHome, // <-- register component CategoryHome
FacebookLoader // <-- register component FacebooLoader dari Vue
Content Loader
},

Setelah itu, kita mendefinisikan sebuah Composition API menggunakan function setup.

setup() {

//...
}

Di dalam function setup, pertama kita buat sebuah state baru dengan nama store dan
isinya merupakan hook dari Vuex, yaitu useStore.

746
//store vuex
const store = useStore()

Saat component dalam proses mounted, maka di dalamnya akan melakukan dispatch
atau memanggil sebuah action yang bernama getCampaign yang berada di dalam module
campaign Vuex.

//onMounted akan menjalankan action "getCampaign" di module "campaign"


onMounted(() => {
store.dispatch('campaign/getCampaign')
})

Setelah itu, kita membuat state baru dengan nama campaigns dan state ini menggunakan
jenis proeperti computed. Dan di dalamnya kita melakukan get untuk mendapatkan data
dari state yang bernama campaigns yang berada di dalam module campaign Vuex.

//digunakan untuk get data state "campaigns" di module "campaign"


const campaigns = computed(() => {
return store.state.campaign.campaigns
})

Agar state campaigns di atas dapat kita gunakan di dalam template, maka kita perlu
melakukan return terlebih dahulu. Kurang lebih seperti berikut ini :

return {
campaigns, // <-- return campaigns
}

Kemudian kita lanjutkan dibagian template untuk menampilkan data campaign. Pertama
kita melakuakn sebuah kondisi untuk mengecek nilai dari state campaigns.

Jika state campaigns memiliki nilai di atas 0, maka akan melakukan perulangan data
campaign menggunakan directive v-for.

747
<div v-if="campaigns.length > 0">
<div class="mt-5 grid grid-cols-4 gap-4" v-for="campaign in
campaigns" :key="campaign.id">
//...
</div>
</div>

Di dalam perulangan data di atas, kita menampilkan banyak sekali data, misalnya untuk
menampilkan judul campaign, kita bisa menggunakan sintaks seperti berikut ini :

{{ campaign.title }}

Untuk gambar campaign :

<img class="w-full h-34 md:w-56 rounded object-cover"


:src="campaign.image" width="384" height="512">

Kemudian di dalamnya kita juga melakukan proses menampilkan donasi yang terkumpul
menggunakan progressbar. Kurang lebih seperti berikut ini :

<div :style="{width: percentage(donation.total,


campaign.target_donation) + '%'}" class="shadow-none flex flex-col text-
center whitespace-nowrap text-white justify-center bg-blue-500"></div>

Di atas, kita panggil helpers/mixins yang bernama percentage dan di dalamnya ada 2
parameter, yang pertama adalah total donasi yang terkumpul dan yang kedua merupakan
total dari target donasi. Dengan menggunakan helpers/mixins tersebut kita bisa
mendapatkan nilai persentasinya atau persen-nya.

kemudian untuk menampilkan jumlah donasi kita menggunakan helpers/mixins yang


bernama formatPrice dan di dalamnyaa kita parsing angka yang akan di format.
Contohnya seperti berikut ini :

748
<span class="font-bold">Rp. {{ formatPrice(campaign.target_donation)
}}</span>

Dan untuk menghitung jumlah hari secara countdown, kita juga menggunakan
helpers/mixins yang bernama countDay dan di dalamnya kita berikan parameter dari
tanggal ditentukannya donasi selesai.

<strong>{{ countDay(campaign.max_date) }}</strong> hari lagi

Helpers/mixins di atas akan menghitung jumlah hari antara tanggal sekarang dengan
tanggal yang ditentukan.

Langkah 2 - Edit Module Campaign Vuex


Sekarang kita lanjutkan untuk menambahkan sebuah state, mutation dan action baru di
dalam module campaign Vuex. Silahkan buka file src/store/module/campaign.js
kemudian ubah semua kode-nya menjadi seperti berikut ini :

//import global API


import Api from '../../api/Api'

const campaign = {

//set namespace true


namespaced: true,

//state
state: {
//index campaigns
campaigns: [],

//loadmore
nextExists: false,
nextPage: 0,

},

//mutations

749
mutations: {

//set state campaigns dengan data dari response


SET_CAMPAIGNS(state, campaigns) {
state.campaigns = campaigns
},

//set state nextExists


SET_NEXTEXISTS(state, nextExists) {
state.nextExists = nextExists
},

//set state nextPage


SET_NEXTPAGE(state, nextPage) {
state.nextPage = nextPage
},

},

//actions
actions: {

//action getCampaign
getCampaign({ commit }) {

//get data campaign ke server


Api.get('/campaign')
.then(response => {

//commit ke mutation SET_CAMPAIGNS dengan response data


commit('SET_CAMPAIGNS', response.data.data.data)

if (response.data.data.current_page <
response.data.data.last_page) {
//commit ke mutation SET_NEXTEXISTS dengan true
commit('SET_NEXTEXISTS', true)
//commit ke mutation SET_NEXTPAGE dengan current
page + 1
commit('SET_NEXTPAGE',
response.data.data.current_page + 1)

} else {

//commit ke mutation SET_NEXTEXISTS dengan false


commit('SET_NEXTEXISTS', false)
}

750
}).catch(error => {

//show error log dari response


console.log(error)

})
},

},

//getters
getters: {

export default campaign

Di atas, pertama kita import konfigurasi global API untuk memanggil baseURL dari endpoint
API Laravel kita.

//import global API


import Api from '../../api/Api'

Kemudian kita tambahkan 1 state baru dengan nama campaigns. State ini akan kita
gunakan untuk menyimpan data campaigns yang di dapatkan dari response Rest API.

//index campaigns
campaigns: [],

Dan kita juga membuat 2 state lagi, yaitu nextExists dan nextPage. Kedua state
tersebut akan kita gunakan untuk membuat proses loadmore halaman.

//loadmore
nextExists: false,
nextPage: 0,

751
Kemudian kita menambahkan 1 action baru dengan nama getCampaign dimana di
dalamnya kita melakukan fetching ke dalam Rest API dengan endpoint
http://localhost:8000/api/campaign.

//get data campaign ke server


Api.get('/campaign')

Jika proses fetching berhasil, maka akan masuk ke dalam callback then dan di dalamnya
pertama kita melakukan commit ke dalam sebuah mutation yang bernama
SET_CAMPAIGNS dan kita berikan isi berupa data response dari Rest API.

//commit ke mutation SET_CAMPAIGNS dengan response data


commit('SET_CAMPAIGNS', response.data.data.data)

Kemduain di dalam mutation SET_CAMPAIGNS kita melakukan assign dari data response di
atas ke dalam state yang bernama campaigns.

//set state campaigns dengan data dari response


SET_CAMPAIGNS(state, campaigns) {
state.campaigns = campaigns
},

Dan balik lagi di dalam action getCampaign, di dalam callback then kita juga membuat
sebuah kondisi untuk menentukan sebuah loadmore data. Jika response current_page
lebih kecil dari response last_page, maka di dalamnya kita melakukan commit ke dalam 2
mutation.

//commit ke mutation SET_NEXTEXISTS dengan true


commit('SET_NEXTEXISTS', true)
//commit ke mutation SET_NEXTPAGE dengan current page + 1
commit('SET_NEXTPAGE', response.data.data.current_page + 1)

Di atas, kita melakukan commit ke dalam mutation yang bernama SET_NEXTEXISTS


dengan isi berupa nilai bolean true.

752
//set state nextExists
SET_NEXTEXISTS(state, nextExists) {
state.nextExists = nextExists
},

Dan di dalam mutation SET_NEXTPAGE kita melakukan assign ke dalam state yang bernama
nextPage yang isinya adalah data current_page + 1 yang dikirim dari action.

//set state nextPage


SET_NEXTPAGE(state, nextPage) {
state.nextPage = nextPage
},

Langkah 3 - Uji Coba Menampilkan Campaign


Sekarang silahkan refresh halaman homepage atau bisa buka link http://localhost:8080, jika
berhasil maka kita akan mendapatkan tampilan seperti berikut ini :

Langkah 4 - Membuat Fitur Loadmore


Sekarang kita lanjutkan untuk menambahkan fitur loadmore di halaman homepage untuk
data campaign. Silahkan buka file src/views/home/Index.vue kemudian ubah semua

753
kode-nya menjadi seperti berikut ini :

<template>
<div class="pb-20 pt-20">
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">

<!-- slider -->


<div class="grid grid-cols-1 bg-white rounded shadow-md p-1
text-sm">
<Slider />
</div>

<!-- categoryHome -->


<CategoryHome />

<div v-if="campaigns.length > 0">


<div class="mt-5 grid grid-cols-4 gap-4" v-for="campaign
in campaigns" :key="campaign.id">
<div class="col-span-4">
<div class="bg-white rounded-md shadow-md p-2">
<div class="md:flex rounded-xl md:p-0">
<img class="w-full h-34 md:w-56 rounded
object-cover"
:src="campaign.image" width="384"
height="512">
<div class="w-full pt-6 p-5 md:p-3 text-
center md:text-left space-y-4">
<a href="#">
<p class="text-sm font-
semibold">
{{ campaign.title }}
</p>
</a>
<div class="font-medium">
<div class="mt-3 text-gray-500
text-xs">
{{ campaign.user.name }}
</div>
<div v-
if="campaign.sum_donation.length > 0">
<div v-for="donation in
campaign.sum_donation" :key="donation">

<div class="relative

754
pt-1">
<div
class="overflow-hidden h-2 mb-4 text-xs flex rounded bg-blue-200">
<div
:style="{width: percentage(donation.total, campaign.target_donation) +
'%'}" class="shadow-none flex flex-col text-center whitespace-nowrap
text-white justify-center bg-blue-500"></div>
</div>
</div>

<p class="text-xs text-


gray-500">
<span class="font-
bold text-blue-400">Rp. {{ formatPrice(donation.total) }} </span>
terkumpul dari
<span class="font-
bold">Rp. {{ formatPrice(campaign.target_donation) }}</span>
</p>
</div>
</div>
<div v-else>

<div class="relative pt-1">


<div class="overflow-
hidden h-2 mb-4 text-xs flex rounded bg-blue-200">
<div :style="{width:
percentage(0, campaign.target_donation) + '%'}" class="shadow-none flex
flex-col text-center whitespace-nowrap text-white justify-center bg-
blue-500"></div>
</div>
</div>

<p class="text-xs text-


gray-500">
<span class="font-bold
text-blue-400">Rp. 0 </span> terkumpul dari
<span class="font-
bold">Rp. {{ formatPrice(campaign.target_donation) }}</span>
</p>
</div>
<div class="mt-3 text-xs">
<strong>{{
countDay(campaign.max_date) }}</strong> hari lagi
</div>
</div>
</div>

755
</div>
</div>
</div>
</div>
</div>
<div v-else>

<div v-for="index in 2" :key="index" class="grid grid-


cols-1 bg-white rounded shadow-md p-3 text-sm mt-4 mb-4">
<FacebookLoader class="h-24"/>
</div>

</div>

</div>

<div class="text-center mt-4 mb-4" v-show="nextExists">


<a @click="loadMore"
class="bg-gray-700 text-white p-2 px-3 rounded-md
shadow-md focus:outline-none focus:bg-gray-900 cursor-pointer">LIHAT
SEMUA <i class="fa fa-long-arrow-alt-right"></i></a>
</div>

</div>
</template>

<script>

//hook vue
import { computed, onMounted } from 'vue'

//vuex
import { useStore } from 'vuex'

//component slider
import Slider from '@/components/Slider.vue'

//component categoryHome
import CategoryHome from '@/components/CategoryHome.vue'

//vue content loader


import { FacebookLoader } from 'vue-content-loader'

export default {

components: {

756
Slider, // <-- register component slider
CategoryHome, // <-- register component CategoryHome
FacebookLoader // <-- register component FacebooLoader dari
Vue Content Loader
},

setup() {

//store vuex
const store = useStore()

//onMounted akan menjalankan action "getCampaign" di module


"campaign"
onMounted(() => {
store.dispatch('campaign/getCampaign')
})

//digunakan untuk get data state "campaigns" di module


"campaign"
const campaigns = computed(() => {
return store.state.campaign.campaigns
})

/**
* LOADMORE
*/

//get status NextExists


const nextExists = computed(() => {
return store.state.campaign.nextExists
})

//get nextPage
const nextPage = computed(() => {
return store.state.campaign.nextPage
})

//loadMore function
function loadMore() {
store.dispatch('campaign/getLoadMore', nextPage.value)
}

return {
campaigns, // <-- return campaigns
nextExists, // <-- return nextExists,
nextPage, // <-- return nextPage

757
loadMore, // <-- return loadMore
}

}
</script>

Dari penambahan kode di atas, pertama kita buat 2 state baru dengan nama nextExists
dan nextPage. Kedua state tersebut mengambil data dari sebuah state yang bernama
nextExists dan nextPage yang ada di dalam module campaign Vuex.

//get status NextExists


const nextExists = computed(() => {
return store.state.campaign.nextExists
})

//get nextPage
const nextPage = computed(() => {
return store.state.campaign.nextPage
})

State nextExists akan berisi nilai bolean antara true dan false. State ini akan
digunakan untuk memberikan kondisi button loadmore, jika bernilai true maka button
loadmore akan ditampilkan dan begitu juga sebaliknya.

Dan untuk state nextPage akan berisi angka halaman page yang akan kita kirim ke server
untuk dijadikan sebuah parameter mendapatkan data.

Selanjutnya kita membuat 1 function baru yang bernama loadMore, function ini akan
dijalankan ketika kita menekan button loadmore yang ada di template. Di dalamnya kita
melakukan dispatch atau memanggil sebuah action yang bernama getLoadMore dan di
dalam parameternya kita berikan nilai page yang diambil dari state nextPage.

//loadMore function
function loadMore() {
store.dispatch('campaign/getLoadMore', nextPage.value)
}

Agar state dan function di atas dapat digunakan di dalam template, maka kita perlu

758
menambahkannya di dalam return Composition API.

return {
campaigns, // <-- return campaigns
nextExists, // <-- return nextExists,
nextPage, // <-- return nextPage
loadMore, // <-- return loadMore
}

Di dalam template kita bisa lihat, disini kita menambahkan kode seperti berikut ini untuk
menampilkan button loadmore :

<div class="text-center mt-4 mb-4" v-show="nextExists">


<a @click="loadMore" class="bg-gray-700 text-white p-2 px-3 rounded-
md shadow-md focus:outline-none focus:bg-gray-900 cursor-pointer">LIHAT
SEMUA <i class="fa fa-long-arrow-alt-right"></i></a>
</div>

Di atas, jika nilai nextExists adalah true. Maka button loadmore di dalamnya akan
ditampilkan dan jika kita perhatikan di dalam button loadmore, kita arahkan ke dalam
sebuah function yang bernama loadMore dan function tersebut merupakan function yang
kita buat di atas sebelumnya.

Langkah 5 - Edit Module Campaign Vuex


Sekarang kita lanjutkan untuk menambahkan action dan mutation di dalam module
campaign Vuex untuk proses fitur loadmore. SIlahkan buka file
src/store/module/campaign.js dan ubah kode-nya menjadi seperti berikut ini :

//import global API


import Api from '../../api/Api'

const campaign = {

//set namespace true


namespaced: true,

//state
state: {
//index campaigns

759
campaigns: [],

//loadmore
nextExists: false,
nextPage: 0,

},

//mutations
mutations: {

//set state campaigns dengan data dari response


SET_CAMPAIGNS(state, campaigns) {
state.campaigns = campaigns
},

//set state nextExists


SET_NEXTEXISTS(state, nextExists) {
state.nextExists = nextExists
},

//set state nextPage


SET_NEXTPAGE(state, nextPage) {
state.nextPage = nextPage
},

//set state campaigns dengan data dari response loadmore


SET_LOADMORE(state, data) {
data.forEach(row => {
state.campaigns.push(row);
});
},

},

//actions
actions: {

//action getCampaign
getCampaign({ commit }) {

//get data campaign ke server


Api.get('/campaign')
.then(response => {

//commit ke mutation SET_CAMPAIGNS dengan response data

760
commit('SET_CAMPAIGNS', response.data.data.data)

if (response.data.data.current_page <
response.data.data.last_page) {
//commit ke mutation SET_NEXTEXISTS dengan true
commit('SET_NEXTEXISTS', true)
//commit ke mutation SET_NEXTPAGE dengan current
page + 1
commit('SET_NEXTPAGE',
response.data.data.current_page + 1)

} else {

//commit ke mutation SET_NEXTEXISTS dengan false


commit('SET_NEXTEXISTS', false)
}

}).catch(error => {

//show error log dari response


console.log(error)

})
},

//action getLoadMore
getLoadMore({ commit }, nextPage) {

//get data campaign dengan page ke server


Api.get(`/campaign?page=${nextPage}`)
.then(response => {

//commit ke mutation SET_LOADMORE dengan response data


commit('SET_LOADMORE', response.data.data.data)

//console.log(response.data.data.data)

if (response.data.data.current_page <
response.data.data.last_page) {
//commit ke mutation SET_NEXTEXISTS dengan true
commit('SET_NEXTEXISTS', true)
//commit ke mutation SET_NEXTPAGE dengan current
page + 1
commit('SET_NEXTPAGE',
response.data.data.current_page + 1)

761
} else {

//commit ke mutation SET_NEXTEXISTS dengan false


commit('SET_NEXTEXISTS', false)
}

}).catch(error => {

//show error log dari response


console.log(error)

})
},

},

//getters
getters: {

export default campaign

Dari penambahan kode di atas, kita membuat 1 action baru dengan nama getLoadMore
yang mana di dalamnya akan melakukan fetching ke dalam Rest API
http://localhost:8000/api/campaign?page=. Dimana untuk nilai page akan diambil dari
parameter yang dikirim dari component.

//get data campaign dengan page ke server


Api.get(`/campaign?page=${nextPage}`)

Dan jika proses fetching berhasil, maka akan masuk ke dalam callback then dan di
dalamnya kita melakukan commit ke dalam mutation yang bernama SET_LOADMORE dan
isinya adalah data response yang di dapatkan dari Rest API.

//commit ke mutation SET_LOADMORE dengan response data


commit('SET_LOADMORE', response.data.data.data)

762
Dan di dalam mutation SET_LOADMORE kita melakukan push data yang kita dapatkan dari
Rest API ke dalam state campaigns.

//set state campaigns dengan data dari response loadmore


SET_LOADMORE(state, data) {
data.forEach(row => {
state.campaigns.push(row);
});
},

Langkah 6 - Uji Coba Fitur Loadmore


Sekarang silahkan refresh halaman homapege dan dengan catatan kita harus punya data
campaign yaang banyak agar bisa melihat fitur ini digunakan. Kurang lebih hasilnya seperti
berikut ini :

763
Konfigurasi Router untuk Category

Pada tahap kali ini kita semua akan belajar bagaimana cara membuat route untuk
menampilkan halaman category dan detail category . Dimana untuk halaman detail
category akan kita gunakan untuk menampilkan data-data camapign yang sesuai dengan
category yang sedang dibuka. Dan route ini bersifat global, yang artinya semua orang bisa
mengaksesnya tanpa perlu proses otentikasi.

Langkah 1 - Membuat View/Component Category


Sekarang kita akan membuat 2 file view/component yang nantinya akan kita gunakan untuk
menampilkan halman category dan detail category.

Silahkan buat folder baru dengan nama category di dalam folder src/views. Dan di
dalam folder category silahakn buat 2 file baru dengan nama Index.vue dan juga
Show.vue.

Sekarang kita lanjutkan untuk memberikan sample template di kedua file tersebut. Ini
bertujuan untuk memastikan apakah route yang akan kita buta nanti sudah berjalan atau
belum.

Silahkan buka file src/views/category/Index.vue kemudian masukkan kode berikut


ini :

764
<template>
<div class="pb-20 pt-20 text-center">
HALAMAN CATEGORY
</div>
</template>

<script>
export default {

}
</script>

Selanjutnya, silahkan buka file src/views/category/Show.vue dan masukkan kode


berikut ini :

<template>
<div class="pb-20 pt-20 text-center">
HALAMAN DETAIL CATEGORY
</div>
</template>

<script>
export default {

}
</script>

Langkah 2 - Membuat Route Category


Sekarang kita akan lanjutkan untuk mendefinisikan route baru untuk halaman category.
Silahkan buka file src/router/index.js dan ubah kode-nya menjadi seperti berikut ini :

//import vue router


import { createRouter, createWebHistory } from 'vue-router'

//import store vuex


import store from '@/store'

//define a routes
const routes = [

765
{
path: '/register',
name: 'register',
component: () => import( /* webpackChunkName: "register" */
'@/views/auth/Register.vue')
},
{
path: '/login',
name: 'login',
component: () => import( /* webpackChunkName: "login" */
'@/views/auth/Login.vue')
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import( /* webpackChunkName: "dashboard" */
'@/views/dashboard/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/donation',
name: 'donation.index',
component: () => import( /* webpackChunkName: "donationIndex" */
'@/views/donation/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/profile',
name: 'profile',
component: () => import( /* webpackChunkName: "profile" */
'@/views/profile/Index.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/profile/password',
name: 'profile.password',
component: () => import( /* webpackChunkName: "profilePassword"

766
*/ '@/views/profile/Password.vue'),
meta: {
//chek is loggedIn
requiresAuth: true
}
},
{
path: '/',
name: 'home',
component: () => import( /* webpackChunkName: "home" */
'@/views/home/Index.vue')
},
{
path: '/category',
name: 'category.index',
component: () => import( /* webpackChunkName: "categoryIndex" */
'@/views/category/Index.vue')
},
{
path: '/category/:slug',
name: 'category.show',
component: () => import( /* webpackChunkName: "categoryShow" */
'@/views/category/Show.vue')
},
]

//create router
const router = createRouter({
history: createWebHistory(),
routes // <-- routes,
})

//define route for handle authentication


router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
//cek nilai dari getters isLoggedIn di module auth
if (store.getters['auth/isLoggedIn']) {
next()
return
}
next('/login')
} else {
next()
}
})

767
export default router

Di atas, kita menambahkan 2 route baru, yaitu :

{
path: '/category',
name: 'category.index',
component: () => import( /* webpackChunkName: "categoryIndex" */
'@/views/category/Index.vue')
},
{
path: '/category/:slug',
name: 'category.show',
component: () => import( /* webpackChunkName: "categoryShow" */
'@/views/category/Show.vue')
},

Pertama, kita buat konfigurasi route untuk menampilkan index data category dan yang
kedua kita gunakan untuk menampilkan detail category, dimana untuk detail category kita
berikan parameter slug yang di dapatkan dari URL browser.

Langkah 3 -Mengaktifkan Link Category


Setelah berhasil membuat konfigurasi 2 route untuk category, sekarang kita lanjutkan untuk
mengaktifkan route tersebut di dalam component categoryHome. Silahkan buka file
src/components/CategoryHome.vue kemudian cari kode berikut ini :

<a href="#">
<div>
<img :src="category.image" width="40" class="inline-block mb-2">
</div>
{{ category.name.toUpperCase() }}
</a>

Kemudian kita ubah menjadi seperti berikut ini :

768
<router-link :to="{name: 'category.show', params:{slug:
category.slug}}">
<div>
<img :src="category.image" width="40" class="inline-block mb-2">
</div>
{{ category.name.toUpperCase() }}
</router-link>

Di atas, kita ubah yang semula menggunakan a href="#" menjadi router-link yang
kita arahkan ke dalam route yang bernama category.show dan di dalamnya kita berikan
parameter slug dari data category.

Selanjutnya, cari kode berikut ini :

<a href="#">
<div>
<img src="@/assets/images/menu.png" width="40" class="inline-block
mb-2">
</div>
LAINNYA
</a>

Kemudian ubah menjadi seperti berikut ini :

<router-link :to="{name: 'category.index'}">


<div>
<img src="@/assets/images/menu.png" width="40" class="inline-block
mb-2">
</div>
LAINNYA
</router-link>

Di atas kita ubah yang semula menggunakan a href="#" menjadi router-link dan kita
arahkan ke dalam route yang bernama category.index.

Langkah 4 - Uji Coba Route Category


Sekarang silahkan klik salah satu category yang ada di halaman homepage dan jika berhasil

769
kita akan diarahkan ke dalam halaman detail category. Kurang lebih seperti berikut ini :

kemudian silahkan klik menu Lainnya di halaman homepage, maka kita akan di arahkan ke
dalam halaman index category. Kurang lebih seperti berikut ini :

770
DEPLOYMENT

771
Deploy Project Laravel di Shared Hosting (cPanel)

Setelah menyelesaikan semua materi di dalam ebook, baik dari sisi backend (Laravel) dan
frontend (Vue 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://)

Kupon Diskon Hosting di RiauCyberSoluion.net / HelloWorldHost.com


Link pembelian Hosting :https://riaucybersolution.net/cart.php?gid=58
Kupon/Voucher : SantriKodingFullstack
CATATAN! : Minimal pembelian Hosting 3 bulan

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://donasi.appdev.my.id. Untuk nama subdomain ini sifatnya bebas dan silahkan
disesuaikan dengan keinginan masing-masing.

Langkah 1 - Membuat Subdomain


Sampai disini saya kira teman-teman semuanya sudah mempunyai domain dan hosting
untuk proses pembelajaran, pertama kita akan belajar bagaimana membuat subdomain
baru di dalam cPanel..

Silahkan buka cPanel dengan mengetikan http://namadomain.com/cpanel, jika berhasil


maka akan muncul halaman login dari cPanel kurang lebih seperti berikut ini :

772
CATATAN! : silahkan ganti namadomain.com sesuai dengan domain yang teman-teman
miliki.

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 :

773
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 donasi dan untuk domain
yang digunakan adalah appdev.my.id. Maka nanti hasilnya seperti berikut ini :
https://donasi.appdev.my.id.

Langkah 2 - Export Database di Localhost


Sekarang kita akan melakukan export database yang ada di lokol komputer kita. Silahkan
buka http://localhost/phpmyadmin, kemudian pilih database yang akan di export.

774
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 :

775
Silahkan buat nama database terlebih dahulu, di atas saya contohkan untuk nama database-
nya adalah appdev_donasi, 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 donasi, 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.

776
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.

777
Langkah 4 - Upload Project Laravel
Setelah kita berhasil import database di cPanel, sekarang kita lanjutkan untuk upload
project website donasi online-nya, Langsung saja kita mulai.

Pertama silahkan compress projectnya ke format zip terlebih dahulu, sebelum itu kita harus
melakukan compile ke mode production agar file-file CSS yang di hasilkan ukurannya
menjadi kecil. Silahkan jalankan perintah berikut ini di dalam terminal/CMD :

npm run production

Setelah proses compile berhasil, 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.

778
779
Di atas, silahkan upload project dengan format zip. Jika sudah berhasil terupload, kurang
lebih seperti berikut ini :

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 :

780
Di atas kita akan extract projectnya dengan nama backend-donasi kemudian silahkan
tunggu proses extracting sampai selesai. Kemudian klik reload di filemanager, maka kita
sudah mendapatkan sebuah folder dengan nama backend-donasi kemudian buka folder
tersebut maka kita akan mendapatkan folder lagi dengan nama backend-donasi.
Kemudian masuk ke folder tersebut dan itulah project kita.

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.

Untuk menampilkannya kita bisa seperti berikut ini :

781
Disini kita akan melakukan move data 2 kali, silahkan masuk ke folder backend-
donasi/backend-donasi/public kemudian select all file dan folder kemudian
pilih move. Kurang lebih seperti berikut ini :

782
Di atas kita move ke dalam folder /donasi.appdev.my.id.

Setelah itu kita juga akan move lagi semua file yang ada di dalam folder backend-donasi
ke dalam folder backend-donasi yang pertama. Jadi kita keluarkan isi dari folder
backend-donasi yang di dalam ke folder backend-donasi yang luar.

783
Sekarang, kita lanjutkan untuk merubah beberapa kode di dalam file index.php di folder
donasi.appdev.my.id, silahkan masuk ke donasi.appdev.my.id kemudian pilih file
index.php klik kanan dan klik edit. Kurang lebih seperti berikut ini :

784
Ubah di bagian autoload.php menjadi seperti berikut ini :

require __DIR__.'/../backend-donasi/vendor/autoload.php’;

Kemudian ubah juga bagian app.php menjadi seperti beirkut ini :

$app = require_once __DIR__.'/../backend-donasi/bootstrap/app.php’;

Kemudian kita juga akan merubah beberapa kode lagi untuk App Service Providers,
silahkan ikuti gambar di bawah ini :

785
Dari gambar di atas, pada bagian function boot, silahkan tambahkan kode berikut ini :

$this->app->bind('path.public', function() {
return base_path().'/../donasi.appdev.my.id';
});

Jadi di atas kita set untuk base path dari project kita adalah folder
donasi.appdev.my.id.

786
Setelah itu kita akan lanjutkan untuk konfigurasi file .env untuk mengatur koneksi database
kita, silahkan buka file .env di dalam folder backend-donasi. Kemudian klik kanan dan
edit.

Langkah 6 - Menjalankan Storage:link Laravel


Sebelum kita menjalankan storage:link atau symlink, kita harus hapus symlink yang
lama bawaan dari localhost terlebih dahulu. Silahkan masuk ke folder
donasi.appdev.my.idl. kemudian klik kanan pada folder storage dan delete.

787
Sekarang kita lanjutkan untuk menjalankan php artisan storage:link di cPanel,
silahkan buka menu terminal di halaman utama cPanel, kurang lebih seperti berikut ini :

788
Sebelum menjalankan perintah berikut ini, silahkan perhatikan gambar di atas, silahkan
ganti appdev dengan username cPanel kalian dan ganti donasi.appdev.my.id dengan
subdomain atau domain kalain. Setelah itu jalankan perintah berikut ini di dalam terminal :

ln -s /home/username_cpanel/backend-donasi/storage/app/public
/home/username_cpanel/subdomain_kalian.com/storage

Langkah 7 - Mencoba Project


Sekarang jika kita coba jalankan project admin dari donasi onlin-nya, dalam studi kasus kali
ini saya menggunakan nama domain https://donasi.appdev.my.id/ dan kurang lebih hasilnya
seperti berikut ini :

789
790
Deploy Project Vue Js di Netlify

Pada tahap kali ini kita semua akan belajar bagaimana cara melakukan deployment project
Vue Js di dalam Netlify. Jadi apa itu Netlify ?

Netlify merupakan salah satu layanan build tools yang memiliki fitur CI/CD atau Continue
Integration dan Continue Development. Dengan Netlify kita dapat men-deploy website
static 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 Netlify untuk proses deployment secara otomatis. Ketika kita
merubah source code dan kita push ulang di GitHub maka Netlify juga akan melakukan
CI/CD dari perubahan yang dilakukan di dalam repository.

Langkah 1 - Purge CSS untuk Tailwind CSS


Pertama kita akan melakukan konfigurasi Purge CSS yang digunakan untuk meminify class-
class dari Tailwind yang memang dibutuhkan dan yang tidak dibutuhkan tidak akan ikut di
compile. Dengan konsep seperti ini, maka file CSS yang di hasilkan dari Tailwind akan
menjadi sangat kecil dan load halaman dari website kita juga cepat.

Silahkan buka file tailwind.config.js di dalam project Vue Js, kemudian ubah kode-nya
menjadi seperti berikut ini :

791
module.exports = {
purge: [
'./src/**/*.vue',
],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}

Di atas, kita set untuk mengambil class-class yang di butuhkan di dalam folder src dan
yang memiliki extensi .vue.

purge: [
'./src/**/*.vue',
],

Langkah 2 - Menambahkan File Redirect


Sekarang kita akan menambahkan file _redirects di dalam project Vue Js terlebih dahulu,
karena file ini akan berfungsi untuk mengizinkan routing static yang ada di dalam project
Vue Js.

Silahkan buat file baru dengan nama _redirects di dalam folder public Vue Js. Dan
kemudian masukkan kode berikut ini :

/* /index.html 200.

Langkah 3 - Membuat Repository di GitHub


Silahkan register atau login di situs resmi GitHub yaitu : https://github.com kemudian
silahkan buat repository baru https://github.com/new dengan jenis private, seperti gambar

792
berikut ini :

Dari konfigurasi di atas, silahkan disesuaikan sendiri untuk nama dan deskripsi dari project
Vue Js kita di GitHub. Setelah berhasil membuat repository langkah selanjutnya kita akan
upload project Vue Js kita di GitHub.

Langkah 4 - Ubah Endpoint API GitHub


Sebelum upload/push project Vue Js ke GitHub, disini kita akan lakukan perubahan endpoint
di dalam global API di dalam Vue Js. Silahkan buka file di dalam project Vue Js yaitu
src/api/Api.js dan kemudian ubah bagian ini :

//set default endpoint API


baseURL: 'http://localhost:8000/api'

Ubah menjadi seperti berikut ini :

//set default endpoint API


baseURL: 'https://donasi.appdev.my.id/api'

Di atas, kita ubah endpoint dari localhost ke dalam nama domain yang kita gunakan untuk
deploy project Laravel sebelumnya. Jadi silahkan disesuaikan dengan nama domain masing-
masing.

793
Langkah 5 - Upload Project ke GitHub
Sekarang kita lanjutkan untuk melakukan upload atau push project Vue Js kita ke GitHub.
Silahkan ikuti dan jalankan perintah berikut ini di dalam project Vue Js.

git init

Perintah di atas digunakan untuk menginisialisasi git di dalam project kita.

git add .

Perintah di atas digunakan untuk menambahkan semua file dan folder ke dalam git.

git commit -m "initial commit"

Perintah di atas digunakan untuk memberikan komentar dari file-file dan folder yang kita
tambahkan di atas, kita bisa mengganti kata initial commit dengan sesuai keinginan.

git remote add origin https://github.com/maulayyacyber/donasi-online.git

Perintah di atas digunakan untuk menambahkan alamat origin dengan repository yang
sudah kita buat. Di atas contohnya untuk alamat repository saya adalah
https://github.com/maulayyacyber/donasi-online.git, maka silahkan
disesuaikan dengan alamat dari repository masing-masing.

git push origin master

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 terupload, maka hasilnya kurang lebih seperti berikut ini :

794
Langkah 6 - Deployment Dengan Netlify
Sekarang kita lanjutkan untuk proses deployment di Netlify, silahkan buka situs resi Netlify
di https://netlify.com kurang lebih seperti berikut ini :

Silahkan Login atau Sign up di Netlify, disini kita bisa dengan mudah Sign up
menggunakan akun dari GitHub, kurang lebih seperti berikut ini :

795
Setelah berhasil Sign up, sekarang kita bisa lanjutkan untuk proses deployent, silahkan klik
tabs Sites kemudian klik New site from Git.

Kemudian silahkan pilih Git host yang akan kita gunakan, disini kita gunakan GitHub.

796
Sekanjutnya kita akan membuat perintah untuk melakukan deployment project Vue Js di
dalam Netlify, silahkan ikuti langkah-langkahnya dari gambar berikut ini :

797
Kemudian proses deployment sudah berjalan dan kita tinggal menggu saja disini, dan nanti
otomatis kita akan mendapatkan alamat domain secara random dari Netlify. Tapi jangan
kawatir kita juga bisa meng-custom domain tersebut.

Setelah berhasil , untuk contoh disini saya mendapatkan alamat domain


https://nervous-colden-ac00e7.netlify.app/, kurang lebih tampilannya seperti berikut ini :

798
Untuk yang ingin custom domain dengan domain pribadi bisa klik di domain setting dan
silahkan disesuaikan langkah-langkahnya dari Netlify. Disini saya custom domain akan
tetapi masih menggunakan subdomain dari Netlify, yaitu : https://donasi-online.netlify.app/.
Teman-teman bisa custom nama domain apapun, misalnya dengan .com, .net, .xyz dan
lain-lain.

799
Konfigurasi Notifikasi Handler Midtrans

Pada tahap kali ini kita akan belajar untuk melakukan konfigurasi notifikasi handler dari
Midtrans. Notifikasi ini merupakan endpoint yang akan kita set di dalam dashboard Midtrans,
yang fungsinya adalah untuk menerima status pembayaran yang dilakukan di dalam
Midtrans dan meng-update status tersebut ke dalam database kita sesuai dengan response
yang di dapatkan.

Sekarang silahkan login ke akun Midtrans dan untuk Environment silahkan pilih yang
Sandbox, karena kita masih di dalam mode development atau testing.

Selanjutnya, silahkan klik menu SETTINGS dan pilih CONFIGURATION.

800
Di atas, kita harus mengatur 5 endpoint, yang digunakan untuk menerima notifikasi dari
Midtrans dan action redirect dari Midtrans.

Silahkan isi konfigurasi di atas kurang lebih seperti berikut ini :

attribute value keterangan

Alamat dimana
Midtrans akan
Payment
mengirimkan
Notification https://donasi.appdev.my.id/api/donation/notification
notifikasi melalui
URL*
request HTTP
POST.

Alamat dimana
Midtrans akan
Recurring mengirimkan
Notification https://donasi.appdev.my.id/api/donation/notification notifikasi
URL* berulang melalui
permintaan HTTP
POST.

Finish Halamanan ketika


Redirect https://donasi-online.netlify.app/donation pembayaran
URL* berhasil

Unfinish Halamanan ketika


Redirect https://donasi-online.netlify.app/donation pembayaran
URL* belum di proses

801
attribute value keterangan

Error Halamanan ketika


Redirect https://donasi-online.netlify.app/donation pembayaran
URL* error/gagal
CATATAN! : Silahkan sesuaikan dengan nama domain masing-masing.

Sekarang, jika kita melakukan donasi di dalam website dan melakukan pembayaran, maka
di database website kita juga akan ikut terupdate sesuai dengan status yang dikirimkan oleh
Midtrans.

802
PENUTUP

803
Source Code

Untuk link unduh project silahkan bias mengunjungi link berikut ini :

Laravel (BackEnd) :
https://drive.google.com/file/d/1bWMdDFeN5nae7eitU_6BNlH8bCrSz5KZ/view
Vue Js (FrontEnd) :
https://drive.google.com/file/d/19aAA8niF7gEXtncsDIc_YLc5TiuwZ24h/views

804
Kesimpulan

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."

805

Anda mungkin juga menyukai