feat(account): update profile dan password,custom css dan js

This commit is contained in:
Yoga Pangestu 2024-11-27 16:16:14 +07:00
parent 5cf06845e1
commit 5c5e402552
13 changed files with 627 additions and 12 deletions

View File

@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers\Admin\Setting;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Master\UserRequest;
use App\Http\Requests\Admin\Setting\PasswordRequest;
use App\Models\User;
use App\Traits\UploadAttachment;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
class AccountController extends Controller
{
use UploadAttachment;
public function index(): View
{
return view('pages.admin.setting.account', [
'pageTitle' => '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');
}
}

View File

@ -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', [

View File

@ -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

View File

@ -0,0 +1,42 @@
<?php
namespace App\Http\Requests\Admin\Setting;
use App\Traits\NotificationTrait;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rules\Password;
class PasswordRequest extends FormRequest
{
use NotificationTrait;
public function authorize(): bool
{
return Auth::check();
}
public function rules(): array
{
return [
'current_password' => ['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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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 = $('<apan class="toggle-password">' +
'<i data-feather="eye" class="feather-eye"></i>' +
'<i data-feather="eye-off" class="feather-eye-off" style="display:none;"></i>' +
'</apan>');
$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');
});
});
});

View File

@ -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 <b>Kata sandi baru!</b>");
} else if (newValue === repeatValue) {
$repeatPassword.removeClass("is-invalid").addClass("is-valid");
$feedback.removeClass("text-danger").addClass("text-success");
$retypeMessage.html("Kata sandi <b>cocok!</b>");
} else {
$repeatPassword.removeClass("is-valid").addClass("is-invalid");
$feedback.removeClass("text-success").addClass("text-danger");
$retypeMessage.html("Kata sandi <b>tidak cocok!</b>");
}
});
});

View File

@ -0,0 +1,315 @@
@extends('layouts.admin')
@section('styles')
<link rel="stylesheet" href="{{ asset('assets/admin/css/vendors/flatpickr/flatpickr.min.css') }}">
<link rel="stylesheet" href="{{ asset('assets/admin/css/vendors/select2.css') }}">
<link rel="stylesheet" href="{{ asset('assets/admin/css/weak-password.css') }}">
<link rEL="stylesheet" href="{{ asset('assets/admin/css/icon-password.css') }}">
</link>
@endsection
@section('app')
<div class="container-fluid">
<div class="edit-profile">
<div class="row">
<div class="col-xl-4">
<div class="card">
<div class="card-body">
<form action="{{ route('setting.account.updatePassword', encryptId($profile->id)) }}"
method="post">
@csrf
@method('put')
<div class="row mb-2">
<div class="profile-title">
<div class="media profile-media-md">
<img alt="image"
src="{{ Storage::url("users/{$profile->attachment->formatted_attachment}") }}">
<div class="media-body">
<h5 class="mb-1">{{ $profile->biography->full_name ?? '' }}</h5>
<p>{{ $profile->role->name ?? '' }}</p>
</div>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label" for="current_password">Kata Sandi Saat Ini</label>
<input name="current_password"
class="form-control @error('current_password') is-invalid @enderror" type="password"
id="current_password" placeholder="********">
@error('current_password')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
<div class="mb-3">
<label class="form-label" for="new_password">Kata Sandi Baru</label>
<div class="password-wrapper">
<input name="new_password"
class="form-control @error('new_password') is-invalid @enderror" type="password"
id="new_password" placeholder="********"
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"
title="Harus berisi setidaknya satu angka dan satu huruf besar dan kecil, dan setidaknya 8 karakter atau lebih">
</div>
<div role="alert" id="new_password_message" style="display: none">
<p style="font-weight: bold;" class="mt-3"> Kata Sandi harus
terdiri
dari: </p>
<p id="length" class="invalid"> Minimal <b> 8
karakter
</b> </p>
<p id="letter" class="invalid"> Huruf <b> kecil
(a-z)</b> </p>
<p id="capital" class="invalid"> Huruf <b> KAPITAL
(A-Z)</b></p>
<p id="number" class="invalid"> <b>Angka</b>(0-9)
</p>
<p id="symbol" class="invalid">
<b>Symbol</b>(!$#%@)
</p>
</div>
@error('new_password')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
<div class="mb-3">
<label class="form-label" for="repeat_new_password">Ulangi Kata Sandi Saat Baru</label>
<div class="password-wrapper">
<input name="repeat_new_password"
class="form-control @error('repeat_new_password') is-invalid @enderror"
type="password" id="repeat_new_password" placeholder="********"
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"
title="Harus berisi setidaknya satu angka dan satu huruf besar dan kecil, dan setidaknya 8 karakter atau lebih">
</div>
@error('repeat_new_password')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
<div id="feedback" class="mt-2" style="display: none;">
<div id="repeat_password_message"></div>
</div>
</div>
<div class="mb-3">
<button class="btn btn-primary button-disable" type="submit">Simpan</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-xl-8">
<form class="card" action="{{ route('setting.account.updateProfile', encryptId($profile->id)) }}"
method="post" enctype="multipart/form-data">
@csrf
@method('put')
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label" for="full_name">Nama Lengkap</label>
<input name="full_name"
class="form-control @error('full_name') is-invalid @enderror" type="text"
placeholder="Masukan Nama Lengkap"
value="{{ old('full_name', $profile->biography->full_name) }}" id="full_name">
@error('full_name')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label" for="username">Nama Pengguna</label>
<input name="username"
class="form-control @error('username') is-invalid @enderror" type="text"
placeholder="Masukan Nama Pengguna"
value="{{ old('username', $profile->username) }}" id="username">
@error('username')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label" for="email">Email</label>
<input name="email" class="form-control @error('email') is-invalid @enderror"
type="text" placeholder="Masukan Email"
value="{{ old('email', $profile->email) }}" id="email">
@error('email')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label" for="contact_number">Kontak
<span class="form-span"><i>(format : 08xxxxxxxx)</i></span>
</label>
<input name="contact_number"
class="form-control @error('contact_number') is-invalid @enderror"
type="text" placeholder="Masukan Kontak"
value="{{ old('contact_number', $profile->biography->contact_number) }}"
id="contact_number">
@error('contact_number')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label" for="pob">Tempat Lahir</label>
<input name="pob" class="form-control @error('pob') is-invalid @enderror"
type="text" placeholder="Masukan Tempat Lahir"
value="{{ old('pob', $profile->biography->pob) }}" id="pob">
@error('pob')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label" for="dob">Tanggal Lahir</label>
<div class="input-group flatpicker-calender">
<input name="dob"
class="form-control @error('dob') is-invalid @enderror human-friendly"
type="date" placeholder="Masukan Tanggal Lahir"
value="{{ old('dob', $profile->biography->dob) }}" id="dob">
</div>
@error('dob')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label" for="entry_date">Tanggal Bekerja</label>
<input name="entry_date"
class="form-control @error('entry_date') is-invalid @enderror" type="text"
placeholder="Masukan Tanggal Bekerja"
value="{{ old('entry_date', formatDateIndo($profile->entry_date)) }}"
id="entry_date" disabled>
@error('entry_date')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label" for="base_salary">Gaji Pokok</label>
<input name="base_salary"
class="form-control @error('base_salary') is-invalid @enderror"
type="text" placeholder="Masukan Gaji Pokok"
value="{{ old('base_salary', formatToRupiah($profile->base_salary)) }}"
id="base_salary" disabled>
@error('base_salary')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label" for="sop">SOP</label>
<textarea name="sop" class="form-control @error('sop') is-invalid @enderror" id="sop" rows="3"
placeholder="Masukan SOP" disabled>{{ old('sop', $profile->sop) }}</textarea>
@error('sop')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label" for="address">Alamat</label>
<textarea name="address" class="form-control @error('address') is-invalid @enderror" id="address" rows="3"
placeholder="Masukan Alamat">{{ old('address', $profile->biography->address) }}</textarea>
@error('address')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label" for="avatar">Avatar
<span class="form-span"><i>(.jpeg,.png,.jpg,.gif,.svg)</i></span>
</label>
<input name="avatar" class="form-control @error('avatar') is-invalid @enderror"
id="avatar" type="file" value="{{ old('avatar') }}"
accept="image/jpeg,image/png,image/jpg,image/gif,image/svg">
@error('avatar')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label" for="gender">Jenis Kelamin
</label>
<select name="gender"
class="form-control @error('gender') is-invalid @enderror select2"
id="gender">
<option value=""></option>
<option value="1" @selected($profile->biography->gender == '1')>Laki-laki</option>
<option value="2" @selected($profile->biography->gender == '2')>Perempuan</option>
</select>
@error('gender')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
</div>
<div class="col-md-12">
<div class="mb-3">
<label class="form-label" for="role">Role</label>
<input name="role" class="form-control @error('role') is-invalid @enderror"
type="text" placeholder="Masukan Role"
value="{{ old('role', $profile->role->name) }}" id="role" disabled>
@error('role')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
</div>
<div class="col-md-12">
<button class="btn btn-primary button-disable" type="submit">Simpan</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection
@section('beforeScripts')
<script src="{{ asset('assets/admin/js/flat-pickr/flatpickr.js?ver=1.0.0') }}"></script>
<script src="{{ asset('assets/admin/js/flat-pickr/custom-flatpickr.js?ver=1.0.0') }}"></script>
@endsection
@section('afterScripts')
<script src="{{ asset('assets/js/forms/button-disable.js?ver=1.0.0') }}"></script>
<script src="{{ asset('assets/admin/js/select2/select2.js') }}"></script>
<script src="{{ asset('assets/admin/js/select2/select2-config.js?ver=1.0.0') }}"></script>
<script src="{{ asset('assets/js/forms/weak-password.js?ver=1.0.0') }}"></script>
<script src="{{ asset('assets/js/forms/toggle-password-icon.js?ver=1.0.0') }}"></script>
@endsection

View File

@ -1,6 +1,5 @@
@extends('layouts.admin')
@section('styles')
<link rel="stylesheet" href="{{ asset('assets/admin/css/vendors/select2.css') }}">
@endsection
@section('app')
<div class="container-fluid">

View File

@ -25,7 +25,8 @@
</li>
<li class="profile-nav onhover-dropdown pe-0 py-0">
<div class="media profile-media">
<img src="{{ asset('assets/admin/images/avatar/speed.jpeg') }}" alt="image">
<img src="{{ Storage::url('users/' . Auth::user()->attachment->formatted_attachment) }}"
alt="image">
<div class="media-body">
<span>{{ Auth::user()->biography->full_name }}</span>
<p class="mb-0">{{ Auth::user()->role->name }} <i class="middle fa fa-angle-down"></i></p>
@ -33,7 +34,7 @@
</div>
<ul class="profile-dropdown onhover-show-div">
<li>
<a href="sign-up.html">
<a href="{{ route('setting.account') }}">
<i data-feather="user"></i>
<span>Profil </span></a>
</li>

View File

@ -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');
});
});
});