Tutorial Laravel & ReactJS #8: Halaman Login Nasabah dan Autentikasi dengan API

Tutorial #8: Halaman Login Nasabah dan Autentikasi dengan API

Halo para nasabah digital! 💻 Hari ini kita akan membuat halaman login yang sesungguhnya di React. Kita akan hubungkan ke API Laravel yang sudah kita buat, simpan token, dan redirect ke dashboard. Anggap saja kita membuat pintu masuk ke bank digital! 🚪

😂 Joke biar melek: "Kenapa form login harus diisi? Karena kalau kosong, yang masuk bukan nasabah, tapi hantu!" 👻

Yang akan kita lakukan:

  • ✅ Membuat form login dengan state (email, password).
  • ✅ Menangani submit form dan mengirim POST request ke API /api/login.
  • ✅ Menyimpan token yang didapat ke localStorage.
  • ✅ Redirect ke halaman dashboard setelah login sukses.
  • ✅ Menampilkan notifikasi SweetAlert jika login gagal.
  • ✅ Menambahkan proteksi route (hanya bisa akses dashboard jika sudah login).

Langkah 1: Install Axios (Opsional, tapi lebih enak)

Kita bisa pakai fetch bawaan JavaScript, tapi biar lebih mudah kita pakai Axios. Install axios di folder frontend:

npm install axios

Axios ini kayak kurir yang mengantar data kita ke backend dan membawa balasan. 📦

💡 Alternatif: Kalau nggak mau ribet install, bisa pakai fetch. Tapi axios lebih pendek syntax-nya dan otomatis parse JSON.

Langkah 2: Membuat Halaman Login yang Interaktif

Buka file src/pages/LoginPage.js. Kita akan ubah dari yang sebelumnya statis menjadi dinamis dengan state dan fungsi handle login.

Pertama, import yang dibutuhkan:

import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom'; // untuk redirect
import axios from 'axios';
import Swal from 'sweetalert2';

Kemudian buat komponen LoginPage dengan state email dan password:

function LoginPage() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [loading, setLoading] = useState(false);
  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);

    try {
      const response = await axios.post('http://127.0.0.1:8000/api/login', {
        email: email,
        password: password
      });

      // Jika sukses, simpan token dan data user
      const { token, user } = response.data;
      localStorage.setItem('token', token);
      localStorage.setItem('user', JSON.stringify(user));

      // Tampilkan notifikasi sukses
      Swal.fire({
        icon: 'success',
        title: 'Login Berhasil!',
        text: `Selamat datang, ${user.name}!`,
        timer: 1500,
        showConfirmButton: false
      });

      // Redirect ke dashboard
      navigate('/dashboard');
    } catch (error) {
      // Tangani error
      const message = error.response?.data?.message || 'Login gagal. Periksa email dan password.';
      Swal.fire({
        icon: 'error',
        title: 'Oops...',
        text: message
      });
    } finally {
      setLoading(false);
    }
  };

Jangan lupa bagian form di JSX diubah supaya terhubung ke state dan onSubmit:

return (
  <div className="row justify-content-center">
    <div className="col-md-6">
      <div className="card">
        <div className="card-header bg-primary text-white">
          <h4 className="mb-0">Login Nasabah</h4>
        </div>
        <div className="card-body">
          <form onSubmit={handleSubmit}>
            <div className="mb-3">
              <label className="form-label">Email</label>
              <input
                type="email"
                className="form-control"
                placeholder="masukkan email"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                required
              />
            </div>
            <div className="mb-3">
              <label className="form-label">Password</label>
              <input
                type="password"
                className="form-control"
                placeholder="••••••••"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                required
              />
            </div>
            <button
              type="submit"
              className="btn btn-primary w-100"
              disabled={loading}
            >
              {loading ? 'Memproses...' : 'Login'}
            </button>
          </form>
        </div>
      </div>
    </div>
  </div>
);

Jangan lupa untuk mengekspor komponen ini.

Langkah 3: Menyimpan Token dan Proteksi Halaman

Setelah login sukses, token disimpan di localStorage. Sekarang kita perlu melindungi halaman dashboard agar tidak bisa diakses jika belum login.

Buat komponen PrivateRoute (atau langsung gunakan di App.js). Kita akan buat file baru src/components/PrivateRoute.js:

import React from 'react';
import { Navigate } from 'react-router-dom';

function PrivateRoute({ children }) {
  const token = localStorage.getItem('token');
  return token ? children : <Navigate to="/login" />;
}

export default PrivateRoute;

Kemudian di App.js, gunakan PrivateRoute untuk membungkus halaman dashboard:

import PrivateRoute from './components/PrivateRoute';

// ... di dalam Routes
<Route path="/dashboard" element={
  <PrivateRoute>
    <DashboardPage />
  </PrivateRoute>
} />

Sekarang jika user mencoba akses /dashboard tanpa token, akan langsung diarahkan ke /login.

Langkah 4: Menambahkan Tombol Logout

Kita perlu tombol logout di dashboard. Di Navbar, kita bisa cek apakah user sudah login, jika ya tampilkan tombol logout.

Modifikasi src/components/Navbar.js:

import React from 'react';
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?',
      text: 'Kamu akan keluar dari aplikasi',
      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 Nasabah</Link>
              </li>
            ) : (
              <>
                <li className="nav-item">
                  <Link className="nav-link" to="/dashboard">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;
😆 "Logout itu kayak keluar rumah: pastiin pintu udah dikunci (token dihapus) biar nggak kemasukan maling!" 🔒

Langkah 5: Menghubungkan Dashboard ke API Nyata

Dashboard kita masih pakai data dummy. Sekarang kita akan ambil data saldo dan mutasi dari API menggunakan token yang tersimpan.

Modifikasi DashboardPage.js:

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import Swal from 'sweetalert2';

function DashboardPage() {
  const [saldo, setSaldo] = useState(null);
  const [mutasi, setMutasi] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      const token = localStorage.getItem('token');
      if (!token) return;

      try {
        // Ambil saldo
        const saldoRes = await axios.get('http://127.0.0.1:8000/api/nasabah/saldo', {
          headers: { Authorization: `Bearer ${token}` }
        });
        setSaldo(saldoRes.data);

        // Ambil mutasi
        const mutasiRes = await axios.get('http://127.0.0.1:8000/api/nasabah/mutasi', {
          headers: { Authorization: `Bearer ${token}` }
        });
        setMutasi(mutasiRes.data.mutasi);
      } catch (error) {
        Swal.fire('Gagal memuat data', error.response?.data?.message || 'Terjadi kesalahan', 'error');
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []);

  if (loading) {
    return <div className="text-center">Loading...</div>;
  }

  return (
    <div>
      <h2>Dashboard Nasabah</h2>
      <div className="card mb-4">
        <div className="card-body">
          <h5 className="card-title">Total Saldo</h5>
          <p className="card-text display-6 text-success">
            Rp {saldo?.saldo?.toLocaleString()}
          </p>
          <p>No. Rekening: {saldo?.no_rekening}</p>
        </div>
      </div>
      <h4>Mutasi Terbaru</h4>
      {mutasi.length === 0 ? (
        <p>Belum ada transaksi.</p>
      ) : (
        <table className="table table-striped">
          <thead>
            <tr>
              <th>Tanggal</th>
              <th>Jenis</th>
              <th>Jumlah</th>
              <th>Keterangan</th>
              <th>Petugas</th>
            </tr>
          </thead>
          <tbody>
            {mutasi.map((trx) => (
              <tr key={trx.id}>
                <td>{trx.tanggal}</td>
                <td>{trx.jenis}</td>
                <td>Rp {trx.jumlah.toLocaleString()}</td>
                <td>{trx.keterangan}</td>
                <td>{trx.petugas}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
}

export default DashboardPage;

Langkah 6: Uji Coba Login

Pastikan backend Laravel berjalan (php artisan serve). Buka frontend (npm start). Coba login dengan email nasabah yang sudah terdaftar (misal budi@mail.com dengan password 123456).

Jika berhasil, akan diarahkan ke dashboard dan muncul data saldo dan mutasi dari database. Jika salah password, muncul SweetAlert error.

🧪 Tips: Kalau muncul error CORS di browser, install laravel-cors di backend. Tapi untuk development, kita bisa gunakan proxy di React. Tambahkan "proxy": "http://127.0.0.1:8000" di package.json frontend, lalu ubah axios panggil /api/login tanpa domain. Atau install middleware CORS di Laravel (lebih disarankan).

Kesimpulan

  • ✅ Form login terhubung ke API dan menyimpan token.
  • ✅ Redirect otomatis ke dashboard setelah login.
  • ✅ Halaman dashboard dilindungi (tidak bisa diakses tanpa token).
  • ✅ Data saldo dan mutasi diambil dari backend real.
  • ✅ SweetAlert memberikan notifikasi manis.

Di tutorial selanjutnya (#9: Dashboard Nasabah – Menampilkan Total Saldo dan Mutasi) kita akan mempercantik dashboard, mungkin menambahkan grafik atau fitur lain. Tapi sebenarnya sudah kita lakukan di sini! Jadi tutorial #9 bisa fokus pada styling atau fitur tambahan. Sampai jumpa! 🚀

Lebih baru Lebih lama

نموذج الاتصال