update: deploy
parent
6ab4d52e7f
commit
8d0f9277cd
|
@ -3,6 +3,10 @@
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
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
|
class ProfileController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -10,4 +14,194 @@ class ProfileController extends Controller
|
||||||
{
|
{
|
||||||
return view('components/users/viewProfile');
|
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)
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
|
@ -12,7 +13,21 @@ use Spatie\Permission\Traits\HasRoles;
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
/** @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.
|
* The attributes that are mass assignable.
|
||||||
|
@ -24,6 +39,7 @@ class User extends Authenticatable
|
||||||
'email',
|
'email',
|
||||||
'username',
|
'username',
|
||||||
'password',
|
'password',
|
||||||
|
'profile_photo',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\URL;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
|
@ -19,6 +20,9 @@ class AppServiceProvider extends ServiceProvider
|
||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
//
|
if ($this->app->environment('production')) {
|
||||||
|
URL::forceScheme('https');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,13 @@ return new class extends Migration
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('users', function (Blueprint $table) {
|
Schema::create('users', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->uuid('id')->primary();
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
$table->string('email')->unique();
|
$table->string('email')->unique();
|
||||||
|
$table->string('username')->nullable()->unique();
|
||||||
$table->timestamp('email_verified_at')->nullable();
|
$table->timestamp('email_verified_at')->nullable();
|
||||||
$table->string('password');
|
$table->string('password');
|
||||||
|
$table->string('profile_photo')->nullable();
|
||||||
$table->rememberToken();
|
$table->rememberToken();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
@ -29,7 +31,7 @@ return new class extends Migration
|
||||||
|
|
||||||
Schema::create('sessions', function (Blueprint $table) {
|
Schema::create('sessions', function (Blueprint $table) {
|
||||||
$table->string('id')->primary();
|
$table->string('id')->primary();
|
||||||
$table->foreignId('user_id')->nullable()->index();
|
$table->uuid('user_id')->nullable()->index();
|
||||||
$table->string('ip_address', 45)->nullable();
|
$table->string('ip_address', 45)->nullable();
|
||||||
$table->text('user_agent')->nullable();
|
$table->text('user_agent')->nullable();
|
||||||
$table->longText('payload');
|
$table->longText('payload');
|
||||||
|
|
|
@ -13,7 +13,9 @@ return new class extends Migration
|
||||||
{
|
{
|
||||||
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->morphs('tokenable');
|
$table->string('tokenable_type');
|
||||||
|
$table->string('tokenable_id');
|
||||||
|
$table->index(['tokenable_type', 'tokenable_id']);
|
||||||
$table->text('name');
|
$table->text('name');
|
||||||
$table->string('token', 64)->unique();
|
$table->string('token', 64)->unique();
|
||||||
$table->text('abilities')->nullable();
|
$table->text('abilities')->nullable();
|
||||||
|
|
|
@ -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');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ return new class extends Migration
|
||||||
$table->unsignedBigInteger($pivotPermission);
|
$table->unsignedBigInteger($pivotPermission);
|
||||||
|
|
||||||
$table->string('model_type');
|
$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->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
|
||||||
|
|
||||||
$table->foreign($pivotPermission)
|
$table->foreign($pivotPermission)
|
||||||
|
@ -62,20 +62,23 @@ return new class extends Migration
|
||||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||||
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
|
$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'],
|
$table->primary(
|
||||||
'model_has_permissions_permission_model_type_primary');
|
[$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||||
|
'model_has_permissions_permission_model_type_primary'
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
$table->primary(
|
||||||
'model_has_permissions_permission_model_type_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) {
|
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
||||||
$table->unsignedBigInteger($pivotRole);
|
$table->unsignedBigInteger($pivotRole);
|
||||||
|
|
||||||
$table->string('model_type');
|
$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->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
|
||||||
|
|
||||||
$table->foreign($pivotRole)
|
$table->foreign($pivotRole)
|
||||||
|
@ -86,11 +89,15 @@ return new class extends Migration
|
||||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||||
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
|
$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'],
|
$table->primary(
|
||||||
'model_has_roles_role_model_type_primary');
|
[$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||||
|
'model_has_roles_role_model_type_primary'
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
$table->primary(
|
||||||
'model_has_roles_role_model_type_primary');
|
[$pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||||
|
'model_has_roles_role_model_type_primary'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,112 +6,177 @@
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
<div class="card basic-data-table">
|
<div class="card basic-data-table">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div
|
<!-- Header Section -->
|
||||||
class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-3 gap-3">
|
<div class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-4 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h5 class="mb-0">Edit Pengguna: {{ $user->name }}</h5>
|
<h5 class="mb-0">Edit Pengguna</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2">
|
<div>
|
||||||
<a href="{{ route('admin.users.index') }}"
|
<a href="{{ route('admin.users.index') }}" class="btn btn-secondary btn-sm d-flex align-items-center gap-2">
|
||||||
class="btn btn-secondary btn-sm d-flex align-items-center gap-2">
|
|
||||||
<iconify-icon icon="iconoir:arrow-left"></iconify-icon>
|
<iconify-icon icon="iconoir:arrow-left"></iconify-icon>
|
||||||
<span>Kembali</span>
|
<span>Kembali</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
@if ($errors->any())
|
@if ($errors->any())
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
<ul class="mb-0">
|
<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)
|
@foreach ($errors->all() as $error)
|
||||||
<li>{{ $error }}</li>
|
<li>{{ $error }}</li>
|
||||||
@endforeach
|
@endforeach
|
||||||
</ul>
|
</ul>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@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
|
@csrf
|
||||||
@method('PUT')
|
@method('PUT')
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-12 col-md-6">
|
<!-- Personal Information Section -->
|
||||||
<label class="form-label">Nama Lengkap</label>
|
<div class="mb-4">
|
||||||
<input type="text" name="name" class="form-control" required
|
<h6 class="mb-3 text-primary d-flex align-items-center">
|
||||||
value="{{ old('name', $user->name) }}" placeholder="Masukkan nama lengkap">
|
<iconify-icon icon="mdi:account-circle" class="me-2"></iconify-icon>
|
||||||
</div>
|
Informasi Personal
|
||||||
<div class="col-12 col-md-6">
|
</h6>
|
||||||
<label class="form-label">Email</label>
|
<div class="row g-3">
|
||||||
<input type="email" name="email" class="form-control" required
|
<div class="col-12 col-md-6">
|
||||||
value="{{ old('email', $user->email) }}" placeholder="Masukkan email">
|
<label class="form-label fw-medium">
|
||||||
</div>
|
Nama Lengkap <span class="text-danger">*</span>
|
||||||
<div class="col-12 col-md-6">
|
</label>
|
||||||
<label class="form-label">Username</label>
|
<input type="text"
|
||||||
<input type="text" name="username" class="form-control" required
|
name="name"
|
||||||
value="{{ old('username', $user->username) }}" placeholder="Masukkan username">
|
class="form-control @error('name') is-invalid @enderror"
|
||||||
</div>
|
required
|
||||||
<div class="col-12 col-md-6">
|
value="{{ old('name', $user->name) }}"
|
||||||
<label class="form-label">Password Baru</label>
|
placeholder="Masukkan nama lengkap">
|
||||||
<input type="password" name="password" class="form-control"
|
@error('name')
|
||||||
placeholder="Kosongkan jika tidak ingin mengubah">
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
<div class="form-text">Kosongkan jika tidak ingin mengubah password. Password harus minimal 8
|
@enderror
|
||||||
karakter, mengandung huruf besar, huruf kecil, angka, dan simbol khusus.</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<label class="form-label">Konfirmasi Password Baru</label>
|
<label class="form-label fw-medium">
|
||||||
<input type="password" name="password_confirmation" class="form-control"
|
Email <span class="text-danger">*</span>
|
||||||
placeholder="Konfirmasi password baru">
|
</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 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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4">
|
<!-- Divider -->
|
||||||
<label class="form-label">Roles</label>
|
<hr class="my-4">
|
||||||
<div class="row g-2">
|
|
||||||
@foreach ($roles as $role)
|
<!-- Password Section -->
|
||||||
<div class="col-12 col-md-4">
|
<div class="mb-4">
|
||||||
<div class="form-check style-check d-flex align-items-center">
|
<h6 class="mb-3 text-primary d-flex align-items-center">
|
||||||
<input class="form-check-input border border-neutral-300 me-8" type="checkbox"
|
<iconify-icon icon="mdi:lock" class="me-2"></iconify-icon>
|
||||||
name="roles[]" value="{{ $role->name }}" id="role_{{ $role->id }}"
|
Keamanan
|
||||||
{{ in_array($role->name, old('roles', $userRoles)) ? 'checked' : '' }}>
|
</h6>
|
||||||
<label class="form-check-label"
|
<div class="row g-3">
|
||||||
for="role_{{ $role->id }}">{{ $role->name }}</label>
|
<div class="col-12 col-md-6">
|
||||||
</div>
|
<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>
|
</div>
|
||||||
@endforeach
|
@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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- User Info -->
|
<!-- Roles Section -->
|
||||||
<div class="mt-4">
|
<div class="mb-4">
|
||||||
<div class="alert alert-info">
|
<h6 class="mb-3 text-primary d-flex align-items-center">
|
||||||
<h6>Informasi Pengguna:</h6>
|
<iconify-icon icon="mdi:shield-account" class="me-2"></iconify-icon>
|
||||||
<ul class="mb-0">
|
Roles & Permissions
|
||||||
<li>Dibuat: {{ $user->created_at->format('d/m/Y H:i:s') }}</li>
|
</h6>
|
||||||
<li>Terakhir diperbarui: {{ $user->updated_at->format('d/m/Y H:i:s') }}</li>
|
<div class="p-3 bg-light rounded-3">
|
||||||
@if ($user->email_verified_at)
|
@if ($roles->count() > 0)
|
||||||
<li>Email diverifikasi: {{ $user->email_verified_at->format('d/m/Y H:i:s') }}</li>
|
<div class="row g-2">
|
||||||
@else
|
@foreach ($roles as $role)
|
||||||
<li class="text-warning">Email belum diverifikasi</li>
|
<div class="col-12 col-sm-6 col-md-4">
|
||||||
@endif
|
<div class="form-check style-check d-flex align-items-center gap-2">
|
||||||
</ul>
|
<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 fw-medium" for="role_{{ $role->id }}">
|
||||||
|
{{ ucfirst($role->name) }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<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
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 d-flex gap-2">
|
<!-- Action Buttons -->
|
||||||
<button class="btn btn-primary d-flex align-items-center gap-2">
|
<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>
|
<iconify-icon icon="material-symbols:save"></iconify-icon>
|
||||||
<span>Perbarui</span>
|
<span>Perbarui Pengguna</span>
|
||||||
</button>
|
</button>
|
||||||
<a href="{{ route('admin.users.show', $user) }}" class="btn btn-info d-flex align-items-center gap-2">
|
<a href="{{ route('admin.users.index') }}" class="btn btn-light d-flex align-items-center gap-2">
|
||||||
<iconify-icon icon="lucide:eye"></iconify-icon>
|
<iconify-icon icon="material-symbols:cancel"></iconify-icon>
|
||||||
<span>Lihat Detail</span>
|
<span>Batal</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('admin.users.index') }}" class="btn btn-light">Batal</a>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
<div
|
<div
|
||||||
class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-3 gap-3">
|
class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-3 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h5 class="mb-0">Detail Pengguna: {{ $user->name }}</h5>
|
<h5 class="mb-0">Detail Pengguna:</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<a href="{{ route('admin.users.index') }}"
|
<a href="{{ route('admin.users.index') }}"
|
||||||
|
@ -51,10 +51,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="table table-borderless mb-0">
|
<table class="table table-borderless mb-0">
|
||||||
<tr>
|
|
||||||
<td width="150"><strong>ID:</strong></td>
|
|
||||||
<td>{{ $user->id }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>Nama:</strong></td>
|
<td><strong>Nama:</strong></td>
|
||||||
<td>{{ $user->name }}</td>
|
<td>{{ $user->name }}</td>
|
||||||
|
@ -63,11 +59,6 @@
|
||||||
<td><strong>Email:</strong></td>
|
<td><strong>Email:</strong></td>
|
||||||
<td>
|
<td>
|
||||||
{{ $user->email }}
|
{{ $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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -133,8 +124,8 @@
|
||||||
<h6 class="mb-0">Ringkasan Aktivitas</h6>
|
<h6 class="mb-0">Ringkasan Aktivitas</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row g-3">
|
<div class="row g-4">
|
||||||
<div class="col-md-3">
|
<div class="col-md-4">
|
||||||
<div class="card bg-light text-center">
|
<div class="card bg-light text-center">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="card-title">Total Role</h6>
|
<h6 class="card-title">Total Role</h6>
|
||||||
|
@ -142,7 +133,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-4">
|
||||||
<div class="card bg-light text-center">
|
<div class="card bg-light text-center">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="card-title">Total Permission</h6>
|
<h6 class="card-title">Total Permission</h6>
|
||||||
|
@ -150,23 +141,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">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="card bg-light text-center">
|
<div class="card bg-light text-center">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="card-title">Akun Aktif</h6>
|
<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>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<div class="d-flex flex-wrap align-items-center gap-3">
|
<div class="d-flex flex-wrap align-items-center gap-3">
|
||||||
{{--
|
{{--
|
||||||
<div class="dropdown">
|
<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">
|
<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>
|
<iconify-icon icon="mage:email" class="text-primary-light text-xl"></iconify-icon>
|
||||||
|
@ -207,24 +207,33 @@
|
||||||
</div><!-- Notification dropdown end --> --}}
|
</div><!-- Notification dropdown end --> --}}
|
||||||
|
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button class="d-flex justify-content-center align-items-center rounded-circle" type="button" data-bs-toggle="dropdown">
|
<button class="d-flex justify-content-center align-items-center rounded-circle" type="button"
|
||||||
<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>
|
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>
|
</button>
|
||||||
<div class="dropdown-menu to-top dropdown-menu-sm">
|
<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>
|
<div>
|
||||||
<h6 class="text-lg text-primary-light fw-semibold mb-2">{{ auth()->user()->name ?? 'User' }}</h6>
|
<h6 class="text-lg text-primary-light fw-semibold mb-2">
|
||||||
<span class="text-secondary-light fw-medium text-sm">{{ optional(auth()->user())->getRoleNames()->implode(', ') ?? '-' }}</span>
|
{{ auth()->user()->name ?? 'User' }}</h6>
|
||||||
|
<span
|
||||||
|
class="text-secondary-light fw-medium text-sm">{{ optional(auth()->user())->getRoleNames()->implode(', ') ?? '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="to-top-list">
|
<ul class="to-top-list">
|
||||||
<li>
|
<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') }}">
|
<a class="dropdown-item text-black px-0 py-8 hover-bg-transparent hover-text-primary d-flex align-items-center gap-3"
|
||||||
<iconify-icon icon="solar:user-linear" class="icon text-xl"></iconify-icon> My Profile
|
href="{{ route('profile.index') }}">
|
||||||
|
<iconify-icon icon="solar:user-linear" class="icon text-xl"></iconify-icon> Profil
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<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
|
<iconify-icon icon="lucide:power" class="icon text-xl"></iconify-icon> Log Out
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -239,32 +248,37 @@
|
||||||
<form id="logout-form" action="{{ route('logout.session') }}" method="POST" class="d-none">
|
<form id="logout-form" action="{{ route('logout.session') }}" method="POST" class="d-none">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="_token" value="{{ csrf_token() }}">
|
||||||
</form>
|
</form>
|
||||||
<script>
|
<script>
|
||||||
(function(){
|
(function() {
|
||||||
const btn = document.getElementById('btn-logout');
|
const btn = document.getElementById('btn-logout');
|
||||||
if(!btn) return;
|
if (!btn) return;
|
||||||
btn.addEventListener('click', async function(e){
|
btn.addEventListener('click', async function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const token = localStorage.getItem('auth_token');
|
const token = localStorage.getItem('auth_token');
|
||||||
try {
|
try {
|
||||||
if (token) {
|
if (token) {
|
||||||
await fetch('{{ url('/api/auth/logout') }}', {
|
await fetch('{{ url('/api/auth/logout') }}', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Authorization': 'Bearer ' + token, 'Accept': 'application/json' }
|
headers: {
|
||||||
}).catch(()=>{});
|
'Authorization': 'Bearer ' + token,
|
||||||
}
|
'Accept': 'application/json'
|
||||||
} catch (err) { /* ignore */ }
|
}
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
|
||||||
localStorage.removeItem('auth_token');
|
localStorage.removeItem('auth_token');
|
||||||
localStorage.removeItem('auth_user');
|
localStorage.removeItem('auth_user');
|
||||||
// Submit standard POST form to ensure session logout with CSRF
|
// Submit standard POST form to ensure session logout with CSRF
|
||||||
const form = document.getElementById('logout-form');
|
const form = document.getElementById('logout-form');
|
||||||
if (form) {
|
if (form) {
|
||||||
form.submit();
|
form.submit();
|
||||||
} else {
|
} else {
|
||||||
window.location.href = '{{ route('login.index') }}';
|
window.location.href = '{{ route('login.index') }}';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,140 +1,280 @@
|
||||||
@extends('layout.layout')
|
@extends('layout.layout')
|
||||||
@php
|
@php
|
||||||
$title='View Profile';
|
$title = 'Profil Pengguna';
|
||||||
$subTitle = 'View Profile';
|
$subTitle = 'Kelola Profil Anda';
|
||||||
$script ='<script>
|
|
||||||
// ======================== Upload Image Start =====================
|
|
||||||
function readURL(input) {
|
|
||||||
if (input.files && input.files[0]) {
|
|
||||||
var reader = new FileReader();
|
|
||||||
reader.onload = function(e) {
|
|
||||||
$("#imagePreview").css("background-image", "url(" + e.target.result + ")");
|
|
||||||
$("#imagePreview").hide();
|
|
||||||
$("#imagePreview").fadeIn(650);
|
|
||||||
}
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Call the function
|
|
||||||
initializePasswordToggle(".toggle-password");
|
|
||||||
// ========================= Password Show Hide Js End ===========================
|
|
||||||
</script>';
|
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
// Upload Image functionality
|
||||||
|
function readURL(input) {
|
||||||
|
if (input.files && input.files[0]) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
$("#imagePreview").css("background-image", "url(" + e.target.result + ")");
|
||||||
|
$("#imagePreview").hide();
|
||||||
|
$("#imagePreview").fadeIn(650);
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(input.files[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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')
|
@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="row gy-4">
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-4">
|
||||||
<div class="user-grid-card position-relative border radius-16 overflow-hidden bg-base h-100">
|
<div class="card h-100">
|
||||||
<img src="{{ asset('assets/images/user-grid/user-grid-bg1.png') }}" alt="" class="w-100 object-fit-cover">
|
<div class="card-body text-center">
|
||||||
<div class="pb-24 ms-16 mb-24 me-16 mt--100">
|
<!-- Profile Image -->
|
||||||
<div class="text-center border border-top-0 border-start-0 border-end-0">
|
<div class="mb-3">
|
||||||
<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">
|
<div class="avatar-upload d-inline-block position-relative">
|
||||||
<h6 class="mb-0 mt-16">Helmy Zulhidayat</h6>
|
<div class="avatar-edit position-absolute bottom-0 end-0 z-1 cursor-pointer">
|
||||||
<span class="text-secondary-light mb-16">amdal.jakarta@gmail.com</span>
|
<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>
|
||||||
<div class="mt-24">
|
<div class="avatar-preview">
|
||||||
<h6 class="text-xl mb-16">Personal Info</h6>
|
@php
|
||||||
<ul>
|
$defaultPhoto = asset('assets/images/user-grid/user-grid-img14.png');
|
||||||
<li class="d-flex align-items-center gap-1 mb-12">
|
$userPhoto = auth()->user()->profile_photo
|
||||||
<span class="w-30 text-md fw-semibold text-primary-light">Full Name</span>
|
? asset('storage/profile_photos/' . auth()->user()->profile_photo)
|
||||||
<span class="w-70 text-secondary-light fw-medium">: Helmy Zulhidayat</span>
|
: $defaultPhoto;
|
||||||
</li>
|
@endphp
|
||||||
<li class="d-flex align-items-center gap-1 mb-12">
|
<div id="imagePreview"
|
||||||
<span class="w-30 text-md fw-semibold text-primary-light"> Email</span>
|
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);">
|
||||||
<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">
|
|
||||||
<iconify-icon icon="solar:camera-outline" class="icon"></iconify-icon>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="avatar-preview">
|
|
||||||
<div id="imagePreview">
|
|
||||||
</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>
|
|
||||||
</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>
|
|
||||||
</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">
|
|
||||||
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>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 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>
|
||||||
|
@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>
|
</div>
|
||||||
</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
|
||||||
|
</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: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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -153,9 +153,16 @@ Route::prefix('admin')->middleware(['web.auth'])->group(function () {
|
||||||
Route::get('/penugasan', [PenugasanController::class, 'index'])->name('penugasan.index');
|
Route::get('/penugasan', [PenugasanController::class, 'index'])->name('penugasan.index');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Profile
|
// Profile Management
|
||||||
Route::prefix('admin')->middleware(['web.auth'])->group(function () {
|
Route::prefix('admin/profile')->middleware(['web.auth'])->group(function () {
|
||||||
Route::get('/profile', [ProfileController::class, 'index'])->name('profile.index');
|
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
|
// News Management
|
||||||
|
|
Loading…
Reference in New Issue