Tutorial Laravel & ReactJS #18: Fitur Profil dan Edit Profil

Tutorial #18: Menambahkan Fitur Profil dan Edit Profil

Halo para pengguna setia! Setelah bisa mengubah password, sekarang saatnya kita membuat halaman profil yang keren di mana pengguna bisa melihat data diri mereka dan mengeditnya (nama dan email). Fitur ini akan tersedia untuk semua level: Nasabah, Petugas, dan Admin. Siap tampil beda? 

😂 Joke profil: "Kenapa foto profil penting? Biar teman online tahu kalau kita bukan robot!" 🤖

Yang akan kita lakukan:

  • ✅ Membuat endpoint API di Laravel untuk mengambil data profil (GET /api/profile).
  • ✅ Membuat endpoint API untuk mengupdate profil (PUT /api/profile).
  • ✅ Membuat halaman Profile di React dengan form yang sudah terisi data.
  • ✅ Validasi input (client & server) agar email unik dan nama tidak kosong.
  • ✅ Menampilkan informasi tambahan sesuai role (misal untuk nasabah: no rekening dan saldo).
  • ✅ Menambahkan link ke halaman profil di dropdown navbar.

Langkah 1: Membuat Endpoint API di Laravel

Kita perlu dua endpoint: satu untuk mengambil data profil, satu untuk mengupdate. Buka routes/api.php dan tambahkan di dalam group auth:sanctum:

Route::middleware('auth:sanctum')->group(function () {
    Route::get('/profile', [App\Http\Controllers\Api\ProfileController::class, 'show']);
    Route::put('/profile', [App\Http\Controllers\Api\ProfileController::class, 'update']);
});

Buat controller baru:

php artisan make:controller Api/ProfileController

Isi app/Http/Controllers/Api/ProfileController.php dengan:

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule;

class ProfileController extends Controller
{
    /**
     * Menampilkan data profil user yang sedang login
     */
    public function show(Request $request)
    {
        $user = $request->user()->load('role');
        
        $data = [
            'id' => $user->id,
            'name' => $user->name,
            'email' => $user->email,
            'role' => $user->role->name,
        ];

        // Jika user adalah nasabah, tambahkan data rekening
        if ($user->role_id == 3) {
            $account = $user->account;
            $data['account_number'] = $account ? $account->account_number : null;
            $data['balance'] = $account ? $account->balance : 0;
        }

        return response()->json($data);
    }

    /**
     * Mengupdate data profil (name, email)
     */
    public function update(Request $request)
    {
        $user = $request->user();

        $request->validate([
            'name' => 'required|string|max:255',
            'email' => [
                'required',
                'string',
                'email',
                'max:255',
                Rule::unique('users')->ignore($user->id),
            ],
        ]);

        $user->name = $request->name;
        $user->email = $request->email;
        $user->save();

        return response()->json([
            'message' => 'Profil berhasil diperbarui',
            'user' => [
                'name' => $user->name,
                'email' => $user->email,
            ]
        ]);
    }
}
💡 Catatan: Untuk update, kita hanya mengizinkan perubahan nama dan email. Password diubah di halaman terpisah (tutorial #17). Validasi email unik mengecualikan email user sendiri.

Langkah 2: Membuat Halaman Profil di React

Buat file baru src/pages/Profile.js dengan komponen berikut:

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

function Profile() {
  const [profile, setProfile] = useState(null);
  const [loading, setLoading] = useState(true);
  const [editing, setEditing] = useState(false);
  const [formData, setFormData] = useState({ name: '', email: '' });
  const [submitting, setSubmitting] = useState(false);
  const navigate = useNavigate();

  useEffect(() => {
    fetchProfile();
  }, []);

  const fetchProfile = async () => {
    const token = localStorage.getItem('token');
    try {
      const response = await axios.get('http://127.0.0.1:8000/api/profile', {
        headers: { Authorization: `Bearer ${token}` }
      });
      setProfile(response.data);
      setFormData({ name: response.data.name, email: response.data.email });
    } catch (error) {
      Swal.fire('Error', 'Gagal memuat profil', 'error');
    } finally {
      setLoading(false);
    }
  };

  const handleChange = (e) => {
    setFormData({ ...formData, [e.target.name]: e.target.value });
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setSubmitting(true);
    const token = localStorage.getItem('token');
    try {
      await axios.put('http://127.0.0.1:8000/api/profile', formData, {
        headers: { Authorization: `Bearer ${token}` }
      });
      Swal.fire('Berhasil', 'Profil diperbarui', 'success');
      setEditing(false);
      fetchProfile(); // refresh data
    } catch (error) {
      const message = error.response?.data?.message || 'Terjadi kesalahan';
      Swal.fire('Gagal', message, 'error');
    } finally {
      setSubmitting(false);
    }
  };

  if (loading) {
    return (
      <div className="text-center my-5">
        <div className="spinner-border text-primary" role="status">
          <span className="visually-hidden">Loading...</span>
        </div>
      </div>
    );
  }

  return (
    <div className="container mt-4">
      <div className="row justify-content-center">
        <div className="col-md-8">
          <div className="card">
            <div className="card-header bg-primary text-white">
              <h4 className="mb-0">Profil Saya</h4>
            </div>
            <div className="card-body">
              {!editing ? (
                <>
                  <div className="profile-info">
                    <p><strong>Nama:</strong> {profile.name}</p>
                    <p><strong>Email:</strong> {profile.email}</p>
                    <p><strong>Role:</strong> 
                      <span className={`badge ms-2 ${
                        profile.role === 'admin' ? 'bg-danger' : 
                        profile.role === 'petugas' ? 'bg-success' : 'bg-info'
                      }`}>
                        {profile.role}
                      </span>
                    </p>
                    
                    {/* Informasi tambahan untuk nasabah */}
                    {profile.role === 'nasabah' && (
                      <>
                        <p><strong>No. Rekening:</strong> {profile.account_number}</p>
                        <p><strong>Saldo:</strong> 
                          <span className="text-success fw-bold ms-2">
                            Rp {profile.balance?.toLocaleString()}
                          </span>
                        </p>
                      </>
                    )}
                  </div>
                  <button className="btn btn-primary" onClick={() => setEditing(true)}>
                    Edit Profil
                  </button>
                </>
              ) : (
                <form onSubmit={handleSubmit}>
                  <div className="mb-3">
                    <label className="form-label">Nama</label>
                    <input
                      type="text"
                      className="form-control"
                      name="name"
                      value={formData.name}
                      onChange={handleChange}
                      required
                    />
                  </div>
                  <div className="mb-3">
                    <label className="form-label">Email</label>
                    <input
                      type="email"
                      className="form-control"
                      name="email"
                      value={formData.email}
                      onChange={handleChange}
                      required
                    />
                  </div>
                  <div className="d-flex gap-2">
                    <button type="submit" className="btn btn-success" disabled={submitting}>
                      {submitting ? 'Menyimpan...' : 'Simpan'}
                    </button>
                    <button type="button" className="btn btn-secondary" onClick={() => setEditing(false)}>
                      Batal
                    </button>
                  </div>
                </form>
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

export default Profile;

Langkah 3: Menambahkan Route dengan Proteksi

Buka src/App.js dan tambahkan route ke halaman profil:

import Profile from './pages/Profile';

// ... di dalam Routes
<Route path="/profile" element={
  <PrivateRoute>
    <Profile />
  </PrivateRoute>
} />

Langkah 4: Menambahkan Link Profil di Navbar

Update src/components/Navbar.js dengan menambahkan item "Profil Saya" di dropdown:

<li>
  <Link className="dropdown-item" to="/profile">
    Profil Saya
  </Link>
</li>

Sehingga dropdown menjadi:

<ul className="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
  <li>
    <Link className="dropdown-item" to="/profile">👤 Profil Saya</Link>
  </li>
  <li>
    <Link className="dropdown-item" to="/change-password">🔐 Ubah Password</Link>
  </li>
  <li><hr className="dropdown-divider" /></li>
  <li>
    <button className="dropdown-item text-danger" onClick={handleLogout}>🚪 Logout</button>
  </li>
</ul>
😆 "Dengan fitur profil, pengguna bisa update nama mereka kalau mau ganti nama panggilan, misal dari 'Budi' jadi 'Budi Ganteng'!" 😎

Langkah 5: Uji Coba Fitur Profil

  1. Login sebagai nasabah, petugas, atau admin.
  2. Klik dropdown nama user, pilih "Profil Saya".
  3. Lihat data profil: untuk nasabah muncul nomor rekening dan saldo, untuk admin/petugas hanya nama, email, role.
  4. Klik tombol "Edit Profil", ubah nama atau email, simpan.
  5. Pastikan data berubah dan muncul notifikasi sukses.
  6. Coba ubah email dengan email yang sudah dipakai user lain → muncul error validasi dari server.

Validasi dan Keamanan

  • Di frontend, kita hanya melakukan validasi required (bisa ditambah validasi email format jika perlu).
  • Di backend, kita validasi email unik kecuali milik sendiri.
  • Endpoint hanya bisa diakses dengan token yang valid (auth:sanctum).
💡 Catatan: Jika ingin menambahkan fitur upload foto profil, kita perlu endpoint khusus dan menangani file upload. Ini bisa menjadi tantangan tersendiri untuk tutorial bonus.

Kesimpulan

  • ✅ Endpoint profil (GET dan PUT) berhasil dibuat di Laravel.
  • ✅ Halaman profil di React menampilkan data sesuai role.
  • ✅ Pengguna bisa mengedit nama dan email dengan mudah.
  • ✅ Validasi memastikan data yang dikirim valid.

Di tutorial selanjutnya (#19: Laporan Transaksi (Admin) – Filter dan Export ke Excel/PDF) kita akan membuat fitur laporan untuk admin, lengkap dengan filter tanggal dan ekspor ke file Excel/PDF. Sampai jumpa!


Daftar Tutorial Lanjutan

  • Tutorial #19: Laporan Transaksi (Admin) – Filter dan Export ke Excel/PDF
  • Tutorial #20: Finalisasi dan Penyempurnaan Aplikasi
Lebih baru Lebih lama

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