diff --git a/app/Http/Controllers/Admin/Setting/AccountController.php b/app/Http/Controllers/Admin/Setting/AccountController.php new file mode 100644 index 0000000..2e98eeb --- /dev/null +++ b/app/Http/Controllers/Admin/Setting/AccountController.php @@ -0,0 +1,78 @@ + 'Profil', + 'profile' => User::with(['attachment', 'biography', 'role'])->findOrFail(Auth::id()), + ]); + } + + public function updateProfile(User $user, UserRequest $request): RedirectResponse + { + $validatedData = $request->validated(); + + DB::transaction(function () use ($validatedData, $user) { + $user->update($validatedData); + + $biographyData = collect($validatedData) + ->except([ + 'email', + 'username', + 'avatar', + ]) + ->toArray(); + $user->biography()->update($biographyData); + if (isset($validatedData['avatar'])) { + $this->uploadAttachment($validatedData['avatar'], 'users', User::class, $user->id); + } + }); + + notify()->success('Data berhasil diubah', 'Berhasil'); + + return back(); + } + + public function updatePassword(User $user, PasswordRequest $request): RedirectResponse + { + $validatedData = $request->validated(); + if (! Hash::check($validatedData['current_password'], $user->password)) { + notify()->error('Kata sandi yang Anda masukkan salah', 'Gagal'); + + return back(); + } elseif ($validatedData['new_password'] == $validatedData['repeat_new_password']) { + Auth::logoutOtherDevices($validatedData['current_password']); + $user->update([ + 'password' => Hash::make($validatedData['new_password']), + ]); + + Auth::logout(); + + request()->session()->invalidate(); + + request()->session()->regenerateToken(); + notify()->success('Kata sandi Anda telah berhasil diperbarui. Silakan masuk kembali untuk melanjutkan', 'Berhasil'); + + return redirect()->route('login'); + } + + notify()->error('Kata sandi baru yang Anda masukkan tidak sesuai dengan konfirmasi', 'Gagal'); + } +} diff --git a/app/Http/Controllers/Admin/Setting/ApplicationController.php b/app/Http/Controllers/Admin/Setting/ApplicationController.php index 5c719e4..70bc4b2 100644 --- a/app/Http/Controllers/Admin/Setting/ApplicationController.php +++ b/app/Http/Controllers/Admin/Setting/ApplicationController.php @@ -5,14 +5,11 @@ use App\Http\Controllers\Controller; use App\Http\Requests\Admin\Setting\ApplicationRequest; use App\Models\Setting; -use App\Traits\UploadAttachment; use Illuminate\Contracts\View\View; use Illuminate\Http\RedirectResponse; class ApplicationController extends Controller { - use UploadAttachment; - public function index(): View { return view('pages.admin.setting.application', [ diff --git a/app/Http/Requests/Admin/Master/UserRequest.php b/app/Http/Requests/Admin/Master/UserRequest.php index 316c91f..c7db97d 100644 --- a/app/Http/Requests/Admin/Master/UserRequest.php +++ b/app/Http/Requests/Admin/Master/UserRequest.php @@ -28,25 +28,33 @@ public function rules(): array { $userId = $this->user->id ?? null; $isEdit = $this->isMethod('put'); + $isUserUrl = $this->is('dashboard/master/users/*'); - return [ + $rules = [ 'full_name' => ['required', 'string', 'max:255'], 'username' => ['required', 'string', 'min:5', 'max:15', Rule::unique('users', 'username')->ignore($userId)], 'email' => ['required', 'email', 'max:255', Rule::unique('users', 'email')->ignore($userId)], 'contact_number' => ['required', 'string', 'regex:/^\+?[0-9]{10,15}$/'], 'pob' => ['required', 'string', 'max:75'], 'dob' => ['required', 'date', 'before:today'], - 'entry_date' => ['required', 'date'], - 'base_salary' => ['required', 'numeric', 'min:0', 'max:9999999999.99'], - 'sop' => ['nullable', 'string', 'max:65535'], 'address' => ['required', 'string', 'max:65535'], 'avatar' => $isEdit ? ['nullable', 'image', 'mimes:jpeg,png,jpg,gif,svg', 'max:2048'] : ['required', 'image', 'mimes:jpeg,png,jpg,gif,svg', 'max:2048'], 'gender' => ['required', Rule::in(['1', '2'])], - 'barbershop' => ['required', Rule::exists('barbershop', 'id')], - 'role' => ['required', Rule::exists('roles', 'id')], ]; + + if ($isUserUrl) { + $rules = array_merge($rules, [ + 'entry_date' => ['required', 'date'], + 'base_salary' => ['required', 'numeric', 'min:0', 'max:9999999999.99'], + 'sop' => ['nullable', 'string', 'max:65535'], + 'barbershop' => ['required', Rule::exists('barbershop', 'id')], + 'role' => ['required', Rule::exists('roles', 'id')], + ]); + } + + return $rules; } protected function failedValidation(Validator $validator): void diff --git a/app/Http/Requests/Admin/Setting/PasswordRequest.php b/app/Http/Requests/Admin/Setting/PasswordRequest.php new file mode 100644 index 0000000..9ed6864 --- /dev/null +++ b/app/Http/Requests/Admin/Setting/PasswordRequest.php @@ -0,0 +1,42 @@ + ['required', 'current_password'], + 'new_password' => [ + 'required', + 'different:current_password', + Password::min(8) + ->letters() + ->mixedCase() + ->numbers() + ->symbols() + ->uncompromised(), + ], + 'repeat_new_password' => ['required', 'same:new_password'], + ]; + } + + protected function failedValidation(Validator $validator): void + { + $this->handleValidationFailure($validator); + } +} diff --git a/public/assets/admin/css/icon-password.css b/public/assets/admin/css/icon-password.css new file mode 100644 index 0000000..81fa76b --- /dev/null +++ b/public/assets/admin/css/icon-password.css @@ -0,0 +1,21 @@ +.password-wrapper { + position: relative; + display: flex; + align-items: center; +} + +.password-wrapper input { + padding-right: 40px; +} + +.password-wrapper .toggle-password { + position: absolute; + right: 10px; + cursor: pointer; + color: #6c757d; + font-size: 0.85rem; +} + +.password-wrapper .toggle-password:hover { + color: #495057; +} diff --git a/public/assets/admin/css/weak-password.css b/public/assets/admin/css/weak-password.css new file mode 100644 index 0000000..1537ca4 --- /dev/null +++ b/public/assets/admin/css/weak-password.css @@ -0,0 +1,26 @@ +#newpass-message { + display: none; +} + +#newpass-message p { + font-size: 12px; +} + +.valid { + color: green; +} + +.valid:before { + content: "✔"; + font-family: Verdana, Tahoma, "DejaVu Sans", sans-serif; + +} + +.invalid { + color: red; +} + +.invalid:before { + content: "✖"; + font-family: Verdana, Tahoma, "DejaVu Sans", sans-serif; +} diff --git a/public/assets/css/custom.css b/public/assets/css/custom.css index 2f79286..0eab23a 100644 --- a/public/assets/css/custom.css +++ b/public/assets/css/custom.css @@ -4,6 +4,12 @@ .profile-media img { height: 35px; object-fit: cover; } +.profile-media-md img { + border-radius: 100%; + width: 100px; + height: 100px; + object-fit: cover; +} .mode-active i { fill: #fff !important; diff --git a/public/assets/js/forms/toggle-password-icon.js b/public/assets/js/forms/toggle-password-icon.js new file mode 100644 index 0000000..e584b49 --- /dev/null +++ b/public/assets/js/forms/toggle-password-icon.js @@ -0,0 +1,29 @@ +$(document).ready(function () { + feather.replace(); + + $(".password-wrapper").each(function () { + const $wrapper = $(this); + const $passwordInput = $wrapper.find('input[type="password"]'); + + const $passwordIcon = $('' + + '' + + '' + + ''); + + $wrapper.append($passwordIcon); + + feather.replace(); + + $passwordIcon.find(".feather-eye").on("click", function () { + $(this).hide(); + $passwordIcon.find(".feather-eye-off").show(); + $passwordInput.attr('type', 'text'); + }); + + $passwordIcon.find(".feather-eye-off").on("click", function () { + $(this).hide(); + $passwordIcon.find(".feather-eye").show(); + $passwordInput.attr('type', 'password'); + }); + }); +}); diff --git a/public/assets/js/forms/weak-password.js b/public/assets/js/forms/weak-password.js new file mode 100644 index 0000000..49a9bd9 --- /dev/null +++ b/public/assets/js/forms/weak-password.js @@ -0,0 +1,86 @@ +$(document).ready(function () { + let $newPassword = $("#new_password"); + let $repeatPassword = $("#repeat_new_password"); + let $letter = $("#letter"); + let $capital = $("#capital"); + let $number = $("#number"); + let $symbol = $("#symbol"); + let $length = $("#length"); + let $feedback = $("#feedback"); + let $retypeMessage = $("#repeat_password_message"); + + $newPassword.on("focus", function () { + $("#new_password_message").show(); + }); + + $newPassword.on("blur", function () { + $("#new_password_message").hide(); + }); + + $newPassword.on("keyup", function () { + let value = $newPassword.val(); + + // Validasi huruf kecil + if (/[a-z]/.test(value)) { + $letter.removeClass("invalid").addClass("valid"); + } else { + $letter.removeClass("valid").addClass("invalid"); + } + + // Validasi huruf besar + if (/[A-Z]/.test(value)) { + $capital.removeClass("invalid").addClass("valid"); + } else { + $capital.removeClass("valid").addClass("invalid"); + } + + // Validasi angka + if (/[0-9]/.test(value)) { + $number.removeClass("invalid").addClass("valid"); + } else { + $number.removeClass("valid").addClass("invalid"); + } + + // Validasi panjang minimal + if (value.length >= 8) { + $length.removeClass("invalid").addClass("valid"); + } else { + $length.removeClass("valid").addClass("invalid"); + } + + // Validasi simbol + if (/[!$#%@]/.test(value)) { + $symbol.removeClass("invalid").addClass("valid"); + } else { + $symbol.removeClass("valid").addClass("invalid"); + } + }); + + // Validasi konfirmasi password + $repeatPassword.on("focus", function () { + $feedback.show(); + }); + + $repeatPassword.on("blur", function () { + $feedback.hide(); + }); + + $repeatPassword.on("keyup", function () { + let repeatValue = $repeatPassword.val(); + let newValue = $newPassword.val(); + + if (repeatValue.length === 0) { + $repeatPassword.removeClass("is-valid").addClass("is-invalid"); + $feedback.addClass("text-danger"); + $retypeMessage.html("Silakan masukkan ulang Kata sandi baru!"); + } else if (newValue === repeatValue) { + $repeatPassword.removeClass("is-invalid").addClass("is-valid"); + $feedback.removeClass("text-danger").addClass("text-success"); + $retypeMessage.html("Kata sandi cocok!"); + } else { + $repeatPassword.removeClass("is-valid").addClass("is-invalid"); + $feedback.removeClass("text-success").addClass("text-danger"); + $retypeMessage.html("Kata sandi tidak cocok!"); + } + }); +}); diff --git a/resources/views/pages/admin/setting/account.blade.php b/resources/views/pages/admin/setting/account.blade.php new file mode 100644 index 0000000..e8a6100 --- /dev/null +++ b/resources/views/pages/admin/setting/account.blade.php @@ -0,0 +1,315 @@ +@extends('layouts.admin') +@section('styles') + + + + + +@endsection +@section('app') +
+
+
+
+
+
+
+ @csrf + @method('put') +
+
+
+ imageattachment->formatted_attachment}") }}"> +
+
{{ $profile->biography->full_name ?? '' }}
+

{{ $profile->role->name ?? '' }}

+
+
+
+
+
+ + + @error('current_password') +
+ {{ $message }} +
+ @enderror +
+
+ +
+ +
+ + @error('new_password') +
+ {{ $message }} +
+ @enderror +
+
+ +
+ +
+ @error('repeat_new_password') +
+ {{ $message }} +
+ @enderror + +
+
+ +
+
+
+
+
+
+
+ @csrf + @method('put') +
+
+
+
+ + + @error('full_name') +
+ {{ $message }} +
+ @enderror +
+
+
+
+ + + @error('username') +
+ {{ $message }} +
+ @enderror +
+
+
+
+ + + @error('email') +
+ {{ $message }} +
+ @enderror +
+
+
+
+ + + @error('contact_number') +
+ {{ $message }} +
+ @enderror +
+
+
+
+ + + @error('pob') +
+ {{ $message }} +
+ @enderror +
+
+
+
+ +
+ +
+ @error('dob') +
+ {{ $message }} +
+ @enderror +
+
+
+
+ + + @error('entry_date') +
+ {{ $message }} +
+ @enderror +
+
+
+
+ + + @error('base_salary') +
+ {{ $message }} +
+ @enderror +
+
+
+
+ + + @error('sop') +
+ {{ $message }} +
+ @enderror +
+
+
+
+ + + @error('address') +
+ {{ $message }} +
+ @enderror +
+
+
+
+ + + @error('avatar') +
+ {{ $message }} +
+ @enderror +
+
+
+
+ + + @error('gender') +
+ {{ $message }} +
+ @enderror +
+
+
+
+ + + @error('role') +
+ {{ $message }} +
+ @enderror +
+
+
+ +
+
+
+
+
+
+
+
+@endsection +@section('beforeScripts') + + +@endsection +@section('afterScripts') + + + + + +@endsection diff --git a/resources/views/pages/admin/setting/application.blade.php b/resources/views/pages/admin/setting/application.blade.php index f011bf6..f842685 100644 --- a/resources/views/pages/admin/setting/application.blade.php +++ b/resources/views/pages/admin/setting/application.blade.php @@ -1,6 +1,5 @@ @extends('layouts.admin') @section('styles') - @endsection @section('app')
diff --git a/resources/views/partials/admin/_header.blade.php b/resources/views/partials/admin/_header.blade.php index 3696784..2eda1e2 100644 --- a/resources/views/partials/admin/_header.blade.php +++ b/resources/views/partials/admin/_header.blade.php @@ -25,7 +25,8 @@
  • - image + image
    {{ Auth::user()->biography->full_name }}

    {{ Auth::user()->role->name }}

    @@ -33,7 +34,7 @@
    • - + Profil
    • diff --git a/routes/web.php b/routes/web.php index 73be892..8e4450c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -12,6 +12,7 @@ use App\Http\Controllers\Admin\Master\CustomerController; use App\Http\Controllers\Admin\Master\ServiceController; use App\Http\Controllers\Admin\Master\UserController; +use App\Http\Controllers\Admin\Setting\AccountController; use App\Http\Controllers\Admin\Setting\ApplicationController; use Illuminate\Support\Facades\Route; @@ -123,5 +124,11 @@ Route::get('/', [ApplicationController::class, 'index'])->name('setting.application'); Route::put('update/{application}', [ApplicationController::class, 'update'])->name('setting.application.update'); }); + + Route::prefix('account')->group(function () { + Route::get('/', [AccountController::class, 'index'])->name('setting.account'); + Route::put('update-profile/{user}', [AccountController::class, 'updateProfile'])->name('setting.account.updateProfile'); + Route::put('update-password/{user}', [AccountController::class, 'updatePassword'])->name('setting.account.updatePassword'); + }); }); });