PENDAHULUAN ................................................................................................................... 6
PERANCANGAN ................................................................................................................ 11
DATABASE ......................................................................................................................... 48
6
Kata Pengantar
Bismillahirrahmannirrahiim.
Alhamdulillah, segala puji dan syukur penulis panjatkan kehadirat Tuhan Yang Maha Esa.
Karena berkat limpahan karunia-Nya, kami dapat menyelesaikan penulisan buku
Membangun Website 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.
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.
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.
14
Dari diagram di atas Vue Js (Frontend) memiliki 2 konsep, yaitu untuk membuat halaman
Dashboard / User dan halaman Front Web.
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 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
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.
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.
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 :
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 :
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
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 ..
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', '', '.');
}
}
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;
echo $hasil;
?>
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"
]
},
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.
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.
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.
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: [],
}
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
]);
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.
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
],
}
@tailwind base;
@tailwind components;
@tailwind utilities;
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=
DB_DATABASE=db_backend_donasi
DB_USERNAME=root
DB_PASSWORD=
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.
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',
];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
'avatar' // <-- tambahkan ini
];
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 :
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.
54
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* 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.
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 :
Dari penambahan kode di dalam file migration di atas, kita menambahkan 8 attribute,
diantaranya adalah :
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;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'title', 'slug', 'category_id', 'target_donation', 'max_date',
'description', 'image', 'user_id',
];
}
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 :
Dari penambahan kode file migration di atas, kita menambahkan 6 attribute baru,
diantaranya adalah :
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;
/**
* 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.
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 :
Dari penambahan kode di atas, kita menambahkan 7 attribute untuk table donations,
yaitu :
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.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* 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.
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 :
62
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* 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.
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
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
3 Terima Kasih 3
field foreign key post_id di dalam table comments dapat menyimpan id yang sama dari
table posts, dengan begini maka relasi one-to-many dapat terpenuhi.
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
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.
69
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* 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;
/**
* 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);
}
}
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;
/**
* 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;
/**
* 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.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* 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;
/**
* 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);
}
77
menjadi seperti berikut ini :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'email', '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;
/**
* 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);
}
80
bahwa model Donation atau table donations ini dimiliki dan terhubung dengan Model
Donatur atau table donaturs.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* 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.
get{namaAttribute}Attribute
83
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
Di atas, kita membuat sebuah function dengan nama getNameAttribute dan di dalamnya
kita lakukan return dengan menjadikannya sebagai huruf besar menggunakan strtouper.
Jika kita asumsikan mempunya data user dengan attribute name Fika Ridaul Maulayya,
dan jika kita coba panggil kurang lebih seperti berikut ini :
use App\Models\User;
$user = User::find(1);
$name = $user->name;
Jika di jalankan, maka akan menghasilkan seperti ini : FIKA RIDAUL MAULAYYA.
84
set{namaAttribute}Attribute
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
Setiap kita melakukan insert data name ke dalam database, maka akan di format menjadi
huruf kecil semuanya.
array
boolean
collection
date
datetime
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;
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.
86
memudahkan kita dalam pemanggilan gambar nantinya.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* 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);
}
}
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
89
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* 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);
}
}
90
storage/categories.
<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\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');
}
}
92
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* 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;
}
}
94
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* fillable
*
* @var array
*/
protected $fillable = [
'image', 'link'
];
/**
* getImageAttribute
*
* @param mixed $image
* @return void
*/
public function getImageAttribute($image)
{
return asset('storage/sliders/'.$image);
}
}
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;
/**
* 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;
}
}
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.
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;
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 :
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 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.
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.
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.
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.
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');
});
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.
<?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;
/**
* 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);
/**
* 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');
});
'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.
protected $hidden = [
'password',
'remember_token',
];
protected $hidden = [
'password',
'remember_token',
'two_factor_secret',
'two_factor_recovery_codes'
];
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).
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.
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>
113
</div>
@enderror
</label>
<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.
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.
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 :
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.
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
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}"
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 :
@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.
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.
124
resources/views/auth dan masukkan kode berikut ini :
@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>
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>
<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.
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.
<!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>
<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>
<span class="mx-3">Kategori</span>
</a>
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>
<span class="mx-3">Donaturs</span>
</a>
<span class="mx-3">Donation</span>
</a>
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">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 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>
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;
//donatur
$donaturs = Donatur::count();
//campaign
$campaigns = Campaign::count();
//donations
$donations = Donation::where('status',
'success')->sum('amount');
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 :
//donatur
$donaturs = Donatur::count();
//campaign
$campaigns = Campaign::count();
//donations
$donations = Donation::where('status', 'success')->sum('amount');
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
*/
//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.
//route dashboard
Route::get('/dashboard', [DashboardController::class,
'index'])->name('admin.dashboard.index');
@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="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="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.
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 :
{{ $donaturs }}
{{ $campaigns }}
{{ 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.
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.
<?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;
145
*
* @return void
*/
public function index()
{
$categories = Category::latest()->when(request()->q,
function($categories) {
$categories = $categories->where('name', 'like', '%'.
request()->q . '%');
})->paginate(10);
//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
]);
} else {
147
//hapus image lama
Storage::disk('local')->delete('public/categories/'.basename($category->
image));
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;
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.
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 :
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());
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 :
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 :
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') == '') {
}else{
Jika request file dengan nama image tidak memiliki value, maka otomatis kita akan
melakukan proses update data category tanpa gambar.
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.
Setelah gambar yang lama berhasil dihapus dari folder storage Laravel, sekarang kita
lanjutkan untuk melakukan proses upload gambar yang baru.
154
Setelah gambar yang baru berhasil diupload, sekarang kita lanjutkan untuk melakukan
proses update data category dengan gambar yang terbaru.
Di atas, kita melakukan proses update data category berdasarkan ID, kurang lebih seperti
berikut ini :
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.
//hapus gambar
Storage::disk('local')->delete('public/categories/'.basename($category->
image));
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'
]);
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 :
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 :
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 :
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>
@section('content')
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-300">
<div class="container mx-auto px-6 py-8">
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>
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.
Dan untuk pencarian, pada form action kita arahkan ke dalam route yang bernama
admin.category.index, dengan menggunakan method GET.
Kemudian untuk menampilkan data category, kita menggunakan sintaks dari blade yaitu
forelse. Kurang lebih seperti berikut ini :
@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 :
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) {
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.
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.
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 :
@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>
<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>
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.
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.
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.
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 :
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.
@section('content')
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-300">
<div class="container mx-auto px-6 py-8">
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>
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.
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.
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;
173
$campaigns = $campaigns->where('title', 'like', '%'.
request()->q . '%');
})->paginate(10);
//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'
]);
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 {
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;
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.
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.
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 :
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 :
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 :
182
KEY VALIDASI KETERANGAN
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.
}else{
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.
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.
Setelah gambar lama berhasil terhapus, selanjutnya kita melakukan upload untuk gambar
yang baru :
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.
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.
//hapus gambar
Storage::disk('local')->delete('public/campaigns/'.basename($campaign->i
mage));
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'
]);
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 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 :
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.
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>
@section('content')
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-300">
<div class="container mx-auto px-6 py-8">
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>
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.
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.
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
@endforelse
Untuk edit data campaign, kita akan memanggil route yang bernama
admin.campaign.edit dan ditambahkan sebuah parameter berupa ID dari data
campaign.
Dan untuk proses delete data campaign, kita akan menggunakan AJAX dengan
dikombinasikan Sweet Alert2. Kurang lebih seperti berikut ini :
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) {
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') }}
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 :
@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>
<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>
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.
Kemudian untuk form action kita arahkan ke dalam sebuah route yang bernama
admin.campaign.store dan di dalam form ini kita menggunakan method POST.
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 :
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.
@section('content')
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-300">
<div class="container mx-auto px-6 py-8">
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>
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.
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.
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.
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;
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;
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.
//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 :
Setelah menambahkan route untuk donatur, selanjutnya kita akan mengaktifkan menu
sidebar admin agar saat di klik akan menuju ke halaman donatur.
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 :
209
resources/views/admin. Dan di dalam folder donatur silahkan buat file baru dengan
nama index.blade.php dan masukkan kode berikut ini :
@section('content')
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-300">
<div class="container mx-auto px-6 py-8">
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>
</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.
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 :
@endforelse
{{ $donaturs->links('vendor.pagination.tailwind') }}
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.
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;
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;
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;
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 :
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.
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.
//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.
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>
@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)
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.
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
@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.
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.
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.
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;
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.
//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 :
227
Setelah menambahkan route untuk profile, selanjutnya kita akan mengaktifkan menu
sidebar admin agar saat di klik akan menuju ke halaman profile.
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>
@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>
@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>
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>
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()))
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 :
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>
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.
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
Kemudian, untuk update profile, form action akan kita arahkan ke sebuah route dengan
nama user-profile-information.update.
Dan untuk form action yang digunakan untuk update password, kita arahkan ke route yang
bernama user-password.update.
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 :
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
<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 :
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.
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 :
@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>
<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.
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;
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;
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.
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 :
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 :
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.
//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'
]);
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 :
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.
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 :
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 :
@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>
<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>
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.
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
@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.
//ajax delete
function destroy(id) {
// kode ajax delete + konfirmasi delete dengan sweet alert2
}
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, 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]
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 :
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.
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.
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.
266
Selanjutnya, silahkan jalankan perintah berikut ini untuk membuat encryption key.
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;
/**
* 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;
}
}
use Laravel\Passport\HasApiTokens;
Kemudian kita gunakan Traits tersebut menggunakan use di dalam class Donatur.
269
<?php
namespace App\Providers;
/**
* 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,
],
],
'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',
// ],
],
'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'
],
],
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;
Dari perubahan kode di atas, pertama kita import Auth dari Laravel.
Kemudian kita ubah default extend class dari Model menjadi 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.
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;
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;
//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 :
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
277
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
/**
* Api Register
*/
Route::post('/register', [RegisterController::class, 'register']);
/**
* Api Register
*/
Route::post('/register', [RegisterController::class, 'register']);
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
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.
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;
281
'password' => 'required'
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}
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;
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'
]);
283
KEY VALIDASI KETERANGAN
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 :
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.
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!',
]);
}
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']);
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.
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;
288
$categories = Category::latest()->paginate(12);
if($category) {
289
$categories = Category::latest()->take(3)->get();
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;
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.
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.
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) {
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 :
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.
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);
/**
* APi Category
*/
Route::get('/category', [CategoryController::class, 'index']);
Route::get('/category/{slug}', [CategoryController::class, 'show']);
Route::get('/categoryHome', [CategoryController::class,
'categoryHome']);
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.
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;
296
$campaigns = $campaigns->where('title', 'like', '%'.
request()->q . '%');
})->latest()->paginate(5);
if($campaign) {
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;
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.
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 :
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.
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 :
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) {
Tapi, jika variable $campaign bernailai false artinya data campaign tidak ditemukan di
dalam database, maka akan mengembalikan sebuah response failed atau 404.
/**
* Api Campaign
*/
Route::get('/campaign', [CampaignController::class, 'index']);
Route::get('/campaign/{slug}', [CampaignController::class, 'show']);
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.
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.
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;
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;
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 :
/**
* Api Slider
*/
Route::get('/slider', [SliderController::class, 'index']);
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.
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;
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);
}
$donatur->update([
'name' => $request->name,
'avatar' => $image->hashName()
]);
308
]);
}
/**
* 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)
]);
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;
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.
$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 :
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 :
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 :
/**
* 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.
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
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 :
KEY VALUE
Accept application/json
Content-Type application/json
Selanjutnya, silahkan klik tab Body dan pilih form-data dan masukkan beberaya key dan
value berikut ini :
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 :
KEY VALUE
Accept application/json
Content-Type application/json
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.
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.
319
Setelah itu, sekarang klik menu SETTINGS > ACCESS KEYS, maka sekarang kita sudah
mendapatkan kredensial Merchant ID, Client Key dan Server Key.
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.
MIDTRANS_SERVERKEY=paste_server_key_midtrans_disini
MIDTRANS_CLIENTKEY=paste_client_key_midtrans_disini
Setelah itu kita akan tambahkan config, dimana config ini akan kita gunakan untuk
mendapatkan data dari konfigurasi Midtrans dari file .env di atas.
//midtrans
'midtrans' => [
// Midtrans server key
'serverKey' => env('MIDTRANS_SERVERKEY'),
// Midtrans client key
'clientKey' => env('MIDTRANS_CLIENTKEY'),
// Isi false jika masih tahap development dan true jika sudah di
production, default false (development)
'isProduction' => env('MIDTRANS_IS_PRODUCTION', false),
'isSanitized' => env('MIDTRANS_IS_SANITIZED', true),
'is3ds' => env('MIDTRANS_IS_3DS', true),
]
Jika file config/services.php di tulis secara lengkap, maka kurang lebih seperti berikut
ini :
<?php
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:
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 :
Silahkan jalankan perintah di bawah ini di dalam terminal/CMD untuk membuat controller
baru :
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;
/**
* index
*
* @return void
*/
public function index()
{
//get data donations
$donations = Donation::with('campaign')->where('donatur_id',
auth()->guard('api')->user()->id)->latest()->paginate(5);
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);
$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',
]);
327
'first_name' =>
auth()->guard('api')->user()->name,
'email' =>
auth()->guard('api')->user()->email,
]
];
$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') {
if($fraud == 'challenge') {
/**
* update invoice to pending
*/
$data_donation->update([
'status' => 'pending'
]);
} else {
/**
* update invoice to success
*/
$data_donation->update([
'status' => 'success'
]);
/**
* update invoice to success
*/
$data_donation->update([
'status' => 'success'
]);
} elseif($transaction == 'pending'){
/**
* update invoice to pending
*/
$data_donation->update([
'status' => 'pending'
329
]);
/**
* update invoice to failed
*/
$data_donation->update([
'status' => 'failed'
]);
/**
* update invoice to expired
*/
$data_donation->update([
'status' => 'expired'
]);
/**
* 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;
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 :
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 :
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.
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 :
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 :
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 :
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.
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 == 'pending'){
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.
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.
KEY VALUE
Accept application/json
Content-Type application/json
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
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.
341
VERSI TANGGAL JUDUL
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>
<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".
343
Vue.createApp({
// ...
})
data() {
return {
message: 'Hello World!'
}
}
Pada Vuejs, kita menggunakan tanda kurung {{ ... }} untuk menampilkan isi variabel di
dalam template.
.mount('#app')
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>
</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.
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
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
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() {
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
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.
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.
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.
Sekarang, kita akan belajar tentang Reactivity API ref, kurang lebih seperti berikut ini :
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.
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>
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>
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 ?
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.
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'
Sekarang, kita dapat mengakses state name dan gender langsung secara global di dalam
semua component aplikasi. Contoh menggunakan state di dalam component kita bisa
seperti berikut ini :
computed: {
name() {
return this.$store.state.name // state "name"
}
gender() {
return this.$store.state.gender // state "gender"
}
}
Dan untuk menampilkan di template, kita bisa menggunakan kode seperti berikut ini :
<h1>Nama Lengkap: {{ name }}</h1> <!-- output "Fika Ridaul Maulayya" -->
Getters
Getters digunakan untuk mendapatkan data dari state, lalu apa bedanya dengan
mengakses state langsung ? bukankah kita bisa langsung mengambil data dari state seperti
yang di lakukan di dalam contoh di atas ?. Jadi dengan menggunakan Getters kita bisa
mengolah data yang ada di dalam state sebelum digunakan di dalam component. Kurang
lebih contohnya seperti berikut ini :
373
import { createStore } from 'vuex'
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 :
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.
375
import { createStore } from 'vuex'
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 :
376
import { createStore } from 'vuex'
Di atas, kita mempunyai actions yang bernama increment, yang mana di dalamnya
melakukan sebuah commit ke dalam mutation yang bernama SET_COUNT dan di dalam
SET_COUNT kita menambah nilai dari state count sebanyak 1.
this.$store.dispatch('increment')
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.
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
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 :
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.
<!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>
<div id="app">
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
}
]
}
})
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.
//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.
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.
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>
<div id="app">
</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: {
})
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 :
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>
<div id="app">
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: {
})
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)
}
},
})
//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 :
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 = ''
}
}
394
this.$store.dispatch('addTodo', this.todo) // <-- dispatch to "addTodo"
with value "this.todo"
//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>
396
<div id="app">
</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) {
},
//action changeTodo
changeTodo({ commit }, data) {
},
398
//getters
getters: {
})
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)
},
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.
changeTodo(todo) {
//call action "changeTodo" on vuex
this.$store.dispatch('changeTodo', todo)
//action changeTodo
changeTodo({ commit }, 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 ?
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.
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)
Untuk installasi Vue CLI secara global, silahkan teman-teman jalankan perintah di bawah ini
:
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
:
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.
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
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.
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
Silahkan tunggu proses installasi berjalan sampai selesai dan jangan lupa pastikan selalu
terhubung dengan internet.
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.
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.
Silahkan di tunggu proses installasinya sampai selesai dan pastikan harus terhubung dengan
internet.
412
Silahkan jalankan perintah di bawah ini untuk menambahkan Vue Content Loader di
dalam project Vue Js kita.
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.
Untuk menambahkan library ini di dalam project Vue, silahkan jalankan perintah berikut ini
di dalam terminal/CMD dan di dalam project Vue Js kita.
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";
app.mount('#app')
/**
* 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.
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.
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 Toastr
*/
import Toast from "vue-toastification";
import "vue-toastification/dist/index.css";
/**
* Tailwind CSS
*/
import './index.css'
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.
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.
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)
/**
*
* @param {*} maxDate
*/
countDay(maxDate) {
420
if(result < 0) {
return 0
}
return result
}
},
}
function formatPrice
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 :
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)
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.
423
countDay(maxDate) {
if(result < 0) {
return 0
}
return result
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'
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.
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 :
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>
430
<template>
<div>
<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.
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 />
</div>
</template>
<script>
export default {
components: {
Header,
Footer
}
}
</script>
<style>
</style>
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 />
</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'
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.
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
438
<template>
<div class="pb-20 pt-20 text-center">
HALAMAN REGISTER
</div>
</template>
<script>
export default {
}
</script>
<template>
<div class="pb-20 pt-20 text-center">
HALAMAN LOGIN
</div>
</template>
<script>
export default {
}
</script>
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,
})
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')
}
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 :
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.
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'
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.
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.
//import vuex
import { createStore } from 'vuex'
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: {
//...
}
449
const auth = {
//state
state: {
},
//mutations
mutations: {
},
//actions
actions: {
},
//getters
getters: {
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.
//import vuex
import { createStore } from 'vuex'
modules: {
auth, // <-- module auth
}
})
451
Di atas, pertama kita import module auth dari folder module.
modules: {
auth, // <-- module auth
}
/**
* 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'
app.mount('#app')
/**
* Vuex
*/
import store from './store'
Kemudian, kita gunakan plugin use agar Vuex dapat digunakan secara global di dalam
aplikasi Vue.
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.
<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>
<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>
</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()
//define variable
let name = user.name
let email = user.email
let password = user.password
let password_confirmation = user.password_confirmation
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
}
</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.
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()
Selanjutnya, kita akan membuat sebuah function yang bernama register, function ini
akan di jalankan ketika form di submit.
//...
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
Kemudian, kita lakukan return semua state dan function yang ada di dalam Composition
API agar dapat digunakan di dalam template.
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">
const auth = {
//state
state: {
},
//mutations
mutations: {
463
state.user = user // <-- assign state "user" dengan
response data "user"
},
},
//actions
actions: {
//action register
register({ commit }, user) {
})
.then(response => {
464
commit('AUTH_SUCCESS', token, user)
}).catch(error => {
})
})
},
},
//getters
getters: {
Dari penambahan kode di atas, pertama kita import konfiguasri global API, ini akan kita
gunakan sebagai baseURL endpoint Laravel.
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: {
},
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.
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', {
})
Jika proses pengiriman data di atas berhasil, maka akan masuk di dalam callback then dan
isinya sebagai berikut :
.then(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.
Dan selanjutnya kita konfigurasi Header Axios Authorization dengan value 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.
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: {
},
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.
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.
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.
<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">
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')
})
}
}
</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.
Setelah itu, agar state di atas dapat dipanggil di dalam component, maka kita harus
melakukan return terlebih dahulu. Kurang lebih seperti berikut ini :
Dan untuk memanggil data user di template, kita bisa menggunakan kode seperti berikut ini
:
Kode di atas akan menampilkan foto user yang sedang login. Dan kode berikut ini untuk
menampilkan nama user yang sedang login.
{{ user.name }}
476
const auth = {
//state
state: {
},
//mutations
mutations: {
},
//actions
actions: {
//action register
register({ commit }, user) {
477
//send data ke server
Api.post('/register', {
})
.then(response => {
}).catch(error => {
478
reject(error.response.data)
})
})
},
//action getUser
getUser({ commit }) {
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: {
//loggedIn
isLoggedIn(state) {
return state.token // return dengan data token
},
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.
//...
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.
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.
//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,
})
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.
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.
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.
<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">
<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>
</div>
</div>
</div>
</template>
<script>
//hook vuex
486
import { useStore } from 'vuex'
//hook Toast
import { useToast } from "vue-toastification"
export default {
setup() {
//store vuex
const store = useStore()
//vue router
const router = useRouter()
//mounted
onMounted(() => {
//panggil action "getUser" dari module "auth" vuex
store.dispatch('auth/getUser')
})
//method logout
function logout() {
487
toast.success("Logout Berhasil!")
})
}
}
</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.
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()
Dan jika kita perhatikan, kita menambahkan sebuah event @click di dalam template yang
mengarah ke dalam sebuah function yang bernama logout.
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.
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(() => {
toast.success("Logout Berhasil!")
})
Dan agar function logout dapat di gunakan di dalam component, maka kita harus
melakukan return terlebih dahulu.
const auth = {
//state
state: {
490
informasi tentang token
token: localStorage.getItem('token') || '',
},
//mutations
mutations: {
},
//actions
actions: {
//action register
register({ commit }, user) {
491
register
name: user.name,
email: user.email,
password: user.password,
password_confirmation:
user.password_confirmation
})
.then(response => {
}).catch(error => {
})
492
})
},
//action getUser
getUser({ commit }) {
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
})
},
},
//getters
getters: {
493
},
//loggedIn
isLoggedIn(state) {
return state.token // return dengan data token
},
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.
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.
494
Kemudian, kita lakukan delete header Authorization dari Axios.
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.
<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>
</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()
//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
498
}
})
}
//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.
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()
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
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">
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 :
const auth = {
//state
state: {
},
//mutations
mutations: {
//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) {
})
.then(response => {
505
//commit auth success ke mutation
commit('AUTH_SUCCESS', token, user)
}).catch(error => {
})
})
},
//action getUser
getUser({ commit }) {
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
})
},
//action login
login({ commit }, user) {
507
//reject ke component dengan hasil response
reject(error.response.data)
})
})
},
//getters
getters: {
//loggedIn
isLoggedIn(state) {
return state.token // return dengan data token
},
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.
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.
Kemudian kita set sebuah localStorage dengan key token dan user, yang isinya adalah
variable-variable di atas.
Dan kita set default header Axios dengan Authorization yang isinya adalah Bearer +
509
Token.
Setelah itu, kita melakukan 2 commit ke dalam mutation yang bernama AUTH_SUCCESS
dan GET_USER.
Dan di dalam mutation AUTH_SUCCESS dan GET_USER kita melakukan perubahan nilai state
token dan user.
Dan setelah itu kita lakukan resolve agar component login dapat menerima callback dari
action login Vuex.
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.
Dan melakukan reject dengan isi sebuah error response. Ini digunakan agar component
login bisa menerima sebuah callback error response dari Rest API Laravel.
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.
//hook vue
import { ref, reactive } from 'vue'
//hook vue
import { ref, reactive, onMounted } from 'vue'
Selanjutnya, di dalam function setup, silahkan tambahkan kode berikut ini di bawah
513
function login :
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.
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.
<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.
//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,
})
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.
<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>
<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>
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.
const donation = {
//state
state: {
},
//mutations
mutations: {
},
//actions
actions: {
},
//getters
getters: {
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.
522
//import vuex
import { createStore } from 'vuex'
modules: {
auth, // <-- module auth
donation, // <-- module donation
}
})
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.
<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="text-xl">
RIWAYAT DONASI SAYA
</div>
<div class="border-2 border-gray-200 mt-3 mb-2"></div>
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>
<div v-else>
</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')
})
//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'
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.
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.
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.
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>
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.
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.
const donation = {
//state
state: {
//donations
donations: [],
//loadmore
nextExists: false,
nextPage: 0,
},
//mutations
mutations: {
532
},
},
//actions
actions: {
//action getDonation
getDonation({ commit }) {
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 {
533
}).catch(error => {
})
},
//action getLoadMore
getLoadMore({ commit }, nextPage) {
//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 {
}).catch(error => {
534
})
},
},
//getters
getters: {
Dari penambahan kode di atas, pertama kita import konfigurasi global 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.
//...
}
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.
Kemudian di dalam mutation SET_DONATIONS, kita melakukan assign dari data response
yang dikirim dari action ke dalam state yang bernama 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.
} else {
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
},
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 => {
//...
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.
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.
<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.
//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,
})
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
}
},
<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>
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.
const profile = {
//state
state: {
},
//mutations
mutations: {
},
//actions
actions: {
},
//getters
getters: {
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.
545
//import vuex
import { createStore } from 'vuex'
modules: {
auth, // <-- module auth
donation, // <-- module donation
profile, // <-- module profile
}
})
Di atas, pertama kita import file dari module profile terlebih dahulu.
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.
<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="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.
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 :
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.
const profile = {
//state
state: {
//profile state
profile: {},
},
//mutations
mutations: {
552
},
//actions
actions: {
//action getProfile
getProfile({ commit }) {
}).catch(error => {
})
},
},
//getters
getters: {
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'
//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 => {
})
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
},
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.
<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="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'
557
import { useToast } from "vue-toastification"
export default {
setup() {
//store vuex
const store = useStore()
//route
const router = useRouter()
//profile
const profile = computed(() => {
return store.state.profile.profile
})
//validation state
const validation = ref([])
//check fileType
if (!imageAvatar.value.type.match('image.*')) {
558
toast.error("Extensi File Tidak Diizinkan!")
//formdata
let formData = new FormData();
formData.append('avatar', imageAvatar.value)
formData.append('name', profile.value.name)
router.push({name: 'dashboard'})
}).catch(error => {
//assign validaation message
validation.value = error
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.
Selanjutnya, kita juga menambahkan 2 state baru untuk menyimpan kedua hook di atas di
dalam Composition API.
//route
const router = useRouter()
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.
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.
//...
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
//...
}
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)
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'})
})
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.
563
const profile = {
//state
state: {
//profile state
profile: {},
},
//mutations
mutations: {
},
//actions
actions: {
//action getProfile
getProfile({ commit }) {
}).catch(error => {
564
})
},
//action updateProfile
updateProfile({commit}, formData) {
resolve(response)
}).catch(error => {
})
})
},
},
//getters
getters: {
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.
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.
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.
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)
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.
<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.
//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,
})
570
})
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.
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>
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.
<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="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'
export default {
setup() {
//store vuex
const store = useStore()
//route
const router = useRouter()
//state user
const user = reactive({
575
password: '',
password_confirmation: ''
})
//validation state
const validation = ref([])
router.push({name: 'dashboard'})
}).catch(error => {
//assign validaation message
validation.value = error
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'
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()
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.
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.
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.
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'})
})
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
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
}
const profile = {
//state
state: {
//profile state
profile: {},
580
},
//mutations
mutations: {
},
//actions
actions: {
//action getProfile
getProfile({ commit }) {
}).catch(error => {
})
},
//action updateProfile
updateProfile({commit}, formData) {
581
const token = localStorage.getItem('token')
resolve(response)
}).catch(error => {
})
})
},
//action updatePassword
updatePassword({commit}, user) {
582
//commit ke mutation SET_PROFILE dengan response
data
commit('SET_PROFILE', response.data.data)
resolve(response)
}).catch(error => {
})
})
},
//getters
getters: {
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) => {
//...
}
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 => {
resolve(response)
})
Di atas, kita juga lakukan resolve agar di dalam component dapat menerima callback dari
action 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.
const slider = {
//state
state: {
},
//mutations
mutations: {
},
//actions
actions: {
},
//getters
getters: {
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.
589
//import vuex
import { createStore } from 'vuex'
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.
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.
<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">
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()
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.
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.
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>
Tapi, jika nilai state categories sama dengan 0, maka akan menampilkan loading block
dari Vue Content Loader.
<ContentLoader />
const category = {
//state
state: {
//index categories
categories: [],
596
},
//mutations
mutations: {
},
//actions
actions: {
//action getCategoryHome
getCategoryHome({ commit }) {
}).catch(error => {
})
},
//action getCategory
getCategory({ commit }) {
}).catch(error => {
597
console.log(error)
})
},
},
//getters
getters: {
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.
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.
Dan di dalam mutation SET_CATEGORIES kita melakukan assign data response tersebut ke
dalam state yang bernama categories.
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.
<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">
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>
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>
</div>
</div>
</template>
<script>
//hook vue
import { onMounted, computed } from 'vue'
//hook vuex
import { useStore } from 'vuex'
export default {
setup() {
//store vuex
const store = useStore()
//const route
602
const route = useRoute()
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.
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.
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.
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>
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 }}
Kemudian di dalamnya kita juga melakukan proses menampilkan donasi yang terkumpul
menggunakan progressbar. Kurang lebih seperti berikut ini :
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.
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.
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>
const category = {
607
//state
state: {
//index categories
categories: [],
//detail category
category: {},
//campaign category
campaignCategory: []
},
//mutations
mutations: {
},
//actions
actions: {
//action getCategoryHome
getCategoryHome({ commit }) {
608
}).catch(error => {
})
},
//action getCategory
getCategory({ commit }) {
}).catch(error => {
})
},
//action getCategory
getDetailCategory({ commit }, slug) {
}).catch(error => {
609
})
},
},
//getters
getters: {
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.
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 => {
})
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.
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.
613
<template>
<div class="pb-20 pt-20 text-center">
HALAMAN CAMPAIGN
</div>
</template>
<script>
export default {
}
</script>
<template>
<div class="pb-20 pt-20 text-center">
HALAMAN DETAIL CAMPAIGN
</div>
</template>
<script>
export default {
}
</script>
//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,
})
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()
}
})
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')
},
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>
<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>
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>
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.
<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">
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>
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>
</div>
</div>
</template>
<script>
//hook vue
import { computed, onMounted } from 'vue'
//vuex
import { useStore } from 'vuex'
export default {
components: {
FacebookLoader // <-- register component FacebooLoader dari
Vue Content Loader
},
setup() {
//store vuex
623
const store = useStore()
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.
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.
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 }}
Kemudian di dalamnya kita juga melakukan proses menampilkan donasi yang terkumpul
menggunakan progressbar. Kurang lebih seperti berikut ini :
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.
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.
<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">
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>
629
</p>
</div>
</div>
<div v-else>
</div>
</div>
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'
export default {
components: {
FacebookLoader // <-- register component FacebooLoader dari
Vue Content Loader
},
setup() {
//store vuex
const store = useStore()
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.
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 :
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.
<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="mt-5">
<p class="text-lg font-semibold">
{{ campaign.title }}
</p>
</div>
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>
</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>
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>
</div>
637
<div class="container mx-auto grid grid-cols-1 p-3 sm:w-full
md:w-5/12">
</div>
<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>
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'
export default {
setup() {
//vue route
const route = useRoute()
//store vuex
const store = useStore()
639
return store.state.campaign.campaign
})
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'
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.
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
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
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
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
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 :
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 :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.
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.
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 :
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 :
Untuk menampilkan nama donatur, kita bisa menggunakan kode seperti berikut ini :
{{ donation.donatur.name }}
const campaign = {
//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: {
648
//set state donatur dengan data dari response
DETAIL_USER(state, data) {
state.user = data
},
},
//actions
actions: {
//action getCampaign
getCampaign({ commit }) {
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 {
}).catch(error => {
649
//show error log dari response
console.log(error)
})
},
//action getLoadMore
getLoadMore({ commit }, nextPage) {
//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 {
}).catch(error => {
})
},
//action getDetailCampaign
getDetailCampaign({ commit }, slug) {
650
.then(response => {
}).catch(error => {
})
},
},
//getters
getters: {
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 => {
})
Dan di dalam mutation kita melakukan assign data yang dikirim dari action di atas ke
dalam sebuah state. Kurang lebih seperti berikut ini :
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.
<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.
//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,
})
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()
}
})
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
}
},
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>
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.
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.
<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="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() {
store.dispatch('donation/storeDonation', donation)
.then(() => {
//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.
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"
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.
//...
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(() => {
//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 :
const donation = {
//state
state: {
//donations
donations: [],
//loadmore
nextExists: false,
nextPage: 0,
},
//mutations
mutations: {
668
data.forEach(row => {
state.donations.push(row);
});
},
},
//actions
actions: {
//action getDonation
getDonation({ commit }) {
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 {
}).catch(error => {
669
})
},
//action getLoadMore
getLoadMore({ commit }, nextPage) {
//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 {
}).catch(error => {
})
},
//storeDonation
670
storeDonation({commit}, data) {
commit('')
resolve(response)
}).catch(error => {
})
})
},
//getters
getters: {
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.
Selanjutnya kita akan melakukan send data ke dalam Rest API Laravel dengan endpoint
http://localhost:8000/api/donation dan untuk method-nya adalah POST.
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.
<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="text-xl">
RIWAYAT DONASI SAYA
</div>
<div class="border-2 border-gray-200 mt-3 mb-2"></div>
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>
<div v-else>
</div>
</div>
</div>
</div>
</template>
<script>
//hook vue
import { computed, onMounted } from 'vue'
//hook vuex
import { useStore } from 'vuex'
export default {
677
setup() {
//store vuex
const store = useStore()
//router
const router = useRouter()
//loadMore function
function loadMore() {
store.dispatch('donation/getLoadmore', nextPage.value)
}
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.
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.
//...
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.
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
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.
<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.
//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,
})
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')
},
<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()
return {
linkToSearch, // <-- method linkToSearch
}
}
}
</script>
<style>
</style>
690
Dari perubahan kode di atas, pertama kita melakukan import hook yang bernama
useRouter dari 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.
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
}
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.
<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)
}
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'
//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.
const campaign = {
//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: {
697
},
},
//actions
actions: {
//action getCampaign
getCampaign({ commit }) {
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 {
}).catch(error => {
698
})
},
//action getLoadMore
getLoadMore({ commit }, nextPage) {
//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 {
}).catch(error => {
})
},
//action getDetailCampaign
getDetailCampaign({ commit }, slug) {
699
data
commit('DETAIL_CAMPAIGN', response.data.data)
}).catch(error => {
})
},
//action searchCampaign
searchCampaign({ commit }, querySearch='') {
}).catch(error => {
})
},
700
},
//getters
getters: {
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.
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
},
<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">
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>
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()
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'
//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>
</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.
<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()
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.
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>
const slider = {
//state
state: {
//index sliders
sliders: [],
},
//mutations
mutations: {
713
SET_SLIDERS(state, data) {
state.sliders = data
},
},
//actions
actions: {
//action getSlider
getSlider({ commit }) {
}).catch(error => {
})
}
},
//getters
getters: {
Di atas, pertama kita import konfigurasi global API untuk memanggil baseURL dari endpoint
API Laravel kita.
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.
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.
Dan di dalam mutation SET_SLIDERS kita melakukan assign dari data yang di dapatkan
tersebut ke dalam state yang bernama sliders.
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.
const category = {
//state
state: {
},
//mutations
mutations: {
},
//actions
actions: {
},
//getters
getters: {
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.
717
//import vuex
import { createStore } from 'vuex'
modules: {
auth, // <-- module auth
donation, // <-- module donation
profile, // <-- module profile
slider, // <-- module slider
category, // <-- module category
}
})
Dari penambahan kode di atas, pertama kita import module category daari folder module.
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.
<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()
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.
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.
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-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>
724
dalam module category Vuex. Silahkan buka file src/store/module/category.js
kemudian ubah semua kode-nya menjadi seperti berikut ini :
const category = {
//state
state: {
//index categories
categories: [],
},
//mutations
mutations: {
},
//actions
actions: {
//action getCategoryHome
getCategoryHome({ commit }) {
}).catch(error => {
725
})
},
},
//getters
getters: {
Di atas, pertama kita import konfigurasi global API untuk memanggil baseURL dari endpoint
API Laravel kita.
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.
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.
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.
<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.
//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,
})
730
next('/login')
} else {
next()
}
})
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')
},
<a href="">
<img src="@/assets/images/muslim.png" class="inline-block">
</a>
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>
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.
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">
</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>
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>
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.
const campaign = {
//state
state: {
},
//mutations
mutations: {
},
//actions
actions: {
},
//getters
getters: {
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.
739
//import vuex
import { createStore } from 'vuex'
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.
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.
<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">
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>
743
</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'
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()
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.
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.
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 }}
Kemudian di dalamnya kita juga melakukan proses menampilkan donasi yang terkumpul
menggunakan progressbar. Kurang lebih seperti berikut ini :
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.
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.
Helpers/mixins di atas akan menghitung jumlah hari antara tanggal sekarang dengan
tanggal yang ditentukan.
const campaign = {
//state
state: {
//index campaigns
campaigns: [],
//loadmore
nextExists: false,
nextPage: 0,
},
//mutations
749
mutations: {
},
//actions
actions: {
//action getCampaign
getCampaign({ commit }) {
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 {
750
}).catch(error => {
})
},
},
//getters
getters: {
Di atas, pertama kita import konfigurasi global API untuk memanggil baseURL dari endpoint
API Laravel kita.
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.
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.
Kemduain di dalam mutation SET_CAMPAIGNS kita melakukan assign dari data response di
atas ke dalam state yang bernama 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.
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.
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">
<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>
755
</div>
</div>
</div>
</div>
</div>
<div v-else>
</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'
//component categoryHome
import CategoryHome from '@/components/CategoryHome.vue'
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()
/**
* LOADMORE
*/
//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 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 :
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.
const campaign = {
//state
state: {
//index campaigns
759
campaigns: [],
//loadmore
nextExists: false,
nextPage: 0,
},
//mutations
mutations: {
},
//actions
actions: {
//action getCampaign
getCampaign({ commit }) {
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 {
}).catch(error => {
})
},
//action getLoadMore
getLoadMore({ commit }, nextPage) {
//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 {
}).catch(error => {
})
},
},
//getters
getters: {
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.
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.
762
Dan di dalam mutation SET_LOADMORE kita melakukan push data yang kita dapatkan dari
Rest API ke dalam state campaigns.
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.
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.
764
<template>
<div class="pb-20 pt-20 text-center">
HALAMAN CATEGORY
</div>
</template>
<script>
export default {
}
</script>
<template>
<div class="pb-20 pt-20 text-center">
HALAMAN DETAIL CATEGORY
</div>
</template>
<script>
export default {
}
</script>
//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,
})
767
export default router
{
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.
<a href="#">
<div>
<img :src="category.image" width="40" class="inline-block mb-2">
</div>
{{ category.name.toUpperCase() }}
</a>
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.
<a href="#">
<div>
<img src="@/assets/images/menu.png" width="40" class="inline-block
mb-2">
</div>
LAINNYA
</a>
Di atas kita ubah yang semula menggunakan a href="#" menjadi router-link dan kita
arahkan ke dalam route yang bernama category.index.
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://)
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.
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.
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 :
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 :
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.
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 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.
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
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.
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',
],
Silahkan buat file baru dengan nama _redirects di dalam folder public Vue Js. Dan
kemudian masukkan kode berikut ini :
/* /index.html 200.
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.
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
git add .
Perintah di atas digunakan untuk menambahkan semua file dan folder ke dalam git.
Perintah di atas digunakan untuk memberikan komentar dari file-file dan folder yang kita
tambahkan di atas, kita bisa mengganti kata initial commit dengan sesuai keinginan.
Perintah di atas digunakan untuk menambahkan alamat origin dengan repository yang
sudah kita buat. Di atas contohnya untuk alamat repository saya adalah
https://github.com/maulayyacyber/donasi-online.git, maka silahkan
disesuaikan dengan alamat dari repository masing-masing.
Perintah di atas digunakan untuk melakukan push/upload semua file project ke dalam
repository GitHub. Silahkan tunggu proses uploadnya sampai selesai.
Jika semua file project sudah 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.
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.
800
Di atas, kita harus mengatur 5 endpoint, yang digunakan untuk menerima notifikasi dari
Midtrans dan action redirect dari Midtrans.
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.
801
attribute value keterangan
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