update: deploy

main
marszayn 2025-09-11 15:48:21 +07:00
parent 6ab4d52e7f
commit 8d0f9277cd
15 changed files with 1231 additions and 302 deletions

View File

@ -3,6 +3,10 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class ProfileController extends Controller
{
@ -10,4 +14,194 @@ class ProfileController extends Controller
{
return view('components/users/viewProfile');
}
/**
* Show change password form
*/
public function changePasswordForm()
{
return view('profile.change-password');
}
/**
* Update user password
*/
public function updatePassword(Request $request)
{
$request->validate([
'current_password' => ['required', 'string'],
'password' => [
'required',
'string',
'min:8',
'confirmed',
'regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/',
],
], [
'current_password.required' => 'Password saat ini wajib diisi.',
'password.min' => 'Password baru minimal 8 karakter.',
'password.confirmed' => 'Konfirmasi password tidak cocok.',
'password.regex' => 'Password harus mengandung huruf besar, huruf kecil, angka, dan simbol khusus.',
]);
$user = Auth::user();
// Verify current password
if (!Hash::check($request->current_password, $user->password)) {
throw ValidationException::withMessages([
'current_password' => ['Password saat ini tidak benar.'],
]);
}
// Update password
$user->update([
'password' => Hash::make($request->password),
]);
// Revoke all API tokens for security (if using Sanctum)
if (method_exists($user, 'tokens')) {
$user->tokens()->delete();
}
// Logout from current session for security
Auth::logout();
// Invalidate the session
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('login.index')
->with('success', 'Password berhasil diubah. Silakan login kembali dengan password baru.');
}
/**
* Show change email form
*/
public function changeEmailForm()
{
return view('profile.change-email');
}
/**
* Update user email
*/
public function updateEmail(Request $request)
{
$user = Auth::user();
$request->validate([
'current_password' => ['required', 'string'],
'email' => [
'required',
'string',
'email',
'max:255',
Rule::unique('users')->ignore($user->id),
],
], [
'current_password.required' => 'Password saat ini wajib diisi untuk konfirmasi.',
'email.required' => 'Email baru wajib diisi.',
'email.email' => 'Format email tidak valid.',
'email.unique' => 'Email sudah digunakan oleh pengguna lain.',
]);
// Verify current password
if (!Hash::check($request->current_password, $user->password)) {
throw ValidationException::withMessages([
'current_password' => ['Password saat ini tidak benar.'],
]);
}
// Update email
$user->update([
'email' => $request->email,
]);
return redirect()->route('profile.change-email')
->with('success', 'Email berhasil diubah.');
}
/**
* Show edit profile form
*/
public function edit()
{
$user = Auth::user();
return view('profile.edit', compact('user'));
}
/**
* Update user profile
*/
public function update(Request $request)
{
$user = Auth::user();
$request->validate([
'name' => ['required', 'string', 'max:255'],
'username' => [
'required',
'string',
'max:255',
Rule::unique('users')->ignore($user->id),
],
], [
'name.required' => 'Nama wajib diisi.',
'username.required' => 'Username wajib diisi.',
'username.unique' => 'Username sudah digunakan oleh pengguna lain.',
]);
$user->update([
'name' => $request->name,
'username' => $request->username,
]);
return redirect()->route('profile.edit')
->with('success', 'Profil berhasil diperbarui.');
}
/**
* Update user profile photo
*/
public function updatePhoto(Request $request)
{
$request->validate([
'profile_photo' => ['required', 'image', 'mimes:jpeg,png,jpg,gif', 'max:2048'],
], [
'profile_photo.required' => 'Foto profil wajib dipilih.',
'profile_photo.image' => 'File harus berupa gambar.',
'profile_photo.mimes' => 'Format foto harus jpeg, png, jpg, atau gif.',
'profile_photo.max' => 'Ukuran foto maksimal 2MB.',
]);
$user = Auth::user();
// Delete old photo if exists
if ($user->profile_photo && file_exists(public_path('storage/profile_photos/' . $user->profile_photo))) {
unlink(public_path('storage/profile_photos/' . $user->profile_photo));
}
// Store new photo
$file = $request->file('profile_photo');
$filename = time() . '_' . $user->id . '.' . $file->getClientOriginalExtension();
// Create directory if not exists
$uploadPath = public_path('storage/profile_photos');
if (!file_exists($uploadPath)) {
mkdir($uploadPath, 0755, true);
}
$file->move($uploadPath, $filename);
// Update user profile photo
$user->update([
'profile_photo' => $filename,
]);
return response()->json([
'success' => true,
'message' => 'Foto profil berhasil diperbarui.',
'photo_url' => asset('storage/profile_photos/' . $filename)
]);
}
}

View File

@ -3,6 +3,7 @@
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Foundation\Auth\User as Authenticatable;
@ -12,7 +13,21 @@ use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasApiTokens, HasFactory, Notifiable, HasRoles;
use HasApiTokens, HasFactory, HasUuids, Notifiable, HasRoles;
/**
* The primary key type.
*
* @var string
*/
protected $keyType = 'string';
/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $incrementing = false;
/**
* The attributes that are mass assignable.
@ -24,6 +39,7 @@ class User extends Authenticatable
'email',
'username',
'password',
'profile_photo',
];
/**

View File

@ -2,6 +2,7 @@
namespace App\Providers;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@ -19,6 +20,9 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot(): void
{
//
if ($this->app->environment('production')) {
URL::forceScheme('https');
       }
}
}

View File

@ -12,11 +12,13 @@ return new class extends Migration
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->uuid('id')->primary();
$table->string('name');
$table->string('email')->unique();
$table->string('username')->nullable()->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->string('profile_photo')->nullable();
$table->rememberToken();
$table->timestamps();
});
@ -29,7 +31,7 @@ return new class extends Migration
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->uuid('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');

View File

@ -13,7 +13,9 @@ return new class extends Migration
{
Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->string('tokenable_type');
$table->string('tokenable_id');
$table->index(['tokenable_type', 'tokenable_id']);
$table->text('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();

View File

@ -1,24 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('username')->nullable()->unique()->after('email');
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropUnique(['username']);
$table->dropColumn('username');
});
}
};

View File

@ -51,7 +51,7 @@ return new class extends Migration
$table->unsignedBigInteger($pivotPermission);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->string($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
$table->foreign($pivotPermission)
@ -62,20 +62,23 @@ return new class extends Migration
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
$table->primary(
[$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary'
);
} else {
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
$table->primary(
[$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary'
);
}
});
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
$table->unsignedBigInteger($pivotRole);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->string($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
$table->foreign($pivotRole)
@ -86,11 +89,15 @@ return new class extends Migration
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
$table->primary(
[$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary'
);
} else {
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
$table->primary(
[$pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary'
);
}
});

View File

@ -6,112 +6,177 @@
@endphp
@section('content')
<div class="card basic-data-table">
<div class="card-body">
<div
class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-3 gap-3">
<!-- Header Section -->
<div class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-4 gap-3">
<div>
<h5 class="mb-0">Edit Pengguna: {{ $user->name }}</h5>
<h5 class="mb-0">Edit Pengguna</h5>
</div>
<div class="d-flex gap-2">
<a href="{{ route('admin.users.index') }}"
class="btn btn-secondary btn-sm d-flex align-items-center gap-2">
<div>
<a href="{{ route('admin.users.index') }}" class="btn btn-secondary btn-sm d-flex align-items-center gap-2">
<iconify-icon icon="iconoir:arrow-left"></iconify-icon>
<span>Kembali</span>
</a>
</div>
</div>
<!-- Error Messages -->
@if ($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<div class="d-flex align-items-center mb-2">
<iconify-icon icon="material-symbols:error" class="me-2"></iconify-icon>
<strong>Terjadi kesalahan:</strong>
</div>
<ul class="mb-0 ps-3">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
<form method="POST" action="{{ route('admin.users.update', $user) }}">
<!-- Form -->
<form method="POST" action="{{ route('admin.users.update', $user) }}" class="needs-validation" novalidate>
@csrf
@method('PUT')
<!-- Personal Information Section -->
<div class="mb-4">
<h6 class="mb-3 text-primary d-flex align-items-center">
<iconify-icon icon="mdi:account-circle" class="me-2"></iconify-icon>
Informasi Personal
</h6>
<div class="row g-3">
<div class="col-12 col-md-6">
<label class="form-label">Nama Lengkap</label>
<input type="text" name="name" class="form-control" required
value="{{ old('name', $user->name) }}" placeholder="Masukkan nama lengkap">
<label class="form-label fw-medium">
Nama Lengkap <span class="text-danger">*</span>
</label>
<input type="text"
name="name"
class="form-control @error('name') is-invalid @enderror"
required
value="{{ old('name', $user->name) }}"
placeholder="Masukkan nama lengkap">
@error('name')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-12 col-md-6">
<label class="form-label">Email</label>
<input type="email" name="email" class="form-control" required
value="{{ old('email', $user->email) }}" placeholder="Masukkan email">
<label class="form-label fw-medium">
Email <span class="text-danger">*</span>
</label>
<input type="email"
name="email"
class="form-control @error('email') is-invalid @enderror"
required
value="{{ old('email', $user->email) }}"
placeholder="Masukkan email">
@error('email')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-12 col-md-6">
<label class="form-label">Username</label>
<input type="text" name="username" class="form-control" required
value="{{ old('username', $user->username) }}" placeholder="Masukkan username">
<label class="form-label fw-medium">
Username <span class="text-danger">*</span>
</label>
<input type="text"
name="username"
class="form-control @error('username') is-invalid @enderror"
required
value="{{ old('username', $user->username) }}"
placeholder="Masukkan username">
@error('username')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-12 col-md-6">
<label class="form-label">Password Baru</label>
<input type="password" name="password" class="form-control"
placeholder="Kosongkan jika tidak ingin mengubah">
<div class="form-text">Kosongkan jika tidak ingin mengubah password. Password harus minimal 8
karakter, mengandung huruf besar, huruf kecil, angka, dan simbol khusus.</div>
</div>
<div class="col-12 col-md-6">
<label class="form-label">Konfirmasi Password Baru</label>
<input type="password" name="password_confirmation" class="form-control"
placeholder="Konfirmasi password baru">
</div>
</div>
<div class="mt-4">
<label class="form-label">Roles</label>
<!-- Divider -->
<hr class="my-4">
<!-- Password Section -->
<div class="mb-4">
<h6 class="mb-3 text-primary d-flex align-items-center">
<iconify-icon icon="mdi:lock" class="me-2"></iconify-icon>
Keamanan
</h6>
<div class="row g-3">
<div class="col-12 col-md-6">
<label class="form-label fw-medium">Password Baru</label>
<input type="password"
name="password"
class="form-control @error('password') is-invalid @enderror"
placeholder="Kosongkan jika tidak ingin mengubah">
<div class="form-text d-flex align-items-center">
<iconify-icon icon="mdi:information" class="me-1"></iconify-icon>
Kosongkan jika tidak ingin mengubah password. Password minimal 8 karakter dengan huruf besar, kecil, angka, dan simbol.
</div>
@error('password')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-12 col-md-6">
<label class="form-label fw-medium">Konfirmasi Password Baru</label>
<input type="password"
name="password_confirmation"
class="form-control"
placeholder="Konfirmasi password baru">
</div>
</div>
</div>
<!-- Roles Section -->
<div class="mb-4">
<h6 class="mb-3 text-primary d-flex align-items-center">
<iconify-icon icon="mdi:shield-account" class="me-2"></iconify-icon>
Roles & Permissions
</h6>
<div class="p-3 bg-light rounded-3">
@if ($roles->count() > 0)
<div class="row g-2">
@foreach ($roles as $role)
<div class="col-12 col-md-4">
<div class="form-check style-check d-flex align-items-center">
<input class="form-check-input border border-neutral-300 me-8" type="checkbox"
name="roles[]" value="{{ $role->name }}" id="role_{{ $role->id }}"
<div class="col-12 col-sm-6 col-md-4">
<div class="form-check style-check d-flex align-items-center gap-2">
<input class="form-check-input border border-neutral-300"
type="checkbox"
name="roles[]"
value="{{ $role->name }}"
id="role_{{ $role->id }}"
{{ in_array($role->name, old('roles', $userRoles)) ? 'checked' : '' }}>
<label class="form-check-label"
for="role_{{ $role->id }}">{{ $role->name }}</label>
<label class="form-check-label fw-medium" for="role_{{ $role->id }}">
{{ ucfirst($role->name) }}
</label>
</div>
</div>
@endforeach
</div>
</div>
<!-- User Info -->
<div class="mt-4">
<div class="alert alert-info">
<h6>Informasi Pengguna:</h6>
<ul class="mb-0">
<li>Dibuat: {{ $user->created_at->format('d/m/Y H:i:s') }}</li>
<li>Terakhir diperbarui: {{ $user->updated_at->format('d/m/Y H:i:s') }}</li>
@if ($user->email_verified_at)
<li>Email diverifikasi: {{ $user->email_verified_at->format('d/m/Y H:i:s') }}</li>
@else
<li class="text-warning">Email belum diverifikasi</li>
<div class="text-center text-muted py-3">
<iconify-icon icon="mdi:shield-off" class="fs-2 mb-2"></iconify-icon>
<p class="mb-0">Tidak ada roles yang tersedia</p>
</div>
@endif
</ul>
</div>
</div>
<div class="mt-4 d-flex gap-2">
<button class="btn btn-primary d-flex align-items-center gap-2">
<!-- Action Buttons -->
<div class="d-flex align-items-center gap-2 pt-3 border-top">
<button type="submit" class="btn btn-primary d-flex align-items-center gap-2">
<iconify-icon icon="material-symbols:save"></iconify-icon>
<span>Perbarui</span>
<span>Perbarui Pengguna</span>
</button>
<a href="{{ route('admin.users.show', $user) }}" class="btn btn-info d-flex align-items-center gap-2">
<iconify-icon icon="lucide:eye"></iconify-icon>
<span>Lihat Detail</span>
<a href="{{ route('admin.users.index') }}" class="btn btn-light d-flex align-items-center gap-2">
<iconify-icon icon="material-symbols:cancel"></iconify-icon>
<span>Batal</span>
</a>
<a href="{{ route('admin.users.index') }}" class="btn btn-light">Batal</a>
</div>
</form>
</div>
</div>
@endsection

View File

@ -32,7 +32,7 @@
<div
class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-3 gap-3">
<div>
<h5 class="mb-0">Detail Pengguna: {{ $user->name }}</h5>
<h5 class="mb-0">Detail Pengguna:</h5>
</div>
<div class="d-flex gap-2">
<a href="{{ route('admin.users.index') }}"
@ -51,10 +51,6 @@
</div>
<div class="card-body">
<table class="table table-borderless mb-0">
<tr>
<td width="150"><strong>ID:</strong></td>
<td>{{ $user->id }}</td>
</tr>
<tr>
<td><strong>Nama:</strong></td>
<td>{{ $user->name }}</td>
@ -63,11 +59,6 @@
<td><strong>Email:</strong></td>
<td>
{{ $user->email }}
@if ($user->email_verified_at)
<span class="badge bg-success ms-2">Terverifikasi</span>
@else
<span class="badge bg-warning ms-2">Belum Terverifikasi</span>
@endif
</td>
</tr>
<tr>
@ -133,8 +124,8 @@
<h6 class="mb-0">Ringkasan Aktivitas</h6>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-3">
<div class="row g-4">
<div class="col-md-4">
<div class="card bg-light text-center">
<div class="card-body">
<h6 class="card-title">Total Role</h6>
@ -142,7 +133,7 @@
</div>
</div>
</div>
<div class="col-md-3">
<div class="col-md-4">
<div class="card bg-light text-center">
<div class="card-body">
<h6 class="card-title">Total Permission</h6>
@ -150,23 +141,11 @@
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-light text-center">
<div class="card-body">
<h6 class="card-title">Status Email</h6>
@if ($user->email_verified_at)
<h3 class="text-success mb-0"></h3>
@else
<h3 class="text-warning mb-0"></h3>
@endif
</div>
</div>
</div>
<div class="col-md-3">
<div class="col-md-4">
<div class="card bg-light text-center">
<div class="card-body">
<h6 class="card-title">Akun Aktif</h6>
<h3 class="text-success mb-0">{{ $user->created_at->diffInDays(now()) }} hari
<h3 class="text-success mb-0">{{ max(1, $user->created_at->diffInDays(now())) }} hari
</h3>
</div>
</div>

View File

@ -13,7 +13,7 @@
</div>
<div class="col-auto">
<div class="d-flex flex-wrap align-items-center gap-3">
{{--
{{--
<div class="dropdown">
<button class="has-indicator w-40-px h-40-px bg-neutral-200 rounded-circle d-flex justify-content-center align-items-center" type="button" data-bs-toggle="dropdown">
<iconify-icon icon="mage:email" class="text-primary-light text-xl"></iconify-icon>
@ -207,24 +207,33 @@
</div><!-- Notification dropdown end --> --}}
<div class="dropdown">
<button class="d-flex justify-content-center align-items-center rounded-circle" type="button" data-bs-toggle="dropdown">
<iconify-icon icon="solar:user-linear" class="text-primary-light text-xl w-40-px h-40-px d-flex justify-content-center align-items-center bg-neutral-200 rounded-circle"></iconify-icon>
<button class="d-flex justify-content-center align-items-center rounded-circle" type="button"
data-bs-toggle="dropdown">
<iconify-icon icon="solar:user-linear"
class="text-primary-light text-xl w-40-px h-40-px d-flex justify-content-center align-items-center bg-neutral-200 rounded-circle"></iconify-icon>
</button>
<div class="dropdown-menu to-top dropdown-menu-sm">
<div class="py-12 px-16 radius-8 bg-primary-50 mb-16 d-flex align-items-center justify-content-between gap-2">
<div
class="py-12 px-16 radius-8 bg-primary-50 mb-16 d-flex align-items-center justify-content-between gap-2">
<div>
<h6 class="text-lg text-primary-light fw-semibold mb-2">{{ auth()->user()->name ?? 'User' }}</h6>
<span class="text-secondary-light fw-medium text-sm">{{ optional(auth()->user())->getRoleNames()->implode(', ') ?? '-' }}</span>
<h6 class="text-lg text-primary-light fw-semibold mb-2">
{{ auth()->user()->name ?? 'User' }}</h6>
<span
class="text-secondary-light fw-medium text-sm">{{ optional(auth()->user())->getRoleNames()->implode(', ') ?? '-' }}</span>
</div>
</div>
<ul class="to-top-list">
<li>
<a class="dropdown-item text-black px-0 py-8 hover-bg-transparent hover-text-primary d-flex align-items-center gap-3" href="{{ route('profile.index') }}">
<iconify-icon icon="solar:user-linear" class="icon text-xl"></iconify-icon> My Profile
<a class="dropdown-item text-black px-0 py-8 hover-bg-transparent hover-text-primary d-flex align-items-center gap-3"
href="{{ route('profile.index') }}">
<iconify-icon icon="solar:user-linear" class="icon text-xl"></iconify-icon> Profil
</a>
</li>
<li>
<a id="btn-logout" class="dropdown-item text-black px-0 py-8 hover-bg-transparent hover-text-danger d-flex align-items-center gap-3" href="#">
<a id="btn-logout"
class="dropdown-item text-black px-0 py-8 hover-bg-transparent hover-text-danger d-flex align-items-center gap-3"
href="#">
<iconify-icon icon="lucide:power" class="icon text-xl"></iconify-icon> Log Out
</a>
</li>
@ -239,22 +248,27 @@
<form id="logout-form" action="{{ route('logout.session') }}" method="POST" class="d-none">
@csrf
<input type="hidden" name="_token" value="{{ csrf_token() }}">
</form>
</form>
<script>
(function(){
(function() {
const btn = document.getElementById('btn-logout');
if(!btn) return;
btn.addEventListener('click', async function(e){
if (!btn) return;
btn.addEventListener('click', async function(e) {
e.preventDefault();
const token = localStorage.getItem('auth_token');
try {
if (token) {
await fetch('{{ url('/api/auth/logout') }}', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + token, 'Accept': 'application/json' }
}).catch(()=>{});
headers: {
'Authorization': 'Bearer ' + token,
'Accept': 'application/json'
}
}).catch(() => {});
}
} catch (err) {
/* ignore */
}
} catch (err) { /* ignore */ }
localStorage.removeItem('auth_token');
localStorage.removeItem('auth_user');

View File

@ -1,9 +1,12 @@
@extends('layout.layout')
@php
$title='View Profile';
$subTitle = 'View Profile';
$script ='<script>
// ======================== Upload Image Start =====================
$title = 'Profil Pengguna';
$subTitle = 'Kelola Profil Anda';
@endphp
@push('scripts')
<script>
// Upload Image functionality
function readURL(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
@ -15,122 +18,259 @@
reader.readAsDataURL(input.files[0]);
}
}
$("#imageUpload").change(function() {
readURL(this);
});
// ======================== Upload Image End =====================
// ================== Password Show Hide Js Start ==========
function initializePasswordToggle(toggleSelector) {
$(toggleSelector).on("click", function() {
$(this).toggleClass("ri-eye-off-line");
var input = $($(this).attr("data-toggle"));
if (input.attr("type") === "password") {
input.attr("type", "text");
} else {
input.attr("type", "password");
function uploadProfilePhoto(file) {
// Double check if already uploading
if (isUploading) {
return;
}
const formData = new FormData();
formData.append('profile_photo', file);
formData.append('_token', '{{ csrf_token() }}');
// Show loading
const uploadLabel = $('label[for="imageUpload"]');
const originalContent = uploadLabel.html();
uploadLabel.html('<div class="spinner-border spinner-border-sm" role="status"></div>');
$.ajax({
url: '{{ route('profile.update-photo') }}',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
if (response.success) {
// Update image preview
$("#imagePreview").css("background-image", "url(" + response.photo_url + ")");
// Show success message (only one)
showNotification('success', response.message);
// Clear file input to prevent duplicate uploads
$("#imageUpload").val('');
}
},
error: function(xhr) {
let errorMessage = 'Terjadi kesalahan saat mengupload foto.';
if (xhr.responseJSON && xhr.responseJSON.errors) {
const errors = xhr.responseJSON.errors;
if (errors.profile_photo) {
errorMessage = errors.profile_photo[0];
}
}
showNotification('error', errorMessage);
// Clear file input on error
$("#imageUpload").val('');
},
complete: function() {
// Restore button
uploadLabel.html(originalContent);
// Reset uploading flag
isUploading = false;
}
});
}
// Call the function
initializePasswordToggle(".toggle-password");
// ========================= Password Show Hide Js End ===========================
</script>';
@endphp
function showNotification(type, message) {
// Remove ALL existing alerts to prevent duplicates
$('.alert').remove();
const alertClass = type === 'success' ? 'alert-success' : 'alert-danger';
const iconClass = type === 'success' ? 'material-symbols:check-circle' : 'material-symbols:error';
const notification = `
<div class="alert ${alertClass} alert-dismissible fade show" role="alert">
<iconify-icon icon="${iconClass}" class="me-2"></iconify-icon>
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
`;
// Add notification to the top of the profile settings card
$('.col-lg-8 .card-body').prepend(notification);
// Auto dismiss after 5 seconds
setTimeout(function() {
$('.alert').fadeOut(function() {
$(this).remove();
});
}, 5000);
}
let isUploading = false;
$(document).ready(function() {
$("#imageUpload").change(function() {
const file = this.files[0];
// Prevent multiple uploads
if (isUploading) {
showNotification('error', 'Upload sedang berlangsung, harap tunggu...');
$(this).val(''); // Clear input
return;
}
if (file) {
// Validate file size (2MB)
if (file.size > 2 * 1024 * 1024) {
showNotification('error', 'Ukuran file maksimal 2MB.');
$(this).val(''); // Clear input
return;
}
// Validate file type
const allowedTypes = ['image/jpeg', 'image/png', 'image/jpg', 'image/gif'];
if (!allowedTypes.includes(file.type)) {
showNotification('error', 'Format file harus jpeg, png, jpg, atau gif.');
$(this).val(''); // Clear input
return;
}
// Clear any existing alerts before starting upload
$('.alert').remove();
// Set uploading flag
isUploading = true;
// Preview image
readURL(this);
// Upload image
uploadProfilePhoto(file);
}
});
});
</script>
@endpush
@section('content')
<!-- Alert box at the top -->
<div class="alert alert-warning radius-8 mb-24">
<div class="d-flex align-items-start">
<i class="ri-information-line me-2 mt-1"></i>
<div>
<ul class="mb-0">
<li class="mb-8">Sinkronisasi akun hanya dapat dilakukan jika NIB anda sudah terdaftar di OSS-RBA, jika NIB yang anda daftarkan di Akun Arndalnet terdapat kesalahan, silahkan hubungi helpdesk Arndalnet (+62 851-8666-0568).</li>
<li class="mb-8">Jika Anda ingin mengubah email untuk kebutuhan mengganti email akun anda yang sekarang menjadi email baru, silahkan anda mengubahnya dibawah lalu klik ubah.</li>
<li>Jika Anda adalah Pelaku Usaha mandiri dengan kondisi belum melakukan integrasi akun dengan OSS-RBA. Silahkan anda klik tombol "Sinkronisasi ke Akun Pelaku Usaha OSS-RBA" untuk menyamakan email saat ini dengan email yang terdaftar di OSS-RBA (pastikan NIB sudah sama dengan yang terdaftar di OSS-RBA).</li>
</ul>
</div>
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</div>
<div class="row gy-4">
<div class="col-lg-4">
<div class="user-grid-card position-relative border radius-16 overflow-hidden bg-base h-100">
<img src="{{ asset('assets/images/user-grid/user-grid-bg1.png') }}" alt="" class="w-100 object-fit-cover">
<div class="pb-24 ms-16 mb-24 me-16 mt--100">
<div class="text-center border border-top-0 border-start-0 border-end-0">
<img src="{{ asset('assets/images/user-grid/user-grid-img14.png') }}" alt="" class="border br-white border-width-2-px w-200-px h-200-px rounded-circle object-fit-cover">
<h6 class="mb-0 mt-16">Helmy Zulhidayat</h6>
<span class="text-secondary-light mb-16">amdal.jakarta@gmail.com</span>
</div>
<div class="mt-24">
<h6 class="text-xl mb-16">Personal Info</h6>
<ul>
<li class="d-flex align-items-center gap-1 mb-12">
<span class="w-30 text-md fw-semibold text-primary-light">Full Name</span>
<span class="w-70 text-secondary-light fw-medium">: Helmy Zulhidayat</span>
</li>
<li class="d-flex align-items-center gap-1 mb-12">
<span class="w-30 text-md fw-semibold text-primary-light"> Email</span>
<span class="w-70 text-secondary-light fw-medium">: amdal.jakarta@gmail.com</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="col-lg-8">
<div class="card h-100">
<div class="card-body p-24">
<h5 class="text-primary-light border-bottom pb-2 mb-4">Akun</h5>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-edit-profile" role="tabpanel" aria-labelledby="pills-edit-profile-tab" tabindex="0">
<h6 class="text-md text-primary-light mb-16">Profile Image</h6>
<!-- Upload Image Start -->
<div class="mb-24 mt-16">
<div class="avatar-upload">
<div class="avatar-edit position-absolute bottom-0 end-0 me-24 mt-16 z-1 cursor-pointer">
<input type='file' id="imageUpload" accept=".png, .jpg, .jpeg" hidden>
<label for="imageUpload" class="w-32-px h-32-px d-flex justify-content-center align-items-center bg-primary-50 text-primary-600 border border-primary-600 bg-hover-primary-100 text-lg rounded-circle">
<div class="card-body text-center">
<!-- Profile Image -->
<div class="mb-3">
<div class="avatar-upload d-inline-block position-relative">
<div class="avatar-edit position-absolute bottom-0 end-0 z-1 cursor-pointer">
<input type='file' id="imageUpload" accept=".png,.jpg,.jpeg,.gif" hidden>
<label for="imageUpload"
class="w-32-px h-32-px d-flex justify-content-center align-items-center bg-primary-50 text-primary-600 border border-primary-600 bg-hover-primary-100 text-lg rounded-circle">
<iconify-icon icon="solar:camera-outline" class="icon"></iconify-icon>
</label>
</div>
<div class="avatar-preview">
<div id="imagePreview">
@php
$defaultPhoto = asset('assets/images/user-grid/user-grid-img14.png');
$userPhoto = auth()->user()->profile_photo
? asset('storage/profile_photos/' . auth()->user()->profile_photo)
: $defaultPhoto;
@endphp
<div id="imagePreview"
style="border-radius: 50%; background-image: url('{{ $userPhoto }}'); background-size: cover; background-position: center; border: 3px solid #fff; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
</div>
</div>
</div>
</div>
<!-- Upload Image End -->
<form action="#">
<div class="row">
<div class="col-sm-6">
<div class="mb-20">
<label for="name" class="form-label fw-semibold text-primary-light text-sm mb-8">Full Name <span class="text-danger-600">*</span></label>
<input type="text" class="form-control radius-8" id="name" value="Helmy Zulhidayat" readonly>
<!-- User Info -->
<h5 class="mb-1">{{ auth()->user()->name ?? 'User' }}</h5>
<p class="text-muted mb-3">{{ auth()->user()->email ?? 'email@example.com' }}</p>
<!-- User Role -->
@if (auth()->user()->roles->count() > 0)
<div class="mb-3">
@foreach (auth()->user()->roles as $role)
<span class="badge bg-primary me-1">{{ $role->name }}</span>
@endforeach
</div>
</div>
<div class="col-sm-6">
<div class="mb-20">
<label for="email" class="form-label fw-semibold text-primary-light text-sm mb-8">Email <span class="text-danger-600">*</span></label>
<input type="email" class="form-control radius-8" id="email" value="amdal.jakarta@gmail.com" readonly>
@endif
<!-- Account Info -->
<div class="text-start">
<h6 class="text-primary mb-3">Informasi Akun</h6>
<div class="small text-muted">
<div class="d-flex justify-content-between mb-2">
<span>Bergabung:</span>
<span>{{ auth()->user()->created_at->format('d/m/Y') }}</span>
</div>
</div>
</div>
<div class="d-flex align-items-center gap-3">
<button type="button" class="btn btn-success px-4 py-2 radius-8">
</div>
</div>
</div>
<div class="col-lg-8">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title mb-4">Pengaturan Profil</h5>
<!-- Profile Actions -->
<div class="row g-3">
<div class="col-md-4">
<div class="card border-0 bg-light h-100">
<div
class="card-body d-flex flex-column justify-content-center align-items-center text-center">
<iconify-icon icon="solar:pen-linear" class="text-success fs-1 mb-3"></iconify-icon>
<h6>Edit Profil</h6>
<p class="text-muted small">Ubah nama dan username</p>
<a href="{{ route('profile.edit') }}"
class="d-flex justify-content-center align-items-center btn btn-success btn-sm">
<iconify-icon icon="solar:pen-linear" class="me-1"></iconify-icon>
Edit
</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card border-0 bg-light h-100">
<div
class="card-body d-flex flex-column justify-content-center align-items-center text-center">
<iconify-icon icon="solar:lock-password-linear"
class="text-warning fs-1 mb-3"></iconify-icon>
<h6>Ubah Password</h6>
<p class="text-muted small">Ganti password akun</p>
<a href="{{ route('profile.change-password') }}"
class="d-flex justify-content-center align-items-center btn btn-warning btn-sm">
<iconify-icon icon="solar:lock-password-linear" class="me-1"></iconify-icon>
Ubah
</button>
<button type="button" class="btn btn-warning px-4 py-2 radius-8">
Ubah Password
</button>
<button type="button" class="btn btn-warning px-4 py-2 radius-8">
Ubah Email
</button>
</a>
</div>
</form>
</div>
</div>
<div class="col-md-4">
<div class="card border-0 bg-light h-100">
<div
class="card-body d-flex flex-column justify-content-center align-items-center text-center">
<iconify-icon icon="solar:letter-linear" class="text-info fs-1 mb-3"></iconify-icon>
<h6>Ubah Email</h6>
<p class="text-muted small">Ganti alamat email</p>
<a href="{{ route('profile.change-email') }}"
class="d-flex justify-content-center align-items-center btn btn-info btn-sm">
<iconify-icon icon="solar:letter-linear" class="me-1"></iconify-icon>
Ubah
</a>
</div>
</div>
</div>
</div>
<!-- Quick Info -->
<div class="mt-4">
<div class="alert alert-info">
<iconify-icon icon="material-symbols:info" class="me-2"></iconify-icon>
<strong>Tips Keamanan:</strong>
<ul class="mb-0 mt-2">
<li>Gunakan password yang kuat dengan kombinasi huruf, angka, dan simbol</li>
<li>Perbarui informasi profil secara berkala</li>
<li>Jaga kerahasiaan informasi login Anda</li>
</ul>
</div>
</div>
</div>

View File

@ -0,0 +1,203 @@
@extends('layout.layout')
@php
$title = 'Ubah Email';
$subTitle = 'Profil Pengguna';
@endphp
@section('content')
<div class="card basic-data-table">
<div class="card-body">
<div
class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-3 gap-3">
<div>
<h5 class="mb-0">Ubah Email</h5>
</div>
<div class="d-flex gap-2">
<a href="{{ route('profile.index') }}" class="btn btn-secondary btn-sm d-flex align-items-center gap-2">
<iconify-icon icon="iconoir:arrow-left"></iconify-icon>
<span>Kembali</span>
</a>
</div>
</div>
@if (session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
@if ($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="POST" action="{{ route('profile.update-email') }}" id="emailChangeForm">
@csrf
@method('PUT')
<div class="row g-3">
<div class="col-12 col-md-6">
<label class="form-label">Email Saat Ini</label>
<input type="email" class="form-control" value="{{ Auth::user()->email }}" readonly>
</div>
<div class="col-12"></div>
<div class="col-12 col-md-6">
<label class="form-label">Email Baru <span class="text-danger">*</span></label>
<input type="email" name="email" class="form-control" required value="{{ old('email') }}"
placeholder="Masukkan email baru">
</div>
<div class="col-12 col-md-6">
<label class="form-label">Password Saat Ini <span class="text-danger">*</span></label>
<div class="position-relative">
<input type="password" name="current_password" id="current_password_email"
class="form-control pe-5" required placeholder="Masukkan password untuk konfirmasi">
<button type="button"
class="btn btn-link position-absolute end-0 top-50 translate-middle-y pe-3 text-secondary toggle-password"
data-target="#current_password_email">
<iconify-icon icon="solar:eye-linear" class="eye-open"></iconify-icon>
<iconify-icon icon="solar:eye-closed-linear" class="eye-closed d-none"></iconify-icon>
</button>
</div>
<div class="form-text">Password diperlukan untuk konfirmasi keamanan.</div>
</div>
</div>
<div class="mt-4 d-flex gap-2">
<button class="btn btn-primary d-flex align-items-center gap-2">
<iconify-icon icon="material-symbols:save"></iconify-icon>
<span>Ubah Email</span>
</button>
<a href="{{ route('profile.index') }}" class="btn btn-light">Batal</a>
</div>
</form>
</div>
</div>
<!-- Custom Confirmation Modal -->
<div class="modal fade" id="confirmEmailChangeModal" tabindex="-1" aria-labelledby="confirmEmailChangeModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header border-0 pb-0">
<div class="d-flex align-items-center">
<div class="me-3">
<div
class="w-48-px h-48-px bg-info-subtle rounded-circle d-flex align-items-center justify-content-center">
<iconify-icon icon="material-symbols:mail" class="text-info fs-3"></iconify-icon>
</div>
</div>
<div>
<h5 class="modal-title mb-1" id="confirmEmailChangeModalLabel">Konfirmasi Ubah Email</h5>
<p class="text-muted mb-0 small">Pastikan email baru sudah benar</p>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body pt-2">
<div class="alert alert-info border-0 bg-info-subtle">
<div class="d-flex">
<iconify-icon icon="material-symbols:info" class="text-info me-2 mt-1"></iconify-icon>
<div>
<strong>Informasi:</strong>
<ul class="mb-0 mt-2">
<li>Email akan <strong>langsung diubah</strong> setelah konfirmasi</li>
<li>Pastikan <strong>email baru sudah benar</strong> dan dapat diakses</li>
<li>Gunakan email ini untuk <strong>login selanjutnya</strong></li>
</ul>
</div>
</div>
</div>
<div class="bg-light p-3 rounded">
<div class="row">
<div class="col-12 mb-2">
<small class="text-muted">Email saat ini:</small>
<div class="fw-medium">{{ Auth::user()->email }}</div>
</div>
<div class="col-12">
<small class="text-muted">Email baru:</small>
<div class="fw-medium text-primary" id="newEmailPreview">-</div>
</div>
</div>
</div>
</div>
<div class="modal-footer border-0 pt-0">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">
<iconify-icon icon="material-symbols:close" class="me-1"></iconify-icon>
Batal
</button>
<button type="button" class="btn btn-info" id="confirmEmailChange">
<iconify-icon icon="material-symbols:mail" class="me-1"></iconify-icon>
Ya, Ubah Email
</button>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
$(document).ready(function() {
// Password toggle functionality
$('.toggle-password').on('click', function() {
const target = $(this).data('target');
const input = $(target);
const eyeOpen = $(this).find('.eye-open');
const eyeClosed = $(this).find('.eye-closed');
if (input.attr('type') === 'password') {
input.attr('type', 'text');
eyeOpen.addClass('d-none');
eyeClosed.removeClass('d-none');
} else {
input.attr('type', 'password');
eyeOpen.removeClass('d-none');
eyeClosed.addClass('d-none');
}
});
// Custom modal confirmation before email change
$('#emailChangeForm').on('submit', function(e) {
e.preventDefault();
// Get new email value and show in modal
const newEmail = $('input[name="email"]').val();
if (newEmail) {
$('#newEmailPreview').text(newEmail);
$('#confirmEmailChangeModal').modal('show');
} else {
// If no email entered, show validation
$('input[name="email"]').focus();
}
});
// Handle modal confirmation
$('#confirmEmailChange').on('click', function() {
// Hide modal
$('#confirmEmailChangeModal').modal('hide');
// Add loading state to button
const btn = $(this);
const originalText = btn.html();
btn.html(
'<span class="spinner-border spinner-border-sm me-2" role="status"></span>Mengubah Email...'
);
btn.prop('disabled', true);
// Submit form after modal closes
setTimeout(function() {
$('#emailChangeForm')[0].submit();
}, 300);
});
});
</script>
@endpush

View File

@ -0,0 +1,214 @@
@extends('layout.layout')
@php
$title = 'Ubah Password';
$subTitle = 'Profil Pengguna';
@endphp
@section('content')
<div class="card basic-data-table">
<div class="card-body">
<div
class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-3 gap-3">
<div>
<h5 class="mb-0">Ubah Password</h5>
</div>
<div class="d-flex gap-2">
<a href="{{ route('profile.index') }}" class="btn btn-secondary btn-sm d-flex align-items-center gap-2">
<iconify-icon icon="iconoir:arrow-left"></iconify-icon>
<span>Kembali</span>
</a>
</div>
</div>
@if (session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
@if ($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="POST" action="{{ route('profile.update-password') }}" id="passwordChangeForm">
@csrf
@method('PUT')
<div class="row g-3">
<div class="col-12">
<div class="alert alert-warning d-flex align-items-center">
<iconify-icon icon="material-symbols:warning" class="me-2"></iconify-icon>
<div>
<strong>Peringatan Keamanan:</strong> Setelah mengubah password, Anda akan otomatis logout
dan diarahkan ke halaman login. Pastikan Anda mengingat password baru sebelum melanjutkan.
</div>
</div>
</div>
<div class="col-12 col-md-6">
<label class="form-label">Password Saat Ini <span class="text-danger">*</span></label>
<div class="position-relative">
<input type="password" name="current_password" id="current_password" class="form-control pe-5"
required placeholder="Masukkan password saat ini">
<button type="button"
class="btn btn-link position-absolute end-0 top-50 translate-middle-y pe-3 text-secondary toggle-password"
data-target="#current_password">
<iconify-icon icon="solar:eye-linear" class="eye-open"></iconify-icon>
<iconify-icon icon="solar:eye-closed-linear" class="eye-closed d-none"></iconify-icon>
</button>
</div>
</div>
<div class="col-12"></div>
<div class="col-12 col-md-6">
<label class="form-label">Password Baru <span class="text-danger">*</span></label>
<div class="position-relative">
<input type="password" name="password" id="new_password" class="form-control pe-5" required
placeholder="Masukkan password baru">
<button type="button"
class="btn btn-link position-absolute end-0 top-50 translate-middle-y pe-3 text-secondary toggle-password"
data-target="#new_password">
<iconify-icon icon="solar:eye-linear" class="eye-open"></iconify-icon>
<iconify-icon icon="solar:eye-closed-linear" class="eye-closed d-none"></iconify-icon>
</button>
</div>
<div class="form-text">Password harus minimal 8 karakter, mengandung huruf besar, huruf
kecil,
angka, dan simbol khusus.</div>
</div>
<div class="col-12 col-md-6">
<label class="form-label">Konfirmasi Password Baru <span class="text-danger">*</span></label>
<div class="position-relative">
<input type="password" name="password_confirmation" id="confirm_password"
class="form-control pe-5" required placeholder="Konfirmasi password baru">
<button type="button"
class="btn btn-link position-absolute end-0 top-50 translate-middle-y pe-3 text-secondary toggle-password"
data-target="#confirm_password">
<iconify-icon icon="solar:eye-linear" class="eye-open"></iconify-icon>
<iconify-icon icon="solar:eye-closed-linear" class="eye-closed d-none"></iconify-icon>
</button>
</div>
</div>
</div>
<div class="mt-4 d-flex gap-2">
<button type="submit" class="btn btn-primary d-flex align-items-center gap-2" id="changePasswordBtn">
<iconify-icon icon="material-symbols:save"></iconify-icon>
<span>Ubah Password</span>
</button>
<a href="{{ route('profile.index') }}" class="btn btn-light">Batal</a>
</div>
</form>
</div>
</div>
<!-- Custom Confirmation Modal -->
<div class="modal fade" id="confirmPasswordChangeModal" tabindex="-1" aria-labelledby="confirmPasswordChangeModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header border-0 pb-0">
<div class="d-flex align-items-center">
<div class="me-3">
<div
class="w-48-px h-48-px bg-warning-subtle rounded-circle d-flex align-items-center justify-content-center">
<iconify-icon icon="material-symbols:warning" class="text-warning fs-3"></iconify-icon>
</div>
</div>
<div>
<h5 class="modal-title mb-1" id="confirmPasswordChangeModalLabel">Konfirmasi Ubah Password</h5>
<p class="text-muted mb-0 small">Pastikan Anda yakin dengan keputusan ini</p>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body pt-2">
<div class="alert alert-warning border-0 bg-warning-subtle">
<div class="d-flex">
<iconify-icon icon="material-symbols:info" class="text-warning me-2 mt-1"></iconify-icon>
<div>
<strong>Perhatian:</strong>
<ul class="mb-0 mt-2">
<li>Anda akan <strong>otomatis logout</strong> dari semua perangkat</li>
<li>Perlu <strong>login ulang</strong> dengan password baru</li>
<li>Pastikan Anda <strong>mengingat password baru</strong> sebelum melanjutkan</li>
</ul>
</div>
</div>
</div>
<p class="mb-0">Apakah Anda yakin ingin mengubah password sekarang?</p>
</div>
<div class="modal-footer border-0 pt-0 d-flex align-items-center justify-content-center">
<button type="button" class="btn btn-light d-flex align-items-center" data-bs-dismiss="modal">
<iconify-icon icon="material-symbols:close" class="me-1"></iconify-icon>
Batal
</button>
<button type="button" class="btn btn-warning d-flex align-items-center" id="confirmPasswordChange">
<iconify-icon icon="material-symbols:lock-reset" class="me-1"></iconify-icon>
Ya, Ubah Password
</button>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
$(document).ready(function() {
// Password toggle functionality
$('.toggle-password').on('click', function() {
const target = $(this).data('target');
const input = $(target);
const eyeOpen = $(this).find('.eye-open');
const eyeClosed = $(this).find('.eye-closed');
if (input.attr('type') === 'password') {
input.attr('type', 'text');
eyeOpen.addClass('d-none');
eyeClosed.removeClass('d-none');
} else {
input.attr('type', 'password');
eyeOpen.removeClass('d-none');
eyeClosed.addClass('d-none');
}
});
// Custom modal confirmation before password change
$('#passwordChangeForm').on('submit', function(e) {
e.preventDefault();
// Show custom modal instead of alert
$('#confirmPasswordChangeModal').modal('show');
});
// Handle modal confirmation
$('#confirmPasswordChange').on('click', function() {
// Hide modal
$('#confirmPasswordChangeModal').modal('hide');
// Add loading state to button
const btn = $(this);
const originalText = btn.html();
btn.html(
'<span class="spinner-border spinner-border-sm me-2" role="status"></span>Mengubah Password...'
);
btn.prop('disabled', true);
// Submit form after modal closes
setTimeout(function() {
$('#passwordChangeForm')[0].submit();
}, 300);
});
});
</script>
@endpush

View File

@ -0,0 +1,106 @@
@extends('layout.layout')
@php
$title = 'Edit Profil';
$subTitle = 'Profil Pengguna';
@endphp
@section('content')
<div class="card basic-data-table">
<div class="card-body">
<div
class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-3 gap-3">
<div>
<h5 class="mb-0">Edit Profil</h5>
</div>
<div class="d-flex gap-2">
<a href="{{ route('profile.index') }}" class="btn btn-secondary btn-sm d-flex align-items-center gap-2">
<iconify-icon icon="iconoir:arrow-left"></iconify-icon>
<span>Kembali</span>
</a>
</div>
</div>
@if (session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
@if ($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="POST" action="{{ route('profile.update') }}">
@csrf
@method('PUT')
<div class="row g-3">
<div class="col-12 col-md-6">
<label class="form-label">Nama Lengkap <span class="text-danger">*</span></label>
<input type="text" name="name" class="form-control" required
value="{{ old('name', $user->name) }}" placeholder="Masukkan nama lengkap">
</div>
<div class="col-12 col-md-6">
<label class="form-label">Username <span class="text-danger">*</span></label>
<input type="text" name="username" class="form-control" required
value="{{ old('username', $user->username) }}" placeholder="Masukkan username">
</div>
<div class="col-12 col-md-6">
<label class="form-label">Email</label>
<input type="email" class="form-control" value="{{ $user->email }}" readonly>
<div class="form-text">
<a href="{{ route('profile.change-email') }}" class="text-primary">Klik di sini untuk mengubah
email</a>
</div>
</div>
<div class="col-12 col-md-6">
<label class="form-label">Password</label>
<input type="password" class="form-control" value="••••••••" readonly>
<div class="form-text">
<a href="{{ route('profile.change-password') }}" class="text-primary">Klik di sini untuk
mengubah password</a>
</div>
</div>
</div>
<!-- User Info -->
<div class="mt-4">
<div class="alert alert-light">
<h6>Informasi Akun:</h6>
<ul class="mb-0">
<li>Bergabung: {{ $user->created_at->format('d/m/Y H:i:s') }}</li>
<li>Terakhir diperbarui: {{ $user->updated_at->format('d/m/Y H:i:s') }}</li>
<li>Role:
@if ($user->roles->count() > 0)
@foreach ($user->roles as $role)
<span class="badge bg-primary me-1">{{ $role->name }}</span>
@endforeach
@else
<span class="badge bg-secondary">Tidak ada role</span>
@endif
</li>
</ul>
</div>
</div>
<div class="mt-4 d-flex gap-2">
<button class="btn btn-primary d-flex align-items-center gap-2">
<iconify-icon icon="material-symbols:save"></iconify-icon>
<span>Simpan Perubahan</span>
</button>
<a href="{{ route('profile.index') }}" class="btn btn-light">Batal</a>
</div>
</form>
</div>
</div>
@endsection

View File

@ -153,9 +153,16 @@ Route::prefix('admin')->middleware(['web.auth'])->group(function () {
Route::get('/penugasan', [PenugasanController::class, 'index'])->name('penugasan.index');
});
// Profile
Route::prefix('admin')->middleware(['web.auth'])->group(function () {
Route::get('/profile', [ProfileController::class, 'index'])->name('profile.index');
// Profile Management
Route::prefix('admin/profile')->middleware(['web.auth'])->group(function () {
Route::get('/', [ProfileController::class, 'index'])->name('profile.index');
Route::get('/edit', [ProfileController::class, 'edit'])->name('profile.edit');
Route::put('/update', [ProfileController::class, 'update'])->name('profile.update');
Route::get('/change-password', [ProfileController::class, 'changePasswordForm'])->name('profile.change-password');
Route::put('/change-password', [ProfileController::class, 'updatePassword'])->name('profile.update-password');
Route::get('/change-email', [ProfileController::class, 'changeEmailForm'])->name('profile.change-email');
Route::put('/change-email', [ProfileController::class, 'updateEmail'])->name('profile.update-email');
Route::post('/update-photo', [ProfileController::class, 'updatePhoto'])->name('profile.update-photo');
});
// News Management