feat: add resources and view components

This commit is contained in:
2026-05-21 16:05:19 +07:00
parent 28a06315b8
commit b2d60e680d
249 changed files with 37379 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
<x-app-layout>
<div class="container-fluid mt-2" id="main-content">
<div class="row gx-3 gx-lg-4">
<div class="col-12 col-lg-4">
{{-- Foto + Summary --}}
<div class="card adminuiux-card border-0 overflow-hidden mb-3">
<div class="text-center position-relative p-3">
{{-- Foto avatar utama --}}
<div class="avatar avatar-120 rounded-circle border border-3 border-white shadow mt-n5">
<img src="assets/img/profile.png" class="rounded-circle">
</div>
{{-- Nama & role user --}}
<h5 class="mt-3 mb-0 fw-bold">{{ Auth::user()->name ?? 'Mobileuxer' }}</h5>
<p class="text-secondary small">Admin</p>
{{-- Informasi user --}}
<div class="text-start mt-3 small">
<p><i class="bi bi-envelope me-2"></i> {{ Auth::user()->email }}</p>
<p><i class="bi bi-telephone me-2"></i> {{ Auth::user()->phone ?? 'N/A' }}</p>
<p><i class="bi bi-cake me-2"></i> {{ Auth::user()->date_of_birth ?? 'N/A' }}</p>
<p><i class="bi bi-geo-alt me-2"></i> {{ Auth::user()->city ?? 'N/A' }}</p>
</div>
</div>
</div>
{{-- Delete Account --}}
@include('profile.partials.delete-user-form')
</div>
<div class="col-12 col-lg-8">
{{-- Profile Information --}}
@include('profile.partials.update-profile-information-form')
{{-- Update Password --}}
@include('profile.partials.update-password-form')
{{-- Passkey Registration (WebAuthn) --}}
@if(get_setting('webauthn_enabled', false))
@include('profile.partials.passkey-registration-form')
@endif
</div>
</div>
</div>
</x-app-layout>
@@ -0,0 +1,71 @@
<div class="card adminuiux-card border-danger shadow-sm mt-4">
<div class="card-body">
{{-- header and description --}}
<h4 class="fw-bold text-danger mb-2">Delete Account</h4>
<p class="text-secondary small mb-3">
Once deleted, all your data will be permanently removed. Download any information you want to keep.
</p>
{{-- trigger button for sweetalert confirmation --}}
<button id="deleteAccountBtn" class="btn btn-danger mt-3 px-3">
Delete Account
</button>
{{-- delete account form (submitted after sweetalert confirmation) --}}
<form id="deleteAccountForm" method="post" action="{{ route('profile.destroy') }}" style="display:none;">
@csrf
@method('delete')
<input type="hidden" name="password" id="passwordInput" required>
</form>
</div>
</div>
{{-- sweetalert2 cdn --}}
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11" crossorigin="anonymous"></script>
<script>
document.getElementById('deleteAccountBtn').addEventListener('click', function(e) {
e.preventDefault();
// show confirmation dialog
StandardSwal.fire({
title: 'Delete Your Account?',
text: "This action is irreversible. All your personal data and resources will be permanently wiped. Please enter your password to authorize this deletion.",
icon: 'warning',
input: 'password',
inputPlaceholder: 'Confirm your password',
showCancelButton: true,
confirmButtonText: 'Permanently Delete',
customClass: {
confirmButton: 'btn-pill-danger',
cancelButton: 'btn-pill-cancel'
},
reverseButtons: false,
preConfirm: (password) => {
if (!password)
StandardSwal.showValidationMessage('Authentication password is required');
return password;
}
})
.then((result) => {
// if confirmed, submit form
if (result.isConfirmed) {
document.getElementById('passwordInput').value = result.value;
document.getElementById('deleteAccountForm').submit();
}
});
});
</script>
{{-- sweetalert error if password is incorrect (server validation) --}}
@if ($errors->userDeletion->has('password'))
<script>
StandardSwal.fire({
icon: 'error',
title: 'Verification Failed!',
text: '{{ $errors->userDeletion->first("password") }}' || 'The password you provided is incorrect.',
confirmButtonText: 'Try Again'
});
</script>
@endif
@@ -0,0 +1,121 @@
<section>
<div class="card border-0 shadow-sm mb-3">
<div class="card-body">
<header>
<h4 class="fw-bold mb-3">
<i class="bi bi-fingerprint me-2"></i> {{ __('Passkeys (WebAuthn)') }}
</h6>
<p class="text-secondary small mb-4">
{{ __('Register your biometrics (Fingerprint, FaceID) or security keys to login securely without a password.') }}
</p>
</header>
<div id="passkey-list" class="mb-4">
@if(Auth::user()->webAuthnCredentials->count() > 0)
<div class="list-group list-group-flush border-top border-bottom mb-3">
@foreach(Auth::user()->webAuthnCredentials as $credential)
<div class="list-group-item d-flex justify-content-between align-items-center py-2 px-0">
<div>
<span class="fw-semibold small d-block">{{ $credential->alias ?: __('Unnamed Device') }}</span>
<small class="text-muted">{{ __('Registered on') }}: {{ $credential->created_at->format('d M Y') }}</small>
</div>
<form method="POST" action="{{ route('webauthn.destroy', $credential->id) }}" onsubmit="return confirm('{{ __('Are you sure you want to remove this passkey?') }}')">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-outline-danger border-0">
<i class="bi bi-trash"></i>
</button>
</form>
</div>
@endforeach
</div>
@else
<div class="alert alert-light border small text-center py-3">
{{ __('No passkeys registered yet.') }}
</div>
@endif
</div>
<div class="d-flex align-items-center gap-3">
<button type="button" id="register-passkey" class="btn btn-primary theme-black px-4">
{{ __('Register New Passkey') }}
</button>
<div id="passkey-status" class="small text-info d-none">
<span class="spinner-border spinner-border-sm me-1"></span> {{ __('Processing...') }}
</div>
</div>
</div>
</div>
@if(get_setting('webauthn_enabled', false))
<script src="{{ asset('vendor/webauthn/webauthn.js') }}"></script>
<script>
document.getElementById('register-passkey').addEventListener('click', async () => {
const btn = document.getElementById('register-passkey');
const status = document.getElementById('passkey-status');
btn.disabled = true;
status.classList.remove('d-none');
if (typeof WebAuthn === 'undefined') {
console.error('WebAuthn library not loaded.');
return;
}
const webauthn = new WebAuthn();
if (WebAuthn.supportsWebAuthn()) {
try {
// Ask for a nickname for the key
const alias = prompt("{{ __('Enter a name for this device/key (e.g. My Laptop, iPhone):') }}", "My Device");
if (alias === null) {
btn.disabled = false;
status.classList.add('d-none');
return;
}
// Laragear WebAuthn v5 registration
const response = await webauthn.register({ alias: alias });
if (response) {
window.location.reload();
}
} catch (error) {
console.error('WebAuthn Error:', error);
let errorMsg = 'Failed to register Passkey. Please use a secure connection.';
if (!window.isSecureContext) {
errorMsg = 'Passkeys require a secure context (HTTPS). \n\n' +
'Developer Tip: If you are on a local domain (like .test), you can bypass this in Chrome/Edge by going to: \n' +
'chrome://flags/#unsafely-treat-insecure-origin-as-secure \n' +
'and adding your domain to the list.';
} else if (error.name === 'NotAllowedError') {
errorMsg = 'Registration was cancelled or timed out.';
} else {
errorMsg = 'Failed to register Passkey. Ensure your device supports biometrics and is configured correctly.';
}
StandardSwal.fire({
icon: 'error',
title: 'Registration Failed!',
text: errorMsg || 'The system was unable to register your Passkey. Please ensure your device is compatible.',
confirmButtonText: 'OK'
});
}
} else {
StandardSwal.fire({
icon: 'info',
title: 'Passkey Not Supported',
text: 'Your current browser or device does not support secure Passkey authentication.',
confirmButtonText: 'Understood'
});
}
btn.disabled = false;
status.classList.add('d-none');
});
</script>
@endif
</section>
@@ -0,0 +1,75 @@
<div class="card adminuiux-card mb-4 shadow-sm">
<div class="card-body">
{{-- header --}}
<h4 class="fw-bold mb-3">Update Password</h4>
<p class="text-secondary small mb-4">Make sure your new password is secure.</p>
{{-- form update password --}}
<form method="POST" action="{{ route('password.update') }}" autocomplete="off" class="ajax-form">
@csrf
@method('PUT')
{{-- anti autofill trap --}}
<input type="text" name="fakeuser" style="display:none">
<input type="password" name="fakepass" style="display:none">
{{-- Current Password --}}
<div class="mb-3">
<label for="update_password_current_password" class="form-label fw-semibold">
{{ __('Current Password') }} <span class="text-danger">*</span>
</label>
<div class="input-group">
<input type="password" id="update_password_current_password" name="current_password"
class="form-control border-end-0" placeholder="Enter current password" required autocomplete="off" minlength="12"
title="Enter your current password">
<button class="btn btn-outline-secondary bg-white border-start-0 password-toggle" type="button" style="border-color: #dee2e6;">
<i class="bi bi-eye text-secondary"></i>
</button>
</div>
</div>
{{-- New Password --}}
<div class="mb-3">
<label for="update_password_password" class="form-label fw-semibold">
{{ __('New Password') }} <span class="text-danger">*</span>
</label>
<div class="input-group">
<input type="password" id="update_password_password" name="password" class="form-control border-end-0"
placeholder="Enter new password" required minlength="12" autocomplete="new-password"
pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{12,}$"
title="Minimum 12 characters with uppercase, lowercase, number, and symbol">
<button class="btn btn-outline-secondary bg-white border-start-0 password-toggle" type="button" style="border-color: #dee2e6;">
<i class="bi bi-eye text-secondary"></i>
</button>
</div>
</div>
{{-- Confirm Password --}}
<div class="mb-3">
<label for="update_password_password_confirmation" class="form-label fw-semibold">
{{ __('Confirm Password') }} <span class="text-danger">*</span>
</label>
<div class="input-group">
<input type="password" id="update_password_password_confirmation" name="password_confirmation"
class="form-control border-end-0" placeholder="Confirm new password" required minlength="12"
autocomplete="new-password" title="Must match the new password exactly">
<button class="btn btn-outline-secondary bg-white border-start-0 password-toggle" type="button" style="border-color: #dee2e6;">
<i class="bi bi-eye text-secondary"></i>
</button>
</div>
</div>
{{-- submit --}}
<div class="d-flex align-items-center gap-3">
<button type="submit" class="btn btn-primary mt-3 px-3">
{{ __('Update Password') }}
</button>
</div>
</form>
</div>
</div>
{{-- redundant script removed --}}
@@ -0,0 +1,57 @@
<div class="card adminuiux-card mb-4 shadow-sm">
<div class="card-body">
{{-- page information header --}}
<h4 class="fw-bold mb-3">Profile Information</h4>
<p class="text-secondary small mb-4">
update your account's profile information and email address
</p>
{{-- form for resending verification email (hidden, triggered in backend) --}}
<form id="send-verification" method="post" action="{{ route('verification.send') }}" style="display:none;">
@csrf
</form>
{{-- update profile form --}}
<form method="POST" action="{{ route('profile.update') }}" autocomplete="off" class="ajax-form">
@csrf
@method('PATCH')
{{-- anti autofill trap --}}
<input type="text" name="fakeuser" style="display:none">
<input type="password" name="fakepass" style="display:none">
<div class="row g-3">
{{-- Name --}}
<div class="col-12 col-md-6">
<label class="form-label fw-semibold">
{{ __('Name') }} <span class="text-danger">*</span>
</label>
<input type="text" name="name" class="form-control" value="{{ old('name', auth()->user()->name) }}"
placeholder="ex: John Wick" required minlength="3" maxlength="100" pattern="^[a-zA-Z\s]+$"
title="Name must be at least 3 characters and contain letters and spaces only">
</div>
{{-- Email --}}
<div class="col-12 col-md-6">
<label class="form-label fw-semibold">
{{ __('Email') }} <span class="text-danger">*</span>
</label>
<input type="email" name="email" class="form-control"
value="{{ old('email', auth()->user()->email) }}" placeholder="john@email.com" required
maxlength="150" title="Enter a valid and unique email address">
</div>
</div>
<button type="submit" class="btn btn-primary mt-3 px-3">
{{ __('Save Changes') }}
</button>
</form>
</div>
</div>
{{-- redundant script removed --}}