~ We are changing the world with technology. ~

Matt Mullenweg

Aplikasi E-Commerce Laravel 6 #2: Templating & Authentication

Aplikasi E-Commerce Laravel 6 #2: Templating & Authentication

1564 Dilihat

Pendahuluan

Menganalisa struktur database adalah utama yang harus dilakukan sebelum melanjutkan proses development. Hal ini bertujuan untuk mengarahkan kita dalam menentukan bagian mana yang harus dikerjakan terlebih dahulu. Tekniknya adalah dengan cari bagian-bagian yang tidak membutuhkan bagian lainnya untuk menjalankan sistemnya. Misalnya saja, table products membutuhkan table categories untuk menjalankan fiturnya, maka terlebih dahulu kita kerjakan fitur untuk membuat data categories terlebih dahulu.

Terus hubungannya dengan templating dan authentication apa? Karena module Category berada dalam kendali administrator (red: pemilik apps), maka kita perlu membuat gerbang untuk memfilter pengguna yang dapat menambahkan data tersebut. Meskipun sebenarnya authentication ini bisa dikerjakan belakangan karena jika dilihat pada database, tidak ada field untuk menyimpan informasi user yang meng-input data tersebut. Akan tetapi guna mempermudah penulisan, maka bagian ini akan kita kerjakan terlebih dahulu.

Baca Juga: Aplikasi E-Commerce Laravel 6 #1: Schema Database

Generate Authentication

Perubahan Laravel 6 memberikan dampak pada proses generate authentication. Jika sebelumnya, proses ini bisa dengan mudah diselesaikan karena hanya menggunakan satu magic command yakni: php artisan make:auth. Pada versi saat ini, Laravel 6 membuang fungsi tersebut dan memisahkannya menjadi eksternal package.

Ada dua pilihan untuk membuat authentication di Laravel, pertama menggunakan magic command-nya atau membuatnya secara manual. Namun, pada seri ke-2 ini kita akan menggunakan magic command-nya karena nantinya akan ada seri yang membahas secara manual dengan tujuan membuat sistem multiple authentication.

Pada command line, install eksternal package tersebut dengan command

composer require laravel/ui --dev

Artisan command-nya sudah tersedia, maka untuk men-generate authentication, jalankan command berikut

php artisan ui:auth

Proses diatas akan men-generate HomeController.php, adapun view-nya yang generate adalah folder auth, layouts dan home.blade.php. Sedangkan routing yang dihasilkan adalah secara otomatis menambahkan code berikut didalam routes/web.php

Auth::routes(); //ROUTING INI MENCAKUP SEMUA ROUTING YANG BERKAITAN DENGAN AUTHENTICATION
Route::get('/home', '[email protected]')->name('home'); //SEDANGKAN ROUTING INI UNTUK MENG-HANDLE HALAMAN SETELAH LOGIN

Kendalanya adalah ketika mengakses http://localhost:8000/login, maka style css-nya maupun js-nya tidak akan di-load karena file tersebut memang belum tersedia (red: berbeda dengan versi Laravel 5.xx).

Masalah tersebut bisa diatasi dengan command npm install && npm run dev, proses ini akan meng-compile file js/app.js dan css/app.css. Sumbernya dari mana sih mereka men-generate file tersebut? Jika kita buka file webpack.mix.js, maka terdapat code berikut:

mix.js('resources/js/app.js', 'public/js')
   .sass('resources/sass/app.scss', 'public/css');

Penjelasan: Cara bacanya adalah mixer laravel akan men-generate js dan output-nya akan disimpan kedalam folder public/js, sedangkan sumber code-nya berasal dari resources/js/app.js. Begitupun code selanjutnya.

Command npm install && npm run dev tidak akan digunakan pada case kali ini, karena kita akan menambahkan template tersendiri untuk style login maupun dashboard nantinya. Jadi 2 paragraf diatas hanya sebagai informasi saja bagaimana cara mengatasi masalah yang seharusnya normal pada Laravel versi 5.xx tapi menjadi kendala pada versi 6.

Use External Template

Laravel menggunakan blade templating engine untuk meng-handle views dari aplikasi yang sedang dikerjakan. Blade ini terbilang cukup unik karena dilengkapi berbagai service dan command khusus dalam menggunakannya.

Khusus untuk seri templating ini, ada beberapa bagian yang harus kamu kenali fungsinya, yakni @yield(), @section(), @extends() dan @include. Adapun code lain-nya akan dibahas ketika dibutuhkan.

Penjelasan: Master template ada 2 buah, admin.blade.php digunakan untuk meng-handle tampilan dashboard, sedangkan auth.blade.php untuk meng-handle authentication. Master digunakan sebagai reusable code, jadi code yang tidak berubah, seperti header, sidebar, dll akan ditempatkan pada file ini.

Child view atau view yang meng-handle perhalaman sesuai halamannya, contohnya home.blade.php, ketika ingin menggunakan master template terkait, maka command yang digunakan adalah @extends. Sedangkan @yield pada master template nantinya berfungsi sebagai content yang dinamis, jadi apapun yang diapit oleh @section akan me-replace @yield pada master template.

Bingung? Mari kita terapkan dalam code untuk melihat bagaimana cara menggunakannya. Buat file admin.blade.php didalam folder resources/views/layouts dan tambahkan code berikut

<!DOCTYPE html>
<html lang="en">
<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, shrink-to-fit=no">
	<meta name="description" content="Daengweb - Aplikasi Ecommerce">
	<meta name="author" content="Daengweb">
  <meta name="keyword" content="aplikasi ecommerce laravel, tutorial laravel basic, belajar laravel, panduan belajar laravel">
    
  	<!-- PERHATIKAN BAGIAN INI, APAPUN YANG DIAPIT OLEH @SECTION('TITLE') PADA VIEW YANG MENGGUNAKAN MASTER INI, MAKA AKAN ME-REPLACE CODE DIBAWAH -->
  	<!-- TITLE MENJADI KATA KUNCI, JADI JIKA MENGGUNAKAN KEY TITLE PADA @YIELD, MAKA GUNAKAN KEY TITLE PADA @SECTION -->
    @yield('title')

  <!-- UNTUK ME-LOAD ASSET DARI PUBLIC, KITA GUNAKAN HELPER ASSET() -->
	<link href="{{ asset('assets/css/font-awesome.min.css') }}" rel="stylesheet">
	<link href="{{ asset('assets/css/simple-line-icons.css') }}" rel="stylesheet">
	<link href="{{ asset('assets/css/style.css') }}" rel="stylesheet">
	<link href="{{ asset('assets/vendors/pace-progress/css/pace.min.css') }}" rel="stylesheet">
</head>
<body class="app header-fixed sidebar-fixed aside-menu-fixed sidebar-lg-show">
  
    <!-- @INCLUDE SAMA DENGAN FUNGSI INCLUDE DI PHP, HANYA SAJA PENULISAN DIBLADE MENJADI @INCLUDE, BERARTI KITA ME-LOAD FILE LAINNYA -->
  	<!-- KENAPA HEADER DIPISAHKAN? AGAR LEBIH RAPI SAJA JADI LEBIH MUDAH MAINTENANCENYA -->
    <!-- KETIKA MELOAD FILE BLADE, MAKA EKSTENSI .BLADE.PHP TIDAK PERLU DITULISKAN -->
    @include('layouts.module.header')
  
    <div class="app-body" id="dw">
        <div class="sidebar">
          
          	<!-- SIDEBAR JUGA KITA PISAHKAN CODENYA MENJADI FILE TERSENDIRI -->
            <!-- KETIKA MELOAD FILE BLADE, MAKA EKSTENSI .BLADE.PHP TIDAK PERLU DITULISKAN -->
            @include('layouts.module.sidebar')
          
            <button class="sidebar-minimizer brand-minimizer" type="button"></button>
        </div>
      
      	<!-- BAGIAN INI AKAN DI-REPLACE SESUAI ISI YANG DIAPIT DARI @SECTION('CONTENT') -->
        @yield('content')
      
    </div>

    <footer class="app-footer">
        <div>
            <a href="https://coreui.io">Daengweb</a>
            <span>&copy; 2018 creativeLabs.</span>
        </div>
        <div class="ml-auto">
            <span>Powered by</span>
            <a href="https://coreui.io">CoreUI</a>
        </div>
    </footer>
    
    <script src="{{ asset('assets/js/jquery.min.js') }}"></script>
    <script src="{{ asset('assets/js/popper.min.js') }}"></script>
    <script src="{{ asset('assets/js/bootstrap.min.js') }}"></script>
    <script src="{{ asset('assets/js/pace.min.js') }}"></script>
    <script src="{{ asset('assets/js/perfect-scrollbar.min.js') }}"></script>
    <script src="{{ asset('assets/js/coreui.min.js') }}"></script>
    <script src="{{ asset('assets/js/custom-tooltips.min.js') }}"></script>
    @yield('js')
</body>
</html>

Selanjutnya buat file header.blade.php didalam folder resources/views/layouts/module dan tambahkan code

<header class="app-header navbar">
    <button class="navbar-toggler sidebar-toggler d-lg-none mr-auto" type="button" data-toggle="sidebar-show">
        <span class="navbar-toggler-icon"></span>
    </button>
    <a class="navbar-brand" href="#">
        <img class="navbar-brand-full" src="https://daengweb.id/front/dw-theme/images/logo-head.png" width="89" height="25" alt="DW Ecommerce">
        <img class="navbar-brand-minimized" src="{{ asset('assets/dw-01/img/brand/sygnet.svg') }}" width="30" height="30" alt="CoreUI Logo">
    </a>
    <button class="navbar-toggler sidebar-toggler d-md-down-none" type="button" data-toggle="sidebar-lg-show">
        <span class="navbar-toggler-icon"></span>
    </button>
    <ul class="nav navbar-nav ml-auto">
        <li class="nav-item d-md-down-none">
            <a class="nav-link" href="#">
                <i class="icon-location-pin"></i>
            </a>
        </li>
        <li class="nav-item dropdown">
            <a class="nav-link" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">
                <img class="img-avatar" src="{{ asset('assets/img/avatars/6.jpg') }}" alt="[email protected]">
            </a>
        <div class="dropdown-menu dropdown-menu-right">
            <div class="dropdown-header text-center">
                <strong>Account</strong>
            </div>
            <div class="divider"></div>
            <a class="dropdown-item" href="#">
                <i class="fa fa-shield"></i> Lock Account
            </a>
            <a class="dropdown-item" href="{{ route('logout') }}" onclick="event.preventDefault();
                document.getElementById('logout-form').submit();">
                <i class="fa fa-lock"></i> Logout
            </a>

            <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                @csrf
            </form>
        </div>
        </li>
    </ul>
</header>

Note: Tidak ada yang perlu dijelaskan dari code diatas karena hanya html biasa yang berisi potongan code untuk header.

File lainnya adalah sidebar.blade.php didalam folder yang sama dan tambahkan code:

<nav class="sidebar-nav">
    <ul class="nav">
        <li class="nav-item">
            <a class="nav-link" href="#">
                <i class="nav-icon icon-speedometer"></i> Dashboard
            </a>
        </li>

        <li class="nav-title">MANAJEMEN PRODUK</li>
        <li class="nav-item">
            <a class="nav-link" href="#">
                <i class="nav-icon icon-drop"></i> Kategori
            </a>
        </li>
        <li class="nav-item nav-dropdown">
            <a class="nav-link nav-dropdown-toggle" href="#">
                <i class="nav-icon icon-settings"></i> Pengaturan
            </a>
            <ul class="nav-dropdown-items">
                <li class="nav-item">
                    <a class="nav-link" href="#">
                        <i class="nav-icon icon-puzzle"></i> Toko
                    </a>
                </li>
            </ul>
        </li>
    </ul>
</nav>

Note: Ketika akan menambahkan sidebar menu, maka nantinya file ini akan banyak di-edit. Untuk sementara kita buat dummy menu terlebih dahulu.

Sekarang saatnya kita meng-edit view yang akan meng-handle halaman home, karena file ini sudah ada dari hasil generate artisan ui:auth, maka kita tinggal memodifikasinya menjadi

<!-- FUNGSI EXTENDS DIGUNAKAN UNTUK ME-LOAD MASTER LAYOUTS YANG ADA, KARENA INI ADALAH HALAMAN HOME MAKA KITA ME-LOAD LAYOUTS ADMIN.BLADE.PHP -->
<!-- KETIKA MELOAD FILE BLADE, MAKA EKSTENSI .BLADE.PHP TIDAK PERLU DITULISKAN -->
@extends('layouts.admin')

<!-- TAG YANG DIAPIT OLEH SECTION('TITLE') AKAN ME-REPLACE @YIELD('TITLE') PADA MASTER LAYOUTS -->
@section('title')
    <title>Dashboard</title>
@endsection

<!-- TAG YANG DIAPIT OLEH SECTION('CONTENT') AKAN ME-REPLACE @YIELD('CONTENT') PADA MASTER LAYOUTS -->
@section('content')
<main class="main">
    <ol class="breadcrumb">
        <li class="breadcrumb-item">Home</li>
        <li class="breadcrumb-item active">Dashboard</li>
    </ol>
    <div class="container-fluid">
        <div class="animated fadeIn">
            <div class="row">
                <div class="col-md-12">
                    <div class="card">
                        <div class="card-header">
                            <h4 class="card-title">Aktivitas Toko</h4>
                        </div>
                        <div class="card-body">
                            <div class="row">
                                <div class="col-md-3">
                                    <div class="callout callout-info">
                                        <small class="text-muted">Omset Harian</small>
                                        <br>
                                        <strong class="h4">Rp 0</strong>
                                    </div>
                                </div>
                                <div class="col-md-3">
                                    <div class="callout callout-danger">
                                        <small class="text-muted">Pelanggan Baru (H-7)</small>
                                        <br>
                                        <strong class="h4">0</strong>
                                    </div>
                                </div>
                                <div class="col-md-3">
                                    <div class="callout callout-primary">
                                        <small class="text-muted">Perlu Dikirim</small>
                                        <br>
                                        <strong class="h4">0</strong>
                                    </div>
                                </div>
                                <div class="col-md-3">
                                    <div class="callout callout-success">
                                        <small class="text-muted">Total Produk</small>
                                        <br>
                                        <strong class="h4">0</strong>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</main>
@endsection

Layouts untuk dashboard (admin area) beserta 1 buah view untuk halaman home sudah selesai dibuat, maka tugas selanjutnya adalah layouts yang akan meng-handle authentication.

Buat file auth.blade.php di dalam folder resources/views/layouts dan tambahkan code

<!DOCTYPE html>
<html lang="en">
<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, shrink-to-fit=no">
    <meta name="description" content="CoreUI - Open Source Bootstrap Admin Template">
    <meta name="author" content="Ɓukasz Holeczek">
    <meta name="keyword" content="Bootstrap,Admin,Template,Open,Source,jQuery,CSS,HTML,RWD,Dashboard">
    
    @yield('title')

    <link href="{{ asset('assets/css/coreui-icons.min.css') }}" rel="stylesheet">
    <link href="{{ asset('assets/css/flag-icon.min.css') }}" rel="stylesheet">
    <link href="{{ asset('assets/css/font-awesome.min.css') }}" rel="stylesheet">
    <link href="{{ asset('assets/css/simple-line-icons.css') }}" rel="stylesheet">

    <link href="{{ asset('assets/css/style.css') }}" rel="stylesheet">
    <link href="{{ asset('assets/css/pace.min.css') }}" rel="stylesheet">
</head>
<body class="app flex-row align-items-center">
    <div class="container">
        @yield('content')
    </div>

    <script src="{{ asset('assets/js/jquery.min.js') }}"></script>
    <script src="{{ asset('assets/js/popper.min.js') }}"></script>
    <script src="{{ asset('assets/js/bootstrap.min.js') }}"></script>
    <script src="{{ asset('assets/js/pace.min.js') }}"></script>
    <script src="{{ asset('assets/js/perfect-scrollbar.min.js') }}"></script>
    <script src="{{ asset('assets/js/coreui.min.js') }}"></script>
    <script>
        $('#ui-view').ajaxLoad();
        $(document).ajaxComplete(function() {
            Pace.restart()
        });
    </script>
</body>
</html>

Note: Cara kerja dan penjelasan @extends dan @yield sama dengan sebelumnya.

Bagian terakhir adalah view yang akan meng-handle halaman login. Karena pada bagian ini kita hanya membutuhkan halaman login saja, maka halaman lainnya seperti register, verify dan forgot password tidak akan diubah untuk saat ini. Buka file resources/views/auth/login.blade.php dan modifikasi menjadi

@extends('layouts.auth')

@section('title')
    <title>Login</title>
@endsection

@section('content')
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card-group">
                <div class="card p-4">
                    <div class="card-body">
                        <h1>Login</h1>
                        <p class="text-muted">Sign In to your account</p>

                      	<!-- ACTIONNYA MENGARAH PADA URL /LOGIN -->
                      	<!-- UNTUK MENCARI TAU TUJUAN URI DARI ROUTE NAME DIBAWAH, PADA COMMAND LINE, KETIKKAN PHP ARTISAN ROUTE:LIST DAN CARI URI YANG MENGGUNAKAN METHOD POST -->
                      	<!-- KARENA URI /LOGIN DENGAN METHOD GET DIGUNAKAN UNTUK ME-LOAD VIEW HALAMAN LOGIN -->
                      	<!-- PENGGUNAAN ROUTE() APABILA ROUTING TERSEBUT MEMILIKI NAMA, ADAPUN NAMENYA ADA PADA COLOM NAME ROUTE:LIST -->
                      	<!-- JIKA ROUTINGNYA TIDAK MEMILIKI NAMA, MAKA GUNAKAN HELPER URL() DAN DIDALAMNYA ADALAH URINYA. CONTOH URL('/LOGIN') -->
                        <form action="{{ route('login') }}" method="post">
                            @csrf
                            <div class="input-group mb-3">
                                <div class="input-group-prepend">
                                    <span class="input-group-text">
                                        <i class="icon-user"></i>
                                    </span>
                                </div>
                              
                              	<!-- $errors->has('email') AKAN MENGECEK JIKA ADA ERROR DARI HASIL VALIDASI LARAVEL, SEMUA KEGAGALAN VALIDASI LARAVEL AKAN DISIMPAN KEDALAM VARIABLE $errors -->
                                <input class="form-control {{ $errors->has('email') ? ' is-invalid' : '' }}" 
                                    type="text" 
                                    name="email"
                                    placeholder="Email Address" 
                                    value="{{ old('email') }}" 
                                    autofocus 
                                    required>
                            </div>
                            <div class="input-group mb-4">
                                <div class="input-group-prepend">
                                    <span class="input-group-text">
                                        <i class="icon-lock"></i>
                                    </span>
                                </div>
                                <input class="form-control {{ $errors->has('email') ? ' is-invalid' : '' }}" 
                                    type="password" 
                                    name="password"
                                    placeholder="Password" 
                                    required>
                            </div>
                            <div class="row">
                                @if (session('error'))
                                <div class="col-md-12">
                                    <div class="alert alert-danger" role="alert">
                                        {{ session('error') }}
                                    </div>
                                </div>
                                @endif

                                <div class="col-6">
                                    <button class="btn btn-primary px-4">Login</button>
                                </div>
                                <div class="col-6 text-right">
                                    <button class="btn btn-link px-0" type="button">Forgot password?</button>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>
                <div class="card text-white bg-primary py-5 d-md-down-none" style="width:44%">
                    <div class="card-body text-center">
                        
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Satu hal yang perlu dilakukan sebelum mengakses melalui browser adalah dengan men-download file css dan js dari eksternal template yang ditambahkan. Download Semua File Didalam folder Assets dan hasil download-nya letakkan kedalam folder public/assets .

Untuk mengakses Laravel bisa dengan url http://localhost/dw-ecommerce/public atau jika menggunakan built-in server, pada command line jalankan php artisan serve. Kemudian akses apps-nya melalui url http://localhost:8000.

Halaman login berada pada url http://localhost:8000/login dan hasilnya sebagai berikut.

Seeder Default User

Data dummy sangat dibutuhkan untuk keperluan testing, maka seeder mengambil peran itu untuk membuat reusable code yang biasanya digunakan untuk memasukkan data dummy. Pernah tidak mengalami proses input data manual? Misal, datanya hilang terus di-input manual lagi. Hal ini tentu saja sangat me-repotkan dan memperlambat proses development.

Nah dengan seeder, kita bisa menggunakannya berkali-kali saat dibutuhkan. Misalkan data kategori bisa disimpan di-seeder, sehingga apabila data tersebut hilang maka kita tinggal menjalankan seeder. Namun pada artikel ini, kita akan menggunakan sebuah seeder untuk memasukkan data default user yang akan digunakan untuk login agar bisa mengakses halaman dashboard (admin area).

Pada command line, jalankan command berikut untuk membuat sebuah seeder.

php artisan make:seeder UsersTableSeeder

Buka file UsersTableSeeder.php yang berada di dalam folder database/seeds dan modifikasi menjadi seperti berikut.

<?php

use Illuminate\Database\Seeder;
use App\User; //IMPORT MODEL APP/USER.PHP

class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        //SIMPAN DATA KE TABLE users MELALUI MODEL USER
        //DENGAN DATA YANG ADA DIDALAM ARRAY DIBAWAH
        //BCRYPT DIGUNAKAN UNTUK MEN-ENKRIPSI SEBUAH STRING
        //KARENA PASSWORD HARUS DISIMPAN DALAM KEADAAN TER-ENKRIPSI
        User::create([
            'name' => 'Admin Daengweb',
            'email' => '[email protected]',
            'password' => bcrypt('secret')
        ]);
    }
}

Jika pada artikel sebelumnya kita belajar bagaimana cara menjalankan seeder sekaligus secara bersamaan, maka artikel kali ini kita akan belajar hal baru yakni menjalankan seeder secara individu. Pada command line jalankan command berikut

php artisan db:seed --class=UsersTableSeeder

Note: UsersTableSeeder adalah nama class seeder yang ingin dijalankan.

Jangan lupa tambahkan class ini ke dalam database/seeds/DatabaseSeeder.php agar nantinya ketika database di-reset maka kita tinggal menjalankan seeder secara massal (red: php artisan db:seed).

public function run()
{
    $this->call(ProvinceTableSeeder::class);
    $this->call(CityTableSeeder::class);
    $this->call(DistrictTableSeeder::class);
    $this->call(UsersTableSeeder::class); //TAMBAHKAN CODE INI
}

Data default user sudah ditambahkan, silahkan login menggunakan email [email protected] dan password secret. Adapun halaman admin-nya akan terlihat seperti gambar dibawah

Baca Juga: Aplikasi Laundry (Laravel 5.8 & Vue.js) #14: Chart & Laporan

Kesimpulan

Sepanjang artikel ini kita telah belajar bagaimana menggunakan blade template, bagaimana mengatasi perubahan artisan command make:auth dari Laravel yang dihilangkan, menambahkan eksternal template, membuat seeder, dan lain sebagainya.

Adapun dokumentasi code dari artikel ini bisa dilihat di Github.

Full Stack Developer & Remote Worker salah satu perusahaan asal Australia. Senang dengan teknologi baru. Laravel addict yang tidak fanatik. Merekam jejak dengan menulis

Aplikasi E-Commerce Laravel 6 #7: Filter & Product Detail Laravel

Aplikasi E-Commerce Laravel 6 #7: Filter...

Hal sederhana tapi kerap kali mengundang tanya, bagaimana sih cara filter data berdasarkan parameter tertentu? Maka pada seri artikel ini kita akan belajar bagaimana cara menampilkan data produk berda...

Aplikasi E-Commerce Laravel 6 #6: Templating & Display Products Laravel

Aplikasi E-Commerce Laravel 6 #6: Templa...

Sejenak kita meninggalkan kegiatan untuk merangkai fitur dari sisi administrator dan beralih pada sisi etalase toko yang bisa dilihat oleh customers atau visitors ketika mengunjungi aplikasi tersebut....

Aplikasi E-Commerce Laravel 6 #5: Mass Upload Products Laravel

Aplikasi E-Commerce Laravel 6 #5: Mass U...

Selain fitur yang memungkinkan user untuk menambahkan data produk satu-persatu, maka alangkah lebih menariknya lagi jika kita menyediakan sebuah fitur yang memungkinkan user untuk menambahkan produk s...

Komentar