Merge pull request #18 from arieeefajar/feat/profile

Feat/profile
This commit is contained in:
Arie Fajar Bachtiar 2025-03-04 19:42:26 +07:00 committed by GitHub
commit 2c90c8a43a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 4675 additions and 28 deletions

View File

@ -0,0 +1,117 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
class ProfileController extends Controller
{
public function index()
{
$user = Auth::user();
return view('auth.profile', compact('user'));
}
public function updateData(request $request)
{
$customMessage = [
'username.required' => 'Username wajib diisi',
'username.max' => 'Username maksimal 12 karakter',
'username.string' => 'Username harus berupa string',
'username.unique' => 'Username sudah terdaftar',
'name.required' => 'Nama wajib diisi',
'name.max' => 'Nama maksimal 255 karakter',
'name.string' => 'Nama harus berupa string',
'name.unique' => 'Nama sudah terdaftar',
'email.required' => 'Email wajib diisi',
'email.email' => 'Email tidak valid',
'email.unique' => 'Email sudah terdaftar',
'email.max' => 'Email maksimal 255 karakter',
'email.string' => 'Email harus berupa string',
'avatar.image' => 'Foto profile harus berupa gambar',
'avatar.mimes' => 'Format foto profile tidak sesuai, harus jpeg, png, atau jpg',
'avatar.max' => 'Ukuran foto profile maksimal 2MB',
];
$validator = Validator::make($request->all(), [
'username' => 'required|string|max:12|unique:users,username,' . Auth::user()->id,
'name' => 'required|string|max:255|unique:users,name,' . Auth::user()->id,
'email' => 'required|string|email|max:255|unique:users,email,' . Auth::user()->id,
'avatar' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',
], $customMessage);
if ($validator->fails()) {
toast($validator->messages()->all()[0], 'error')->position('top-right')->autoclose(3000);
return redirect()->back()->withInput();
}
try {
$user = User::findOrFail(Auth::id());
$user->username = $request->username;
$user->name = $request->name;
$user->email = $request->email;
if ($request->hasFile('avatar')) {
$path = $request->file('avatar')->store('avatars', 'public');
if (!empty($user->avatar) && Storage::disk('public')->exists($user->avatar)) {
Storage::disk('public')->delete($user->avatar);
}
$user->avatar = $path;
}
$user->save();
toast('Data berhasil diubah', 'success')->position('top-right')->autoclose(3000);
return redirect()->back();
} catch (\Throwable $th) {
toast('Terjadi kesalahan', 'error')->position('top-right')->autoclose(3000);
return redirect()->back();
}
}
public function updatePassword(request $request)
{
$customMessage = [
'new_password.required' => 'Password wajib diisi',
'new_password.confirmed' => 'Konfirmasi password tidak cocok',
'new_password.min' => 'Password minimal 8 karakter',
];
$user = User::find(Auth::user()->id);
if (Hash::check($request->old_password, $user->password)) {
$validator = Validator::make($request->all(), [
'new_password' => 'required|string|min:8|confirmed',
], $customMessage);
if ($validator->fails()) {
toast($validator->messages()->all()[0], 'error')->position('top-right')->autoclose(3000);
return redirect()->back()->withInput();
}
$user->password = Hash::make($request->new_password);
try {
$user->save();
toast('Password berhasil diubah', 'success')->position('top-right')->autoclose(3000);
return redirect()->back();
} catch (\Throwable $th) {
toast('Terjadi kesalahan', 'error')->position('top-right')->autoclose(3000);
return redirect()->back();
}
} else {
toast('Password lama tidak cocok', 'error')->position('top-right')->autoclose(3000);
return redirect()->back();
}
}
}

View File

@ -23,6 +23,7 @@ class User extends Authenticatable
'email', 'email',
'password', 'password',
'role', 'role',
'avatar'
]; ];
/** /**

View File

@ -22,6 +22,7 @@ public function up(): void
$table->enum('role', ['admin', 'user'])->default('user'); $table->enum('role', ['admin', 'user'])->default('user');
$table->string('activation_code')->nullable(); $table->string('activation_code')->nullable();
$table->boolean('is_active')->default(false); $table->boolean('is_active')->default(false);
$table->string('avatar')->nullable();
$table->timestamps(); $table->timestamps();
}); });
} }

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
function previewImage(event) {
var input = event.target;
var reader = new FileReader();
reader.onload = function () {
var previewImg = document.getElementById("preview-img");
previewImg.src = reader.result;
};
if (input.files && input.files[0]) {
reader.readAsDataURL(input.files[0]);
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,195 @@
@extends('layouts.app')
@push('title', 'Profile')
@section('content')
<div class="page-content">
<div class="container-fluid">
<div class="position-relative mx-n4 mt-n4">
<div class="profile-wid-bg profile-setting-img">
<img src="assets/images/profile-bg.jpg" class="profile-wid-img" alt="">
</div>
</div>
<div class="row">
<div class="col-xxl-3">
<div class="card mt-n5">
<div class="card-body p-4">
<div class="text-center">
<div class="profile-user position-relative d-inline-block mx-auto mb-4">
<img src="{{ $user->avatar ? asset('storage/' . $user->avatar) : asset('assets/images/users/user-dummy-img.jpg') }}"
class="rounded-circle avatar-xl img-thumbnail user-profile-image"
alt="user-profile-image" id="preview-img">
</div>
<h5 class="fs-16 mb-1">{{ $user->name }}</h5>
<p class="text-muted mb-0">{{ $user->role }}</p>
</div>
</div>
</div>
<!--end card-->
</div>
<!--end col-->
<div class="col-xxl-9">
<div class="card mt-xxl-n5">
<div class="card-header">
<ul class="nav nav-tabs-custom rounded card-header-tabs border-bottom-0" role="tablist">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#personalDetails" role="tab">
<i class="fas fa-home"></i>
Data Personal
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#changePassword" role="tab">
<i class="far fa-user"></i>
Ubah Password
</a>
</li>
</ul>
</div>
<div class="card-body p-4">
<div class="tab-content">
<div class="tab-pane active" id="personalDetails" role="tabpanel">
<form action="{{ route('profile.update_data') }}" class="needs-validation" novalidate
id="personalDetailsForm" method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
<div class="row">
<div class="col-lg-6">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username"
placeholder="Masukan username anda" name="username"
value="{{ old('username', $user->username) }}" required>
<div class="invalid-feedback">
Harap masukan username anda
</div>
</div>
</div>
<!--end col-->
<div class="col-lg-6">
<div class="mb-3">
<label for="fullname" class="form-label">Nama Lengkap</label>
<input type="text" class="form-control" id="name"
placeholder="Masukan nama lengkap anda" name="name"
value="{{ old('name', $user->name) }}" required>
<div class="invalid-feedback">
Harap masukan nama lengkap anda
</div>
</div>
</div>
<!--end col-->
<div class="col-lg-6">
<div class="mb-3">
<label for="emailInput" class="form-label">Alamat Email</label>
<input type="email" class="form-control" id="email"
placeholder="Masukan alamat email anda" name="email"
value="{{ old('email', $user->email) }}" required>
<div class="invalid-feedback">
Harap masukan alamat email anda
</div>
</div>
</div>
<!--end col-->
<div class="col-lg-6">
<div class="mb-3">
<label for="avatarInput" class="form-label">Foto Profile</label>
<input type="file" class="form-control" id="avatar" name="avatar"
accept="image/jpeg,image/png,image/jpg"
onchange="previewImage(event)">
</div>
</div>
<!--end col-->
<div class="col-lg-12">
<div class="hstack gap-2 justify-content-end">
<button type="submit" class="btn btn-success">Ubah</button>
<button type="button" class="btn btn-soft-secondary">Batal</button>
</div>
</div>
<!--end col-->
</div>
<!--end row-->
</form>
</div>
<!--end tab-pane-->
<div class="tab-pane" id="changePassword" role="tabpanel">
<form action="{{ route('profile.update_password') }}" class="needs-validation"
novalidate id="changePasswordForm" method="POST">
@csrf
@method('PUT')
<div class="row g-2">
<div class="col-lg-4">
<div>
<label for="oldpasswordInput" class="form-label">Password
Lama</label>
<input type="password" class="form-control" id="oldpasswordInput"
placeholder="Masukan password lama anda" name="old_password"
required>
<div class="invalid-feedback">
Harap masukan password lama anda
</div>
</div>
</div>
<!--end col-->
<div class="col-lg-4">
<div>
<label for="newpasswordInput" class="form-label">Password
Baru</label>
<input type="password" class="form-control" id="newpasswordInput"
placeholder="Masukan password baru anda" name="new_password"
required>
<div class="invalid-feedback">
Harap masukan password baru anda
</div>
</div>
</div>
<!--end col-->
<div class="col-lg-4">
<div>
<label for="confirmpasswordInput" class="form-label">Konfirmasi
Password</label>
<input type="password" class="form-control" id="confirmpasswordInput"
placeholder="Masukan konfirmasi password baru anda"
name="new_password_confirmation" required>
<div class="invalid-feedback">
Harap masukan konfirmasi password baru anda
</div>
</div>
</div>
<!--end col-->
{{-- <div class="col-lg-12">
<div class="mb-3">
<a href="javascript:void(0);"
class="link-primary text-decoration-underline">Lupa Password ?</a>
</div>
</div> --}}
<!--end col-->
<div class="col-lg-12">
<div class="text-end">
<button type="submit" class="btn btn-success">Ubah Password</button>
</div>
</div>
<!--end col-->
</div>
<!--end row-->
</form>
</div>
<!--end tab-pane-->
</div>
</div>
</div>
</div>
<!--end col-->
</div>
<!--end row-->
</div>
<!-- container-fluid -->
</div><!-- End Page-content -->
@push('other-js')
<!-- profile-setting init js -->
<script src="{{ asset('assets/js/pages/customJs/auth/profile.js') }}"></script>
<script src="{{ asset('assets/js/pages/form-validation.init.js') }}"></script>
@endpush
@endsection

View File

@ -604,7 +604,7 @@ class="form-check-input">
@stack('other-js') @stack('other-js')
<!-- App js --> <!-- App js -->
<script src="assets/js/app.js"></script> <script src="assets/js/customApp.js"></script>
<script src="assets/libs/jquery/jquery.min.js"></script> <script src="assets/libs/jquery/jquery.min.js"></script>
</body> </body>

View File

@ -168,7 +168,8 @@ class="btn btn-icon btn-topbar btn-ghost-secondary rounded-circle light-dark-mod
<button type="button" class="btn" id="page-header-user-dropdown" data-bs-toggle="dropdown" <button type="button" class="btn" id="page-header-user-dropdown" data-bs-toggle="dropdown"
aria-haspopup="true" aria-expanded="false"> aria-haspopup="true" aria-expanded="false">
<span class="d-flex align-items-center"> <span class="d-flex align-items-center">
<img class="rounded-circle header-profile-user" src="assets/images/users/avatar-1.jpg" <img class="rounded-circle header-profile-user"
src="{{ Auth::user()->avatar ? asset('storage/' . Auth::user()->avatar) : asset('assets/images/users/user-dummy-img.jpg') }}"
alt="Header Avatar"> alt="Header Avatar">
<span class="text-start ms-xl-2"> <span class="text-start ms-xl-2">
<span <span
@ -181,29 +182,10 @@ class="d-none d-xl-block ms-1 fs-12 text-muted user-name-sub-text">{{ Auth::user
<div class="dropdown-menu dropdown-menu-end"> <div class="dropdown-menu dropdown-menu-end">
<!-- item--> <!-- item-->
<h6 class="dropdown-header">Selamat Datang {{ Auth::user()->name }}!</h6> <h6 class="dropdown-header">Selamat Datang {{ Auth::user()->name }}!</h6>
<a class="dropdown-item" href="pages-profile.html"><i <a class="dropdown-item" href="{{ route('profile.index') }}"><i
class="mdi mdi-account-circle text-muted fs-16 align-middle me-1"></i> <span class="mdi mdi-account-circle text-muted fs-16 align-middle me-1"></i> <span
class="align-middle">Profile</span></a> class="align-middle">Profile</span></a>
<a class="dropdown-item" href="apps-chat.html"><i
class="mdi mdi-message-text-outline text-muted fs-16 align-middle me-1"></i>
<span class="align-middle">Messages</span></a>
<a class="dropdown-item" href="apps-tasks-kanban.html"><i
class="mdi mdi-calendar-check-outline text-muted fs-16 align-middle me-1"></i>
<span class="align-middle">Taskboard</span></a>
<a class="dropdown-item" href="pages-faqs.html"><i
class="mdi mdi-lifebuoy text-muted fs-16 align-middle me-1"></i> <span
class="align-middle">Help</span></a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a class="dropdown-item" href="pages-profile.html"><i
class="mdi mdi-wallet text-muted fs-16 align-middle me-1"></i> <span
class="align-middle">Balance : <b>$5971.67</b></span></a>
<a class="dropdown-item" href="pages-profile-settings.html"><span
class="badge bg-soft-success text-success mt-1 float-end">New</span><i
class="mdi mdi-cog-outline text-muted fs-16 align-middle me-1"></i> <span
class="align-middle">Settings</span></a>
<a class="dropdown-item" href="auth-lockscreen-basic.html"><i
class="mdi mdi-lock text-muted fs-16 align-middle me-1"></i> <span
class="align-middle">Lock screen</span></a>
<form action="{{ route('auth.logout') }}" class="d-inline" method="POST"> <form action="{{ route('auth.logout') }}" class="d-inline" method="POST">
@csrf @csrf
<button type="submit" class="dropdown-item"> <button type="submit" class="dropdown-item">
@ -211,9 +193,6 @@ class="align-middle">Lock screen</span></a>
class="align-middle" data-key="t-logout">Logout</span> class="align-middle" data-key="t-logout">Logout</span>
</button> </button>
</form> </form>
{{-- <a class="dropdown-item" href="auth-logout-basic.html"><i
class="mdi mdi-logout text-muted fs-16 align-middle me-1"></i> <span
class="align-middle" data-key="t-logout">Logout</span></a> --}}
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,6 +4,7 @@
use App\Http\Controllers\AssessmentHistoryController; use App\Http\Controllers\AssessmentHistoryController;
use App\Http\Controllers\Auth\AuthenticatedSessionController; use App\Http\Controllers\Auth\AuthenticatedSessionController;
use App\Http\Controllers\Auth\PasswordResetLinkController; use App\Http\Controllers\Auth\PasswordResetLinkController;
use App\Http\Controllers\Auth\ProfileController;
use App\Http\Controllers\Auth\RegisteredUserController; use App\Http\Controllers\Auth\RegisteredUserController;
use App\Http\Controllers\Auth\TwoStepVerifyController; use App\Http\Controllers\Auth\TwoStepVerifyController;
use App\Http\Controllers\DashboardController; use App\Http\Controllers\DashboardController;
@ -60,6 +61,12 @@
Route::middleware(['auth', 'verifiedAcount'])->group(function () { Route::middleware(['auth', 'verifiedAcount'])->group(function () {
Route::post('/logout', [AuthenticatedSessionController::class, 'destroy'])->name('auth.logout'); Route::post('/logout', [AuthenticatedSessionController::class, 'destroy'])->name('auth.logout');
Route::prefix('profile')->controller(ProfileController::class)->name('profile.')->group(function () {
Route::get('/', 'index')->name('index');
Route::put('/', 'updateData')->name('update_data');
Route::put('/password', 'updatePassword')->name('update_password');
});
Route::prefix('location')->controller(LocationController::class)->name('location.')->group(function () { Route::prefix('location')->controller(LocationController::class)->name('location.')->group(function () {
Route::get('/get-province', 'getProvinces')->name('get_provinces'); Route::get('/get-province', 'getProvinces')->name('get_provinces');
Route::get('/get-regency/{id}', 'getRegencies')->name('get_regencies'); Route::get('/get-regency/{id}', 'getRegencies')->name('get_regencies');