Tutorial #13: Halaman Login Multi-Level (Admin & Petugas) di React
Halo para pengelola bank! Setelah berhasil membuat API untuk admin dan petugas di tutorial #4–#6, sekarang saatnya kita membangun antarmuka login dan dashboard mereka di React. Mereka akan login, dan kita akan arahkan ke halaman yang sesuai. Siap jadi resepsionis bank digital?
Yang akan kita lakukan:
- ✅ Menggunakan halaman login yang sama (sudah ada dari tutorial #8) untuk semua role.
- ✅ Menyimpan token dan data user setelah login (role ikut tersimpan).
- ✅ Redirect ke dashboard yang sesuai (admin atau petugas).
- ✅ Membuat komponen dashboard sederhana untuk admin dan petugas.
- ✅ Menambahkan proteksi route berdasarkan role (private route).
- ✅ Menampilkan notifikasi SweetAlert untuk sukses/gagal login.
Langkah 1: Struktur Routing untuk Admin & Petugas
Buka src/App.js. Tambahkan rute baru untuk dashboard admin dan petugas, serta gunakan komponen PrivateRoute yang sudah dimodifikasi untuk mengecek role.
import AdminDashboard from './pages/AdminDashboard';
import PetugasDashboard from './pages/PetugasDashboard';
// ... di dalam Routes
<Route path="/login" element={<LoginPage />} />
<Route path="/dashboard" element={
<PrivateRoute allowedRoles={['nasabah']}>
<DashboardPage />
</PrivateRoute>
} />
<Route path="/admin/dashboard" element={
<PrivateRoute allowedRoles={['admin']}>
<AdminDashboard />
</PrivateRoute>
} />
<Route path="/petugas/dashboard" element={
<PrivateRoute allowedRoles={['petugas']}>
<PetugasDashboard />
</PrivateRoute>
} />
Langkah 2: PrivateRoute dengan Pengecekan Role
Buat atau perbarui file src/components/PrivateRoute.js:
import React from 'react';
import { Navigate } from 'react-router-dom';
function PrivateRoute({ children, allowedRoles }) {
const token = localStorage.getItem('token');
const user = JSON.parse(localStorage.getItem('user') || '{}');
if (!token) {
return <Navigate to="/login" />;
}
if (allowedRoles && !allowedRoles.includes(user.role)) {
// Redirect ke dashboard yang sesuai jika role tidak diizinkan
if (user.role === 'admin') return <Navigate to="/admin/dashboard" />;
if (user.role === 'petugas') return <Navigate to="/petugas/dashboard" />;
if (user.role === 'nasabah') return <Navigate to="/dashboard" />;
return <Navigate to="/" />;
}
return children;
}
export default PrivateRoute;
Langkah 3: Modifikasi Halaman Login (sudah ada)
Di src/pages/LoginPage.js, kita hanya perlu menambahkan logika redirect setelah login sukses berdasarkan role. Kode yang sudah ada (dari tutorial #8) bisa ditambahkan bagian berikut di dalam blok try setelah menyimpan token:
// Redirect berdasarkan role
if (user.role === 'admin') {
navigate('/admin/dashboard');
} else if (user.role === 'petugas') {
navigate('/petugas/dashboard');
} else {
navigate('/dashboard'); // nasabah
}
Pastikan juga di response login dari API, kita mengirimkan role user (seperti yang sudah dilakukan di tutorial #3).
Langkah 4: Membuat Dashboard Admin Sederhana
Buat file src/pages/AdminDashboard.js:
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Swal from 'sweetalert2';
function AdminDashboard() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUsers();
}, []);
const fetchUsers = async () => {
const token = localStorage.getItem('token');
try {
const response = await axios.get('http://127.0.0.1:8000/api/users', {
headers: { Authorization: `Bearer ${token}` }
});
setUsers(response.data);
} catch (error) {
Swal.fire('Error', 'Gagal memuat data pengguna', 'error');
} finally {
setLoading(false);
}
};
// Hitung jumlah berdasarkan role
const adminCount = users.filter(u => u.role.name === 'Admin').length;
const petugasCount = users.filter(u => u.role.name === 'Petugas').length;
const nasabahCount = users.filter(u => u.role.name === 'Nasabah').length;
return (
<div className="container mt-4">
<h2>Dashboard Admin</h2>
<div className="row">
<div className="col-md-3 mb-3">
<div className="card text-white bg-primary">
<div className="card-body">
<h5 className="card-title">Total Pengguna</h5>
<p className="card-text display-6">{users.length}</p>
</div>
</div>
</div>
<div className="col-md-3 mb-3">
<div className="card text-white bg-danger">
<div className="card-body">
<h5 className="card-title">Admin</h5>
<p className="card-text display-6">{adminCount}</p>
</div>
</div>
</div>
<div className="col-md-3 mb-3">
<div className="card text-white bg-success">
<div className="card-body">
<h5 className="card-title">Petugas</h5>
<p className="card-text display-6">{petugasCount}</p>
</div>
</div>
</div>
<div className="col-md-3 mb-3">
<div className="card text-white bg-warning">
<div className="card-body">
<h5 className="card-title">Nasabah</h5>
<p className="card-text display-6">{nasabahCount}</p>
</div>
</div>
</div>
</div>
<div className="card mt-4">
<div className="card-header bg-primary text-white">
<h5 className="mb-0">Manajemen Pengguna (Admin & Petugas)</h5>
</div>
<div className="card-body">
<table className="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Nama</th>
<th>Email</th>
<th>Role</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
{users.filter(u => u.role.name !== 'Nasabah').map(user => (
<tr key={user.id}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>{user.email}</td>
<td>
<span className={`badge ${user.role.name === 'Admin' ? 'bg-danger' : 'bg-success'}`}>
{user.role.name}
</span>
</td>
<td>
<button className="btn btn-sm btn-warning me-2">Edit</button>
<button className="btn btn-sm btn-danger">Hapus</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
}
export default AdminDashboard;
Langkah 5: Dashboard Petugas Sederhana
Buat file src/pages/PetugasDashboard.js:
import React, { useState } from 'react';
import axios from 'axios';
import Swal from 'sweetalert2';
function PetugasDashboard() {
const [form, setForm] = useState({
account_id: '',
amount: '',
description: ''
});
const [loading, setLoading] = useState(false);
const handleChange = (e) => {
setForm({ ...form, [e.target.name]: e.target.value });
};
const handleDeposit = async (e) => {
e.preventDefault();
setLoading(true);
const token = localStorage.getItem('token');
try {
await axios.post('http://127.0.0.1:8000/api/deposit', form, {
headers: { Authorization: `Bearer ${token}` }
});
Swal.fire('Sukses', 'Setor tunai berhasil', 'success');
setForm({ account_id: '', amount: '', description: '' });
} catch (error) {
Swal.fire('Gagal', error.response?.data?.message || 'Terjadi kesalahan', 'error');
} finally {
setLoading(false);
}
};
const handleWithdraw = async (e) => {
e.preventDefault();
setLoading(true);
const token = localStorage.getItem('token');
try {
await axios.post('http://127.0.0.1:8000/api/withdraw', form, {
headers: { Authorization: `Bearer ${token}` }
});
Swal.fire('Sukses', 'Tarik tunai berhasil', 'success');
setForm({ account_id: '', amount: '', description: '' });
} catch (error) {
Swal.fire('Gagal', error.response?.data?.message || 'Terjadi kesalahan', 'error');
} finally {
setLoading(false);
}
};
return (
<div className="container mt-4">
<h2>Dashboard Petugas</h2>
<div className="row">
<div className="col-md-6">
<div className="card">
<div className="card-header bg-success text-white">
<h5 className="mb-0">Form Setor Tunai</h5>
</div>
<div className="card-body">
<form onSubmit={handleDeposit}>
<div className="mb-3">
<label className="form-label">ID Rekening</label>
<input type="number" className="form-control" name="account_id" value={form.account_id} onChange={handleChange} required />
</div>
<div className="mb-3">
<label className="form-label">Jumlah (Rp)</label>
<input type="number" className="form-control" name="amount" value={form.amount} onChange={handleChange} required />
</div>
<div className="mb-3">
<label className="form-label">Keterangan</label>
<input type="text" className="form-control" name="description" value={form.description} onChange={handleChange} />
</div>
<button type="submit" className="btn btn-success w-100" disabled={loading}>
{loading ? 'Memproses...' : 'Setor'}
</button>
</form>
</div>
</div>
</div>
<div className="col-md-6">
<div className="card">
<div className="card-header bg-danger text-white">
<h5 className="mb-0">Form Tarik Tunai</h5>
</div>
<div className="card-body">
<form onSubmit={handleWithdraw}>
<div className="mb-3">
<label className="form-label">ID Rekening</label>
<input type="number" className="form-control" name="account_id" value={form.account_id} onChange={handleChange} required />
</div>
<div className="mb-3">
<label className="form-label">Jumlah (Rp)</label>
<input type="number" className="form-control" name="amount" value={form.amount} onChange={handleChange} required />
</div>
<div className="mb-3">
<label className="form-label">Keterangan</label>
<input type="text" className="form-control" name="description" value={form.description} onChange={handleChange} />
</div>
<button type="submit" className="btn btn-danger w-100" disabled={loading}>
{loading ? 'Memproses...' : 'Tarik'}
</button>
</form>
</div>
</div>
</div>
</div>
</div>
);
}
export default PetugasDashboard;
Langkah 6: Menyesuaikan Navigasi (Navbar)
Update src/components/Navbar.js agar menampilkan menu yang sesuai dengan role pengguna yang login.
// Di dalam komponen Navbar
import { Link, useNavigate } from 'react-router-dom';
import Swal from 'sweetalert2';
function Navbar() {
const navigate = useNavigate();
const token = localStorage.getItem('token');
const user = JSON.parse(localStorage.getItem('user') || '{}');
const handleLogout = () => {
Swal.fire({
title: 'Yakin mau logout?',
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Ya, logout',
cancelButtonText: 'Batal'
}).then((result) => {
if (result.isConfirmed) {
localStorage.removeItem('token');
localStorage.removeItem('user');
navigate('/login');
Swal.fire('Berhasil logout!', '', 'success');
}
});
};
return (
<nav className="navbar navbar-expand-lg navbar-dark bg-primary">
<div className="container">
<Link className="navbar-brand" to="/">🏦 Bank Tabungan</Link>
<button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarNav">
<ul className="navbar-nav ms-auto">
<li className="nav-item">
<Link className="nav-link" to="/">Home</Link>
</li>
{!token ? (
<li className="nav-item">
<Link className="nav-link" to="/login">Login</Link>
</li>
) : (
<>
{user.role === 'nasabah' && (
<li className="nav-item">
<Link className="nav-link" to="/dashboard">Dashboard</Link>
</li>
)}
{user.role === 'admin' && (
<li className="nav-item">
<Link className="nav-link" to="/admin/dashboard">Admin Dashboard</Link>
</li>
)}
{user.role === 'petugas' && (
<li className="nav-item">
<Link className="nav-link" to="/petugas/dashboard">Petugas Dashboard</Link>
</li>
)}
<li className="nav-item">
<span className="nav-link">Halo, {user.name}</span>
</li>
<li className="nav-item">
<button className="btn btn-outline-light btn-sm" onClick={handleLogout}>Logout</button>
</li>
</>
)}
</ul>
</div>
</div>
</nav>
);
}
export default Navbar;
Langkah 7: Uji Coba Login Multi-Level
- Login sebagai admin (email: admin@bank.com, password: rahasia123) → harusnya redirect ke
/admin/dashboard. - Login sebagai petugas (email: petugas@bank.com, password: rahasia123) → redirect ke
/petugas/dashboard. - Login sebagai nasabah (budi@mail.com) → redirect ke
/dashboard(yang sudah dibuat di tutorial #9). - Coba akses langsung
/admin/dashboardtanpa token → redirect ke login. - Coba akses
/admin/dashboarddengan token nasabah → redirect ke dashboard nasabah (karena tidak punya akses).
Kesimpulan
- ✅ Admin dan petugas bisa login menggunakan halaman yang sama, dan diarahkan ke dashboard masing-masing.
- ✅ Proteksi route berdasarkan role bekerja dengan baik.
- ✅ Dashboard admin menampilkan statistik dan tabel pengguna (fitur CRUD akan kita lanjutkan di tutorial berikutnya).
- ✅ Dashboard petugas sudah memiliki form untuk setor dan tarik tunai.
Di tutorial selanjutnya (#14: Dashboard Admin – Manajemen Pengguna (CRUD) dengan React) kita akan membuat fitur tambah, edit, dan hapus pengguna dari dashboard admin. Sampai jumpa!
Daftar Tutorial Lanjutan (Rencana)
- Tutorial #14: Dashboard Admin – Manajemen Pengguna (CRUD) dengan React
- Tutorial #15: Dashboard Petugas – Melihat Mutasi Nasabah
- Tutorial #16: Fitur Ubah Password untuk Semua Level Pengguna
- Tutorial #17: Menambahkan Fitur Profil dan Edit Profil
- Tutorial #18: Laporan Transaksi (Admin) – Filter dan Export ke Excel/PDF
- Tutorial #19: Finalisasi dan Penyempurnaan Aplikasi