Logo Universitas

Universitas ASA Indonesia

Program Studi: Teknologi Informasi

Mata Kuliah: Pengembangan Aplikasi Web Lanjut

SKS: 3 (2 teori, 1 praktikum)

Dosen Pengampu: Istiqomah Sumadikarta, S.T., M.Kom.

Beranda Mundur Maju

Pertemuan 7: Implementasi Halaman Admin #1

Konfigurasi Route Login


Sebelum kita lanjutkan untuk membuat otentikasi di React.js, maka kita perlu melakukan konfigurasi route-nya terlebih dahulu. Route ini merupakan URL yang diakses di dalam browser dan menampilkan sebuah halaman.

Dan disini kita akan membuat route baru untuk menampilkan halaman login di dalam website. Dan dimateri berikut-nya kita akan lanjutkan untuk proses otentikasi-nya.

Langkah 1 - Membuat View / Component Login

Pertama-tama kita harus membuat seebuah view/component yang nantinya ditampilkan di halaman web browser. Silahkan buat folder baru dengan nama pages di dalam folder src. Dan di dalam folder pages silahkan buat folder lagi dengan nama admin, kemudian di dalam folder admin silahkan buat file baru dengan nama Login.jsx.

Setelah itu, silahkan masukkan kode berikut ini di dalam file Login.jxs, kode ini sementara untuk sample dan akan kita ubah dimateri selanjutnya.

function Login () {
    return (
        <h1>Halaman Login</h1>
    )
}

export default Login

Langkah 2 - Konfigurasi Route Login

Setelah berhasil membuat view/component, sekarang kita lanjutkan untuk melakukan konfigurasi route login. Dimana route tersebut akan memanggil view/component yang sudah kita buat di atas.

Silahkan buat folder baru dengan nama routes di dalam folder src. Dan di dalam folder routes silahkan buat file baru dengan nama routes.jsx dan masukkan kode berikut ini di dalamnya.

//import react router dom
import { Routes, Route } from "react-router-dom";

//=======================================================================
//ADMIN
//=======================================================================

//import view Login
import Login from '../pages/admin/Login.jsx';

function RoutesIndex() {
    return (
        <Routes>

            {/* route "/admin/login" */}
            <Route path="/admin/login" element={<Login />} />

        </Routes>
    )
}

export default RoutesIndex

Dari penambahan kode di atas, pertama kita import 2 module dari React Router DOM, yaitu Routes dan Route. Kedua module tersebut akan kita gunakan untuk membuat konfigurasi route di dalam React.js.

//import react router dom
import { Routes, Route } from "react-router-dom";

Setelah itu, kita import view/component Login yang sudah kita buat sebelumnya.

//import view Login
import Login from '../pages/admin/Login.jsx';

Kemudian untuk mendefinisikan route, pertama kita letakkan di dalam module <Routes> dan di dalamnya kita membuat route baru.

 <Route path="/admin/login" element={<Login />} />

Di atas untuk path merupakan URL yang akan digunakan untuk route dan di dalam element kita memanggil view/component <Login />.

Jadi ketika kita melakukan akses di URL browser dengan /admin/login, maka view/component Login akan dieksekusi/dipanggil.

Langkah 3 - Mendaftarkan Route secara Global

Agar route-route yang kita buat nanti dapat dibaca di dalam project React.js, maka kita perlu melakukan konfigurasi atau mendaftarkan route ini di dalam component App.jsx.

Silahkan buka file src/App.jsx, kemudian ubah kode-nya menjadi seperti berikut ini :

//import Toaster
import { Toaster } from 'react-hot-toast';

//import routes
import Routes from './routes/routes';

function App() {

  return (
    <>
      <Toaster />
      <Routes />
    </>
  )
}

export default App

Dari perubahan kode di atas, pertama kita melakukan import file routes yang sudah kita buat di atas.

//import routes
import Routes from './routes/routes';

Setelah itu, kita taruh di dalam method return agar route tersebut dapat digunakan secara global di dalam project React.js.

Sekarang, silahkan kita uji coba di web browser dengan mengetikkan http://localhost:5173/admin/login dan jika berhasil maka kurang lebih seperti berikut ini :

Membuat Proses Login


Setelah berhasil menampilkan halaman login, sekarang kita akan lanjutkan untuk membuat proses login di dalam React.js. Jadi kita akan memanfaatkan Rest API yang sudah dibuat di dalam Laravel untuk proses otentikasi dan setelah proses otentikasi berhasil, maka kita akan menyimpan token dari hasil otentikasi ke dalam cookies.

Token dari proses otentikasi tersebut yang nanti akan kita gunakan untuk mengakses endpoint-endpoint Rest API yang membutuhkan authorization.

Langkah 1 - Edit View/Component Login

Karena sebelumnya kita sudah pernah memberikan sample kode di dalam view/component Login, maka sekarang kita akan merubah semua kode yang ada di dalamnya.

Silahkan buka file src/pages/admin/Login.jsx, kemudian ubah semua kode-nya menjadi seperti berikut ini :

//import hook react
import React, { useState } from "react";

//import BASE URL API
import Api from "../../api";

//import toats
import toast from "react-hot-toast";

//import js cookie
import Cookies from "js-cookie";

//import react router dom
import { useNavigate } from "react-router-dom";

function Login() {

	//title page
    document.title = "Login - Administrator Travel GIS";

    //navigate
    const navigate = useNavigate();

    //state user
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");

    //state loading
    const [isLoading, setLoading] = useState(false);

    //state validation
    const [validation, setValidation] = useState({});

    //function "loginHandler"
    const loginHandler = async (e) => {
        e.preventDefault();

        //set state isLoading to "true"
        setLoading(true);

        await Api.post("/api/admin/login", {
                email: email,
                password: password,
            })
            .then((response) => {
                //set state isLoading to "false"
                setLoading(false);

                //show toast
                toast.success("Login Successfully.", {
                    duration: 4000,
                    position: "top-right",
                    style: {
                        borderRadius: '10px',
                        background: '#333',
                        color: '#fff',
                    },
                });

                //set cookie
                Cookies.set("token", response.data.token);

                //redirect dashboard page
                navigate("/admin/dashboard");
            })
            .catch((error) => {
                //set state isLoading to "false"
                setLoading(false);

                //set error response validasi to state "validation"
                setValidation(error.response.data);
            });
    };

    if (Cookies.get("token")) {
        //redirect dashboard page
        return navigate("/admin/dashboard");
    }


    return (
        <React.Fragment>
            <div className="container">
                <div className="row justify-content-center">
                    <div className="col-md-4 mt-150">
                        <div className="text-center mb-4">
                            <h4><i className="fa fa-map-marked-alt"></i> <strong>TRAVEL GIS</strong></h4>
                        </div>
                        <div className="card border-0 rounded shadow-sm">
                            <div className="card-body">
                                <div className="text-center">
                                    <h6 className="fw-bold">LOGIN ADMIN</h6>
                                    <hr />
                                </div>
                                {validation.message && (
                                    <div className="alert alert-danger">
                                        {validation.message}
                                    </div>
                                )}
                                <form onSubmit={loginHandler}>

                                    <label className="mb-1">EMAIL ADDRESS</label>
                                    <div className="input-group mb-3">
                                        <span className="input-group-text"><i className="fa fa-envelope"></i></span>
                                        <input type="text" className="form-control" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email Address" />
                                    </div>
                                    {validation.email && (
                                        <div className="alert alert-danger">
                                            {validation.email[0]}
                                        </div>
                                    )}

                                    <label className="mb-1">PASSWORD</label>
                                    <div className="input-group mb-3">
                                        <span className="input-group-text"><i className="fa fa-lock"></i></span>
                                        <input type="password" className="form-control" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" />
                                    </div>
                                    {validation.password && (
                                        <div className="alert alert-danger">
                                            {validation.password[0]}
                                        </div>
                                    )}

                                    <button className="btn btn-success shadow-sm rounded-sm px-4 w-100" type="submit" disabled={isLoading}> {isLoading ? "LOADING..." : "LOGIN"} </button>
                                </form>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </React.Fragment>
    )

}

export default Login;

Dari perubahan kode di atas, pertama kita import hook useState dari React. Hook tersebut akan kita gunakan untuk mendefinisikan sebuah state di dalam component.

//import hook react
import React, { useState } from "react";

Setelah itu, kita juga import baseURL api yang sudah pernah kita buat sebelumnya.

//import BASE URL API
import Api from "../../api";

Karena kita akan menampilkan sebuah notifikasi saat proses otentikasi berhasil, maka kita akan melakukan import React Hot Toast.

//import toats
import toast from "react-hot-toast";

Dan saat proses otentikasi berhasil, maka kita akan menyimpan token-nya di dalam cookies dan disini kita akan menggunakan package Js Cookies.

//import js cookie
import Cookies from "js-cookie";

Setelah itu kita juga import hook dari React Router DOM, yaitu useNavigate.

//import react router dom
import { useNavigate } from "react-router-dom";

Di dalam function component Login, pertama-tama kita mendefinisikan title untuk halaman.

//title page
document.title = "Login - Administrator Travel GIS";

Kemudian kita membuat variable dengan nama navigate yang mana isinya adalah hook useNavigate dari React Router DOM. Tujuannya adalah mempermudah kita dalam memanggil hook tersebut nantinya.

//navigate
const navigate = useNavigate();

Setelah itu kita buat 2 state baru, yaitu email dan password. Kedua state tersebut akan kita gunakan untuk menyimpan data yang diinputkan di dalam form.

//state user
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");

Kemudian dibawah-nya kita buat 2 state lagi, yaitu isLoading dan validation.

//state loading
const [isLoading, setLoading] = useState(false);

//state validation
const [validation, setValidation] = useState({});

Untuk state isLoading akan kita gunakan untuk menampilkan loading di dalam button saat di klik dan by default kita berikan value false.

Sedangkan state validation akan kita gunakan untuk menyimpan response validasi yang di dapatkan dari Rest API dan untuk default value-nya adalah object kosong.

Setelah itu, kita buat function yang bernama loginHandler, dimana fungsi tersebut akan dijalankan ketika form disubmit.

//function "loginHandler"
const loginHandler = async (e) => {

	//...

}

Saat fungsi loginHandler dieksekusi, pertama kita akan melakukan event preventDefault, tujuannya untuk menonaktifkan kebiasan dari sebuah form submit, yaitu reload halaman.

e.preventDefault();

Setelah itu, kita set state isloading menjadi true.

//set state isLoading to "true"
setLoading(true);

Kemudian kita melakukan fetching ke dalam Rest API dengan method POST di dalam endpoint /api/admin/login dengan mengirimkan 2 data, yaitu email dan password.

await Api.post("/api/admin/login", {
   email: email,
   password: password,
})

Jika proses otentikasi berhasil dilakukan di dalam backend, maka akan masuk di dalam promise then. Dan di dalamya pertama kita akan mengupdate state isLoading menjadi false.

//set state isLoading to "false"
setLoading(false);

Selanjutnya, kita akan menampilkan notifikasi menggunakan React Hot Toast, yang berisi informasi proses otentikasi berhasil dilakukan.

//show toast
toast.success("Login Successfully.", {
    duration: 4000,
    position: "top-right",
    style: {
        borderRadius: '10px',
        background: '#333',
        color: '#fff',
    },
});

Kemudian kita akan set cookies dengan key token dan isinya adalah token dari hasil response Rest API.

//set cookie
Cookies.set("token", response.data.token);

Dan setelah itu kita arahkan ke dalam URL /admin/dashboard atau halaman dashboard menggunakan navigate.

//redirect dashboard page
navigate("/admin/dashboard");

Tapi, jika proses otentikasi gagal dilakukan, maka pertama-tama kita juga akan mengubah isi dari state isLoading menjadi false.

//set state isLoading to "false"
setLoading(false);

Dan melakukan assign response validasi dari Rest API ke dalam state validation.

//set error response validasi to state "validation"
setValidation(error.response.data);

Dan di bawah function loginHandler, kita membuat kondisi untuk mengecek apakah terdapat token di dalam cookies atau tidak. Kondisi ini digunakan ketika kita sudah melakukan proses otentikasi dan berhasil, kemudian kita coba akses halaman login kembali, maka akan kita paksa arahkan ke dalam URL /admin/dashboard.

if(Cookies.get("token")) {
    //redirect dashboard page
    return navigate("/admin/dashboard");
}

kemudian di dalam JSX, untuk form action kita arahkan ke dalam function loginHandler yang sudah kita buat di atas.

<form onSubmit={loginHandler}>

	//...

</form>

Untuk input kita berikan event onChange yang isinya melakukan assign value ke dalam sebuah state. Contohnya seperti berikut ini :

<input type="text" className="form-control" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email Address" />

Di atas, di dalam event onChange kita membuat function dengan parameter e (event) yang isinya melakukan assign ke dalam state email menggunakan setEmail dengan value dari inputan itu sendiri, yaitu e.target.value.

Dan untuk menampilkan validasi, kita membuat kondisi di dalam JSX. Kurang lebih seperti berikut ini :

{validation.email && (
    <div className="alert alert-danger">
       {validation.email[0]}
    </div>
)}

Di atas, jika state validation.email memiliki value, maka akan melakukan render sintaks HTML di dalamnya.

Langkah 2 - Uji Coba Proses Login

Sekarang kita akan lakukan uji coba proses login, silahkan buka http://localhost:5173/admin/login dan jika berhasil maka kita akan mendapatkan tampilan seperti berikut ini :

Sekarang silahkan klik button LOGIN tanpa mengisi data apapun, maka kita akan mendapatkan hasil validasi kurang lebih seperti berikut ini :

kemudian kita coba masukkan data user yang belum ada di dalam database, maka kita akan mendapatkan error yang berbeda lagi. Kurang lebih seperti berikut ini :

Terakhir, kita akan mencoba untuk memasukkan data user yang ada di dalam database. Dan jika berhasil maka kurang lebih seperti berikut ini :

Di atas, kita berhasil melakukan proses otentikasi, dimana kita telah mendapatkan notifikasi Login Successfully. Kemudian kita bisa melakukan check terhadapat token yang di dapatkan.

Silahkan klik kanan pada browser dan pilih Inspect > Application > Cookies. Kurang lebih seperti berikut ini :

Dan di atas masih menampilkan halaman blank, karena kita memang belum membuat route untuk halaman dashboard.

Membuat Component Sidebar Admin


Sebelum kita lanjutkan menampilkan halaman dashboard, sekarang kita akan belajar membuat component sidebar terlebih dahulu. Component ini akan digunakan untuk menampilkan menu di halaman admin.

Silahkan buat folder baru dengan nama components di dalam folder src. Dan di dalam folder components silahkan buat folder lagi dengan nama admin, kemudian di dalam folder admin silahkan buat file baru dengan nama Sidebar.jsx dan masukkan kode berikut ini di dalam-nya.

import React from "react";

//import Link
import {Link, useLocation} from 'react-router-dom';

function Sidebar() {

    //assigning location variable
    const location = useLocation();

    //destructuring pathname from location
    const { pathname } = location;

    //Javascript split method to get the name of the path in array
    const splitLocation = pathname.split("/");

    return (
        <React.Fragment>
            <div className="list-group list-group-flush">
                <Link className={splitLocation[2] === "dashboard" ? "list-group-item list-group-item-action list-group-item-light p-3 text-uppercase active" : "list-group-item list-group-item-action list-group-item-light p-3 text-uppercase"} to="/admin/dashboard"><i className="fa fa-tachometer-alt me-2"></i> Dashboard</Link>
                <Link className={splitLocation[2] === "categories" ? "list-group-item list-group-item-action list-group-item-light p-3 text-uppercase active" : "list-group-item list-group-item-action list-group-item-light p-3 text-uppercase"} to="/admin/categories"><i className="fa fa-folder me-2"></i> Categories</Link>
                <Link className={splitLocation[2] === "places" ? "list-group-item list-group-item-action list-group-item-light p-3 text-uppercase active" : "list-group-item list-group-item-action list-group-item-light p-3 text-uppercase"} to="/admin/places"><i className="fa fa-map-marked-alt me-2"></i> PLACES</Link>
                <Link className={splitLocation[2] === "sliders" ? "list-group-item list-group-item-action list-group-item-light p-3 text-uppercase active" : "list-group-item list-group-item-action list-group-item-light p-3 text-uppercase"} to="/admin/sliders"><i className="fa fa-images me-2"></i> Sliders</Link>
                <Link className={splitLocation[2] === "users" ? "list-group-item list-group-item-action list-group-item-light p-3 text-uppercase active" : "list-group-item list-group-item-action list-group-item-light p-3 text-uppercase"} to="/admin/users"><i className="fa fa-users me-2"></i> Users</Link>
            </div>
        </React.Fragment>
    )

}

export default Sidebar;

Dari penambahan kode di atas, pertama kita import React dari react.

import React from "react";

Setelah itu, kita import provider Link hook useLocation dari React Router DOM.

//import Link
import {Link, useLocation} from 'react-router-dom';

Kemudian di dalam function component Sidebar, pertama kita membuat variable baru dengan nama location yang berisi hook useLocation dari React Router DOM. Ini bertujuan agar kita lebih mudah menggunakan hook tersebut.

//assigning location variable
const location = useLocation();

Setelah itu kita melakukan destructuring atau ambil object yang bernama pathname yang berada di dalam array location di atas.

//destructuring pathname from location
const { pathname } = location;

Dan kita buat variable lagi untuk melakukan split atau memisahkan nama path atau URL menjadi array.

//Javascript split method to get the name of the path in array
const splitLocation = pathname.split("/");

Kemudian kita gunakan variable splitLocation untuk membuat kondisi menu active di sidebar. Contohnya seperti berikut ini :

{splitLocation[2] === "dashboard" ? "active" : ""}

Di atas kita menggunakan kondisional ternary. Jika index array kedua dari variable splitLocation bernilai dashboard, maka kita akan menambahkan class active.

Membuat Layouts Admin


Setelah berhasil membuat sidebar untuk menampilkan menu, maka sekarang kita akan lanjutkan belajar bagaimana cara membuat layout induk untuk halaman admin. Dimana kita juga akan memanggil component sidebar yang sudah kita buat sebelumnya.

Dan di dalam layout admin ini kita juga akan belajar beberapa point, diantaranya adalah :

  1. menampilkan user yang sedang login.
  2. membuat fitur toggle sidebar.
  3. membuat fitur logout.

Untuk point pertama, kita akan belajar melakukan fetching ke dalam server menggunakan Rest API untuk mendapatkan data user yang sedang login. Kemudian point kedua kita akan belajar membuat toggle sidebar di dalam layout admin.

Dan terakhir kita akan belajar membuat fitur logout. Dimana kita juga akan melakukan fetching ke dalam server untuk melakukan proses logout dengan menghapus token active di dalam server dan menghapus token di client (cookies).

Silahkan buat folder baru dengan nama layouts di dalam folder src. Setelah itu silahkan buat file baru dengan nama Admin.jsx di dalam folder layouts tersebut dan masukkan kode berikut ini di dalamnya.

//import React
import React, {useState, useEffect} from 'react';

//import component bootstrap
import { NavDropdown } from 'react-bootstrap';

//import Sidebar
import Sidebar from '../components/admin/Sidebar';

//import BASE URL API
import Api from '../api';

//import js cookie
import Cookies from "js-cookie";

//hook link
import { useNavigate, Link } from 'react-router-dom';

//import toats
import toast from "react-hot-toast";

const LayoutAdmin =({children}) =>{

    //state user
    const [user, setUser] = useState({});

    //state toggle
    const [sidebarToggle, setSidebarToggle] = useState(false);

    //navigate
    const navigate = useNavigate();

    //token
    const token = Cookies.get("token");

    //function toggle hanlder
    const sidebarToggleHandler = (e) => {
        e.preventDefault();

        if(!sidebarToggle) {
            //add class on body
            document.body.classList.add('sb-sidenav-toggled');

            //set state "sidebarToggle" to true
            setSidebarToggle(true);
        } else {

            //remove class on body
            document.body.classList.remove('sb-sidenav-toggled');

            //set state "sidebarToggle" to false
            setSidebarToggle(false);
        }
    }

    //fetchData
    const fetchData = async () => {

        //fetch on Rest API
        await Api.get('/api/admin/user', {
            headers: {
                
                //header Bearer + Token
                Authorization: `Bearer ${token}`,
            }
        })
        .then((response) => {

            //set state "user"
            setUser(response.data);
        })
    };

    //hook useEffect
    useEffect(() => {

        //call function "fetchData"
        fetchData();

        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    //function logout
    const logoutHandler = async (e) => {
        e.preventDefault();

        await Api.post('/api/admin/logout', null, {
            headers: {
                
                //header Bearer + Token
                Authorization: `Bearer ${token}`,
            }
        })
        .then(() => {

            //remove token
            Cookies.remove('token');

            //show toast
            toast.success("Logout Successfully.", {
                duration: 4000,
                position: "top-right",
                style: {
                    borderRadius: '10px',
                    background: '#333',
                    color: '#fff',
                },
            });

            //redirect login page
            navigate('/admin/login');
        })
    }

    return(
        <React.Fragment>
            <div className="d-flex sb-sidenav-toggled" id="wrapper">
            <div className="bg-white" id="sidebar-wrapper">
                <div className="sidebar-heading bg-light text-center"><i className="fa fa-map-marked-alt"></i> <strong>TRAVEL GIS</strong> <small>ADMIN</small></div>
                <Sidebar />
            </div>
            <div id="page-content-wrapper">
                <nav className="navbar navbar-expand-lg navbar-light bg-light">
                    <div className="container-fluid">
                        <button className="btn btn-success-dark" onClick={sidebarToggleHandler}><i className="fa fa-list-ul"></i></button>
                        <div className="collapse navbar-collapse" id="navbarSupportedContent">
                            <ul className="navbar-nav ms-auto mt-2 mt-lg-0">
                            <NavDropdown title={user.name} className="fw-bold" id="basic-nav-dropdown">
                                <NavDropdown.Item as={Link} to="/" target="_blank"><i className="fa fa-external-link-alt me-2"></i> Visit Web</NavDropdown.Item>
                                <NavDropdown.Divider />
                                <NavDropdown.Item as={Link} to="/admin/categories"><i className="fa fa-folder me-2"></i> Categories</NavDropdown.Item>
                                <NavDropdown.Item as={Link} to="/admin/places"><i className="fa fa-map-marked-alt me-2"></i> Places</NavDropdown.Item>
                                <NavDropdown.Item as={Link} to="/admin/sliders"><i className="fa fa-images me-2"></i> Sliders</NavDropdown.Item>
                                <NavDropdown.Item as={Link} to="/admin/users"><i className="fa fa-users me-2"></i> Users</NavDropdown.Item>
                                <NavDropdown.Divider />
                                <NavDropdown.Item onClick={logoutHandler}><i className="fa fa-sign-out-alt me-2"></i> Logout</NavDropdown.Item>
                            </NavDropdown>
                            </ul>
                        </div>
                    </div>
                </nav>
                <div className="container-fluid">
                    {children}
                </div>
            </div>
        </div>
        </React.Fragment>
    )
}

export default LayoutAdmin;

Dari penambahan kode di atas, pertama kita import 2 hook dari React, yaitu useState dan useEfferct. Untuk hook useState akan kita gunakan untuk membuat sebuah state.

Sedangkan untuk hook useEffect merupakan hook yang akan dijalankan pertama kali saat component di load dan disini kita akan manfaatkan untuk proses fetching ke dalam Rest API.

INFORMASI : hook useEffect merupakan kombinasi dari hook componentDidMount, componentDidUpdate dan componentWillUnmount di lifecycle React.

//import React
import React, {useState, useEffect} from 'react';

Karena kita akan membuat navbar dengan menu dropdown, maka kita akan memanggil component NavDropdown dari React Bootstrap.

//import component bootstrap
import { NavDropdown } from 'react-bootstrap';

Kemudian kita import component sidebar yang sudah pernah kita buat sebelumnya.

//import Sidebar
import Sidebar from '../components/admin/Sidebar';

Dan karena akan berurusan dengan Rest API, maka kita akan import global endpoint yang sudah kita buat sebelumnya.

//import BASE URL API
import Api from '../api';

Kita juga akan import package Js Cookie, karena kita akan gunakan untuk mendapatkan token yang telah disimpan di dalam cookie.

//import js cookie
import Cookies from "js-cookie";

Kemudian kita import provider Link dan hook useNavigate dari React Router DOM.

//hook link
import { useNavigate, Link } from 'react-router-dom';

Dan saat proses pembuatan fitur logout kita akan menampilkan sebuah notifikasi, maka kita butuh import package React Hot Toast.

//import toats
import toast from "react-hot-toast";

Untuk function component LayoutAdmin kita berikan paremeter berupa props children. Dimana props tersebut akan digunakan untuk merender view/component yang meng-extends dari layout ini.

const LayoutAdmin =({children}) => {

	//...

}

Di dalam-nya, pertama kita membuat state baru dengan nama user, dimana state tersebut akan kita gunakan untuk menyimpan object user dari response Rest API nantinya, dan default kita berikan value object kosong {}.

//state user
const [user, setUser] = useState({});

Kemudian kita buat state lagi untuk meng-handle fitur toggle sidebar dan disini default-nya kita berikan nilai false.

//state toggle
const [sidebarToggle, setSidebarToggle] = useState(false);

Dan kita buat variable dengan nama navigate yang berisi hook useNavigate. Dengan tujuan agar kita lebih mudah dalam memanggil hook tersebut.

//navigate
const navigate = useNavigate();

Setelah itu kita buat variable lagi dengan nama token, yang isinya adalah token yang ada di dalam cookie.

//token
const token = Cookies.get("token");

Dan kita membuat function yang bernama sidebarToggleHandler, fungsi ini akan dijalankan ketika kita melakukan klik di button toogle. Dimana akan kita gunakan untuk melakukan show dan hide sidebar secara interaktif.

<button className="btn btn-success-dark" onClick={sidebarToggleHandler}><i className="fa fa-list-ul"></i></button>

Ketika button di atas di klik, maka function di bawah ini akan di eksekusi.

//function toggle hanlder
const sidebarToggleHandler = (e) => {

	//...

}

Di dalam function sidebarToggleHandler, kita membuat sebuah kondisi untuk melakukan penambahan class sb-sidenav-toggled jika bernilai true dan menghapusnya jika bernilai false.

Setalah itu, kita buat function lagi dengan nama fetchData. Dimana function ini akan kita jadikan asynchronus, karena akan kita gunakan untuk melakukan fetching ke dalam Rest API.

//fetchData
const fetchData = async () => {

	//...

}

Di dalam function fetchData tersebut, kita melakukan HTTP request ke dalam endpoint /api/admin/user dengan method GET. Dan kita juga sertakan headers dengan Authorization yang berisi token dari cookies, karena endpoint tersebut membutuhkan otentikasi.

//fetch on Rest API
await Api.get('/api/admin/user', {
    headers: {

        //header Bearer + Token
        Authorization: `Bearer ${token}`,
    }
})

Jika dari proses fetching berhasil dilakukan, maka akan masuk ke dalam promise then. Dan di dalamnya kita melakukan assign response data dari Rest API ke dalam state user.

//set state "user"
setUser(response.data);

Agar function fetchData dapat dijalankan saat component di load, maka kita perlu memanggilnya di dalam hook useEffect.

//hook useEffect
useEffect(() => {

    //call function "fetchData"
    fetchData();

    // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

Dan untuk memanggil nama user di dalam JSX, kita cukup seperti ini :

{user.name}

Kemudian kita buat function lagi untuk proses logout, yaitu bernama logoutHandler. Fungsi ini akan dijalankan ketika menu logout diklik.

<NavDropdown.Item onClick={logoutHandler}><i className="fa fa-sign-out-alt me-2"></i> Logout</NavDropdown.Item>

Jika menu di atas diklik, maka akan menjalankan function logoutHanlder berikut ini :

//function logout
const logoutHandler = async (e) => {

	//...

}

Di dalam function tersebut kita melakukan HTTP request ke endpoint /api/admin/logout dengan method POST.

Dan karena kita tidak mengirimkan data apapun, maka untuk parameter kedua kita set null dan untuk headers-nya kita berikan Authorizarion dengan value token dari cookie.

await Api.post('/api/admin/logout', null, {
    headers: {

        //header Bearer + Token
        Authorization: `Bearer ${token}`,
    }
})

Jika dari proses logout berhasil dilakukan di dalam server, maka kita akan lanjutkan disisi client, yaitu menghapus token dari cookie.

//remove token
Cookies.remove('token');

Setelah itu, kita akan menampilkan notifikasi yang berisi informasi logout berhasil dilakukan.

//show toast
toast.success("Logout Successfully.", {
    duration: 4000,
    position: "top-right",
    style: {
        borderRadius: '10px',
        background: '#333',
        color: '#fff',
    },
});

Kemudian kita arahkan atau redirect ke halaman login.

//redirect login page
navigate('/admin/login');

Dan untuk merender view/component yang meng-extends dari layout ini, kita bisa menggunakan sintaks seperti berikut ini :

{children}

Konfigurasi Private Route Dashboard


Sebelum membuat private route, pertama-tama kita akan membuat view/component dashboard terlebih dahulu. Di dalam view/component dashboard nantinya akan kita gunakan untuk menampilkan statistik data atau jumlah data yang ada di beberapa table.

Langkah 1 - Membuat View/Component Dashboard

Sekarang kita akan membuat view/component dashboard terlebih dahulu. Silahkan buat folder baru dengan nama dashboard di dalam folder src/pages/admin. Dan di dalam folder dashboard silahkan buat file baru dengan nama Index.jsx dan masukkan kode berikut ini di dalamnya.

//import react  
import React from "react";

//import layout admin
import LayoutAdmin from "../../../layouts/Admin";

function Dashboard() {

	//title page
    document.title = "Dashboard - Administrator Travel GIS";

    return(
        <React.Fragment>
            <LayoutAdmin>
                <div className="row mt-4">
                    <div className="col-12">
                        <div className="card border-0 rounded shadow-sm border-top-success">
                            <div className="card-header">
                                <span className="font-weight-bold"><i className="fa fa-tachometer-alt"></i> DASHBOARD</span>
                            </div>
                        </div>
                    </div>
                </div>
            </LayoutAdmin>
        </React.Fragment>
    )

}

export default Dashboard

Dari penambahan kode di dalam view/component dashboard di atas, pertama-tama kita import React dari react.

//import react  
import React from "react";

Setelah itu, kita import LayoutAdmin yang pernah kita buat sebelumnya. Dimana kode yang akan kita tulis nanti di dalam JSX kita letakkan di dalam LayoutAdmin.

//import layout admin
import LayoutAdmin from "../../../layouts/Admin";

Kemudian perhatikan di dalam JSX, kita taruh kode yang kita buat di dalam LayoutAdmin. Dengan tujuan agar kode yang kita tambahkan menjadi child dari LayoutAdmin.

 <LayoutAdmin>
 
 	//...
 
 </LayoutAdmin>

Langkah 2 - Membuat Component Private Route

Sekarang kita akan membuat sebuah component yang digunakan untuk melakukan handle sebuah halaman yang membutuhkan proses otentikasi.

Silahkan buat file baru dengan nama PrivateRoutes.jsx di dalam folder src/routes. Setelah itu silahkan masukkan kode berikut ini di dalamnya.

//import cookie
import Cookies from "js-cookie";

//import react router dom
import { Navigate } from "react-router-dom";

function privateRoutes({ children }) {

    //token from cookie
    const token = Cookies.get('token');

    //if token not set
    if (!token) {
        return <Navigate to="/" replace />;
    }

    return children;

}

export default privateRoutes;

Dari penambahan kode di atas, pertama kita import Js Cookie terlebih dahulu.

//import cookie
import Cookies from "js-cookie";

Setelah itu, kita import Navigate dari React Router DOM.

//import react router dom
import { Navigate } from "react-router-dom";

Di dalam function privateRoutes terdapat sebuah parameter yang bernama children. Isi dari parameter tersebut adalah route yang akan dirender.

function privateRoutes({ children }) {

	//...
	
}

Setelah itu kita buat variable baru dengan nama token dan isinya adalah token yang diambil dari cookies browser.

//token from cookie
const token = Cookies.get('token');

Kemudian dibawahnya kita membuat kondisi, jika varibale token bernilai false, maka kita akan redirect atau arahkan ke URL / atau pada halaman login.

//if token not set
if(!token) {
    return < Navigate to = "/" replace / > ;
}

Tapi, jika token tersedia, maka kita return children.

Langkah 3 - Konfigurasi Private Route Dashboard

Setelah berhasil membuat view/component untuk halaman dashboard dan juga berhasil membuat component untuk private route, maka sekarang kita lanjutkan untuk melakukan konfigurasi private route halaman dashboard.

Silahkan buka file src/routes/routes.jsx, kemudian ubah kode-nya menjadi seperti berikut ini :

//import react router dom
import { Routes, Route } from "react-router-dom";

//=======================================================================
//ADMIN
//=======================================================================

//import view Login
import Login from '../pages/admin/Login.jsx';

//import component private routes
import PrivateRoute from "./PrivateRoutes";

//import view admin Dashboard
import Dashboard from '../pages/admin/dashboard/Index.jsx';

function RoutesIndex() {
    return (
        <Routes>

            {/* route "/admin/login" */}
            <Route path="/admin/login" element={<Login />} />

            {/* private route "/admin/dashboard" */}
            <Route
                path="/admin/dashboard"
                element={
                        <PrivateRoute>
                            <Dashboard />
                        </PrivateRoute>
                }
            />

        </Routes>
    )
}

export default RoutesIndex

Dari perubahan kode di atas. Pertama kita melakukan import component PrivateRoute yang sudah kita buat sebelumnya.

//import component private routes
import PrivateRoute from "./PrivateRoutes";

Setelah itu, kita import view/component halaman dashboard-nya.

//import view admin Dashboard
import Dashboard from '../pages/admin/dashboard/Index.jsx';

Kemudian, kita konfigurasi agar view/component dashboard di atas menggunakan PrivateRoute.

{/* private route "/admin/dashboard" */}
<Route
    path="/admin/dashboard"
    element={
    <PrivateRoute>
        <Dashboard />
    </PrivateRoute>
    }
/>

Langkah 4 - Uji Coba Halaman Dashboard

Sekarang silahkan lakukan proses login dan jika berhasil maka kita akan menampilkan hasil seperti berikut ini :

Menampilkan Statistik Data di Halaman Dashboard


Setelah berhasil menampilkan halaman dashboard, sekarang kita akan lanjutkan belajar menampilkan statistik data di halaman dashboard, seperti jumlah categories, places, sliders dan users.

Langkah 1 - Edit View/Component Dashboard

Silahkan buka file src/pages/admin/dashboard/Index.jsx, kemudian ubah semua kode-nya menjadi seperti berikut ini :

//import react  
import React, { useState, useEffect } from "react";

//import layout admin
import LayoutAdmin from "../../../layouts/Admin";

//import BASE URL API
import Api from "../../../api";

//import js cookie
import Cookies from "js-cookie";

function Dashboard() {

	//title page
    document.title = "Dashboard - Administrator Travel GIS";

    //set state
    const [categories, setCategories] = useState(0);
    const [places, setPlaces] = useState(0);
    const [sliders, setSliders] = useState(0);
    const [users, setUsers] = useState(0);

    //token
    const token = Cookies.get('token');

    //function fetchData
    const fetchData = async () => {

        //fetching data from Rest API
        const response = await Api.get('api/admin/dashboard', {
            headers: {

                //header Bearer + Token
                Authorization: `Bearer ${token}`
            }
        });

        //get response data
        const data = await response.data.data;

        //assign response data to state
        setCategories(data.categories);
        setPlaces(data.places);
        setSliders(data.sliders);
        setUsers(data.users);
    }

    //hook useEffect
    useEffect(() => {

        //call method "fetchData"
        fetchData();

        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    return (
        <React.Fragment>
            <LayoutAdmin>
                <div className="row mt-4">
                    <div className="col-12 col-lg-3 mb-4">
                    <div className="card border-0 shadow-sm overflow-hidden">
                        <div className="card-body p-0 d-flex align-items-center">
                            <div className="bg-primary py-4 px-5 mfe-3" style={{ width: "130px" }}>
                                <i className="fas fa-folder fa-2x text-white"></i>
                            </div>
                            <div>
                                <div className="text-value text-primary">{categories}</div>
                                <div className="text-muted text-uppercase font-weight-bold small">
                                    CATEGORIES
                                </div>
                            </div>
                        </div>
                    </div>
                    </div>
                    <div className="col-12 col-lg-3 mb-4">
                    <div className="card border-0 rounded shadow-sm overflow-hidden">
                        <div className="card-body p-0 d-flex align-items-center">
                            <div className="bg-success py-4 px-5 mfe-3" style={{ width: "130px" }}>
                                <i className="fas fa-map-marked-alt fa-2x text-white"></i>
                            </div>
                            <div>
                                <div className="text-value text-success">{places}</div>
                                <div className="text-muted text-uppercase font-weight-bold small">
                                    PLACES
                                </div>
                            </div>
                        </div>
                    </div>
                    </div>
                    <div className="col-12 col-lg-3 mb-4">
                    <div className="card border-0 rounded shadow-sm overflow-hidden">
                        <div className="card-body p-0 d-flex align-items-center">
                            <div className="bg-warning py-4 px-5 mfe-3" style={{ width: "130px" }}>
                                <i className="fas fa-images fa-2x text-white"></i>
                            </div>
                            <div>
                                <div className="text-value text-warning">{sliders}</div>
                                <div className="text-muted text-uppercase font-weight-bold small">
                                    SLIDERS
                                </div>
                            </div>
                        </div>
                    </div>
                    </div>
                    <div className="col-12 col-lg-3 mb-4">
                    <div className="card border-0 rounded shadow-sm overflow-hidden">
                        <div className="card-body p-0 d-flex align-items-center">
                            <div className="bg-danger py-4 px-5 mfe-3" style={{ width: "130px" }}>
                                <i className="fas fa-users fa-2x text-white"></i>
                            </div>
                            <div>
                                <div className="text-value text-danger">{users}</div>
                                <div className="text-muted text-uppercase font-weight-bold small">
                                    USERS
                                </div>
                            </div>
                        </div>
                    </div>
                    </div>
                </div>
            </LayoutAdmin>
        </React.Fragment>
    )

}

export default Dashboard;

Dari perubahan kode di atas, pertama kita import 2 hook dari React, yaitu useState dan useEffect.

//import react  
import React, { useState, useEffect } from "react";

Karena kita akan melakukan HTTP request ke dalam server, maka kita import global api endpoint.

//import BASE URL API
import Api from "../../../api";

Dan karena API yang akan kita fetch membutuhkan token, maka kita juga import package Js Cookie untuk mempermudah kita dalam memanggil token di cookies.

//import js cookie
import Cookies from "js-cookie";

Di dalam function component Dashboard, pertama-tama kita membuat 4 state baru, yaitu : categories, places, sliders dan users.

//set state
const [categories, setCategories] = useState(0);
const [places, setPlaces] = useState(0);
const [sliders, setSliders] = useState(0);
const [users, setUsers] = useState(0);

Ke-empat state tersebut akan kita gunakan untuk menyimpan jumlah data yang ada di masing-masing table. Dan untuk default value-nya kita berikan 0.

Setelah itu kita buat varibale dengan nama token yang berisi token dari cookies.

//token
const token = Cookies.get('token');

Kemudian kita buat sebuah function dengan nama fetchData dengan jenis asynchronus, yang mana di dalamnya kita melakukan HTTP request ke dalam server dengan method GET. Dan untuk headers Authorization kita isi dengan Berare + Token.

//fetching data from Rest API
const response = await Api.get('api/admin/dashboard', {
    headers: {

        //header Bearer + Token
        Authorization: `Bearer ${token}`
    }
});

Setelah itu, kita buat variable data yang isinya adalah response dari Rest API.

//get response data
const data = await response.data.data;

Dan kita assign data response tersebut ke dalam masing-masing state.

//assign response data to state
setCategories(data.categories);
setPlaces(data.places);
setSliders(data.sliders);
setUsers(data.users);

Agar function fetchData dapat dijalankan saat halaman di akses, maka kita panggil di dalam hook useEffect.

//hook useEffect
useEffect(() => {

    //call method "fetchData"
    fetchData();

    // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

Dan untuk menampilkan data-nya di dalam JSX, kita cukup memanggil nama state-nya saja. Kurang lebih seperti berikut ini :

{categories}

{places}

{sliders}

{users}

Langkah 2 - Uji Coba Halaman Dashboard

Silahkan reload/refresh halaman dashboard dan jika berhasil maka kita akan mendpaatkan hasil seperti berikut ini :

Di atas, kita telah berhasil menampilkan 4 statistik data, yaaitu categories, places, sliders dan users.

Beranda Mundur Maju