app settings
This commit is contained in:
+584
-110
@@ -1,191 +1,665 @@
|
||||
<?php
|
||||
<?php
|
||||
session_start();
|
||||
require '../config.php';
|
||||
require '../include_header.php';
|
||||
|
||||
?>
|
||||
|
||||
<body>
|
||||
<?php require '../include_topbar.php';?>
|
||||
<?php require '../include_setting_sidebar.php';?>
|
||||
<?php require '../include_topbar.php'; ?>
|
||||
<?php require '../include_setting_sidebar.php'; ?>
|
||||
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<main id="content" class="content py-15">
|
||||
<div class="container-fluid">
|
||||
|
||||
<!-- Page header -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 gap-3">
|
||||
<div class="">
|
||||
<h1 class="fs-3 mb-1">Manage Profile</h1>
|
||||
<p class="mb-0">Update profile detail</p>
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-6 gap-3">
|
||||
<div>
|
||||
<h1 class="fs-3 mb-1">Profile</h1>
|
||||
<p class="mb-0">Manage your account details</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" id="userForm">
|
||||
<div class="col-12">
|
||||
<div class="row g-5">
|
||||
|
||||
<!-- LEFT: Avatar card -->
|
||||
<div class="col-lg-3 col-12">
|
||||
<div class="card">
|
||||
<div class="card-body p-6 text-center">
|
||||
<div class="position-relative d-inline-block mb-4">
|
||||
<img id="avatar_preview"
|
||||
src="<?php echo $server_url?>assets/images/avatar-1.jpg"
|
||||
alt="Profile picture"
|
||||
class="rounded-circle border"
|
||||
style="width:96px;height:96px;object-fit:cover;">
|
||||
<label for="profile_picture"
|
||||
class="position-absolute bottom-0 end-0 btn btn-sm btn-primary rounded-circle p-0 d-flex align-items-center justify-content-center"
|
||||
style="width:26px;height:26px;cursor:pointer;" title="Change photo">
|
||||
<i class="ti ti-camera" style="font-size:12px;line-height:1;"></i>
|
||||
</label>
|
||||
<input type="file" id="profile_picture" accept="image/*" class="d-none">
|
||||
</div>
|
||||
<h5 class="mb-0" id="display_name">—</h5>
|
||||
<p class="text-muted small mb-0" id="display_username">—</p>
|
||||
<p class="text-muted small mt-1 mb-0" id="display_email">—</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT -->
|
||||
<div class="col-lg-9 col-12">
|
||||
|
||||
<!-- Personal information -->
|
||||
<div class="card mb-5" id="profileForm">
|
||||
<div class="card-body p-6">
|
||||
<h2 class="fs-5 mb-4">Personal Information</h2>
|
||||
|
||||
<!-- Row 1: username / email -->
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" placeholder="Enter username" required>
|
||||
<label for="name" class="form-label">First Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="name" placeholder="First name" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input type="email" class="form-control" id="email" placeholder="Enter email" required>
|
||||
<label for="surname" class="form-label">Last Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="surname" placeholder="Last name" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 2: password / phone -->
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" placeholder="Enter password" required>
|
||||
<label for="email" class="form-label">Email <span class="text-danger">*</span></label>
|
||||
<input type="email" class="form-control" id="email" placeholder="your@email.com" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="phone" class="form-label">Phone</label>
|
||||
<input type="text" class="form-control" id="phone" placeholder="Enter phone number">
|
||||
<input type="text" class="form-control" id="phone" placeholder="e.g. 0812345678">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 3: name / surname -->
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="name" class="form-label">Name</label>
|
||||
<input type="text" class="form-control" id="name" placeholder="Enter first name" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="surname" class="form-label">Surname</label>
|
||||
<input type="text" class="form-control" id="surname" placeholder="Enter last name" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 4: country / language -->
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="country" class="form-label">Country</label>
|
||||
<input type="text" class="form-control" id="country" placeholder="Enter country">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="language" class="form-label">Language</label>
|
||||
<select class="form-select" id="language">
|
||||
<option value="th" selected>Thai (th)</option>
|
||||
<option value="en">English (en)</option>
|
||||
<select class="form-select" id="country">
|
||||
<option value="">— Select country —</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username_display" disabled>
|
||||
<div class="form-text">Username cannot be changed</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- default_company / profile_picture -->
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="default_company" class="form-label">Default Company ID</label>
|
||||
<input type="number" class="form-control" id="default_company" placeholder="0" value="0">
|
||||
<div class="col-12 mb-3">
|
||||
<label for="address" class="form-label">Address</label>
|
||||
<textarea class="form-control" id="address" rows="2" placeholder="Enter your address"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- address -->
|
||||
<div class="mb-3">
|
||||
<label for="address" class="form-label">Address</label>
|
||||
<input type="text" class="form-control" id="address" placeholder="Enter address">
|
||||
</div>
|
||||
|
||||
<!-- facebook / line_code -->
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="facebook" class="form-label">Facebook</label>
|
||||
<input type="text" class="form-control" id="facebook" placeholder="Enter Facebook profile">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="line_code" class="form-label">Line Code</label>
|
||||
<input type="text" class="form-control" id="line_code" placeholder="Enter Line ID">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- signature -->
|
||||
<div class="mb-3">
|
||||
<label for="signature" class="form-label">Signature</label>
|
||||
<input type="text" class="form-control" id="signature" placeholder="Enter signature">
|
||||
</div>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary" onclick="manage_user();">Create</button>
|
||||
<div class="d-flex gap-2 mt-2">
|
||||
<button class="btn btn-primary" onclick="save_profile();">
|
||||
<i class="ti ti-device-floppy me-1"></i>Save Changes
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Change password -->
|
||||
<div class="card" id="passwordForm">
|
||||
<div class="card-body p-6">
|
||||
<h2 class="fs-5 mb-1">Change Password</h2>
|
||||
<p class="text-muted small mb-4">Minimum required strength: <strong>Strong (3/4)</strong></p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="current_password" class="form-label">
|
||||
Current Password <span class="text-danger">*</span>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="password" class="form-control" id="current_password"
|
||||
placeholder="Enter current password" oninput="update_pw_button()">
|
||||
<button class="btn btn-outline-secondary toggle-pw" type="button" data-target="current_password">
|
||||
<i class="ti ti-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text">
|
||||
<a href="javascript:;" class="link-warning" onclick="open_forgot_modal();">
|
||||
<i class="ti ti-help-circle me-1"></i>Forgot your current password?
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="new_password" class="form-label">
|
||||
New Password <span class="text-danger">*</span>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="password" class="form-control" id="new_password"
|
||||
placeholder="Enter new password" oninput="on_new_password_input(this.value)">
|
||||
<button class="btn btn-outline-secondary toggle-pw" type="button" data-target="new_password">
|
||||
<i class="ti ti-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div class="progress" style="height:5px;">
|
||||
<div id="pw_strength_bar" class="progress-bar"
|
||||
style="width:0%;transition:width .25s,background-color .25s;border-radius:4px;"></div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-start mt-1 gap-2">
|
||||
<small id="pw_strength_label" class="fw-semibold" style="white-space:nowrap;">—</small>
|
||||
<small id="pw_feedback" class="text-muted text-end"></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="confirm_password" class="form-label">
|
||||
Confirm Password <span class="text-danger">*</span>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="password" class="form-control" id="confirm_password"
|
||||
placeholder="Repeat new password" oninput="check_confirm_match()">
|
||||
<button class="btn btn-outline-secondary toggle-pw" type="button" data-target="confirm_password">
|
||||
<i class="ti ti-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
<small id="pw_match_label" class="mt-1 d-block"></small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2 mt-2">
|
||||
<button class="btn btn-warning" id="btn_change_password"
|
||||
onclick="change_password();" disabled>
|
||||
<i class="ti ti-key me-1"></i>Update Password
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /RIGHT -->
|
||||
</div><!-- /row -->
|
||||
|
||||
</div>
|
||||
<?php require '../include_ending.php'; ?>
|
||||
</main>
|
||||
|
||||
<?php require "../include_ending.php";?>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════
|
||||
FORGOT PASSWORD MODAL
|
||||
Step 1 → confirm send OTP
|
||||
Step 2 → enter OTP + new password
|
||||
════════════════════════════════════════════════════ -->
|
||||
<div class="modal fade" id="forgotModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title">
|
||||
<i class="ti ti-lock-open me-2 text-warning"></i>Reset Password
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
||||
onclick="reset_forgot_modal();"></button>
|
||||
</div>
|
||||
|
||||
<!-- Step 1 -->
|
||||
<div id="forgot_step1">
|
||||
<div class="modal-body pt-3">
|
||||
<p class="text-muted small mb-3">
|
||||
An OTP will be sent to your registered email address.
|
||||
</p>
|
||||
<div class="alert alert-secondary py-2 px-3 small mb-0">
|
||||
<i class="ti ti-mail me-1"></i>
|
||||
<span id="modal_masked_email">—</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0 pt-0">
|
||||
<button type="button" class="btn btn-secondary btn-sm"
|
||||
data-bs-dismiss="modal" onclick="reset_forgot_modal();">Cancel</button>
|
||||
<button type="button" class="btn btn-warning btn-sm" id="btn_send_otp"
|
||||
onclick="send_reset_otp();">
|
||||
<i class="ti ti-send me-1"></i>Send OTP
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 2 -->
|
||||
<div id="forgot_step2" class="d-none">
|
||||
<div class="modal-body pt-3">
|
||||
|
||||
<p class="text-muted small mb-3">
|
||||
OTP sent to <strong id="modal_masked_email2"></strong> —
|
||||
Reference: <strong id="modal_reference"></strong>
|
||||
</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">OTP <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="reset_otp"
|
||||
placeholder="6-digit OTP" maxlength="6"
|
||||
oninput="this.value=this.value.replace(/\D/g,''); update_reset_button();">
|
||||
<div class="form-text">
|
||||
Valid for 5 minutes.
|
||||
<a href="javascript:;" class="link-primary" onclick="send_reset_otp();">Resend OTP</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">New Password <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<input type="password" class="form-control" id="reset_new_password"
|
||||
placeholder="Enter new password"
|
||||
oninput="on_reset_password_input(this.value)">
|
||||
<button class="btn btn-outline-secondary toggle-pw" type="button"
|
||||
data-target="reset_new_password">
|
||||
<i class="ti ti-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div class="progress" style="height:5px;">
|
||||
<div id="reset_strength_bar" class="progress-bar"
|
||||
style="width:0%;transition:width .25s,background-color .25s;border-radius:4px;"></div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mt-1 gap-2">
|
||||
<small id="reset_strength_label" class="fw-semibold" style="white-space:nowrap;">—</small>
|
||||
<small id="reset_feedback" class="text-muted text-end"></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-0">
|
||||
<label class="form-label">Confirm Password <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<input type="password" class="form-control" id="reset_confirm_password"
|
||||
placeholder="Repeat new password"
|
||||
oninput="check_reset_confirm_match()">
|
||||
<button class="btn btn-outline-secondary toggle-pw" type="button"
|
||||
data-target="reset_confirm_password">
|
||||
<i class="ti ti-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
<small id="reset_match_label" class="mt-1 d-block"></small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer border-0 pt-0">
|
||||
<button type="button" class="btn btn-secondary btn-sm"
|
||||
data-bs-dismiss="modal" onclick="reset_forgot_modal();">Cancel</button>
|
||||
<button type="button" class="btn btn-primary btn-sm"
|
||||
id="btn_reset_confirm" onclick="confirm_reset_password();" disabled>
|
||||
<i class="ti ti-lock-check me-1"></i>Reset Password
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
function manage_category() {
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Shared strength config
|
||||
// ═══════════════════════════════════════════════════════
|
||||
const STRENGTH_LEVELS = [
|
||||
{ label: 'Very weak', color: '#dc3545', pct: 20 },
|
||||
{ label: 'Weak', color: '#fd7e14', pct: 40 },
|
||||
{ label: 'Fair', color: '#ffc107', pct: 60 },
|
||||
{ label: 'Strong', color: '#198754', pct: 80 },
|
||||
{ label: 'Very strong', color: '#0d6efd', pct: 100 },
|
||||
];
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>inventory/api/engine/manage_category.php",
|
||||
autoPrepare: true,
|
||||
checkRequired: 1,
|
||||
debugMode: 0,
|
||||
action: 'manage',
|
||||
onSuccess: function(res) {
|
||||
|
||||
window.location.href = "<?php echo $server_url?>inventory/product.php";
|
||||
|
||||
}
|
||||
});
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// State
|
||||
// ═══════════════════════════════════════════════════════
|
||||
var pw_score = -1;
|
||||
var pw_debounce = null;
|
||||
var pw_check_xhr = null;
|
||||
var reset_pw_score = -1;
|
||||
var reset_pw_debounce = null;
|
||||
var reset_pw_check_xhr = null;
|
||||
var user_email = ''; // stored on retrieve, used for modal masking
|
||||
|
||||
function mask_email(email) {
|
||||
const at = email.indexOf('@');
|
||||
if (at < 0) return email;
|
||||
return email.substring(0, 2)
|
||||
+ '*'.repeat(Math.max(1, at - 2))
|
||||
+ email.substring(at);
|
||||
}
|
||||
|
||||
|
||||
function retrieve_for_edit() {
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// On load
|
||||
// ═══════════════════════════════════════════════════════
|
||||
$(async function () {
|
||||
try {
|
||||
await load_countries();
|
||||
await retrieve_profile();
|
||||
} catch(e) { console.error(e); }
|
||||
});
|
||||
|
||||
<?php if(empty($_GET["id"])){ ?>
|
||||
return false;
|
||||
<?php } ?>
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// COUNTRIES — load from ISO 3166 CDN
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function load_countries() {
|
||||
return $.getJSON(
|
||||
'https://cdn.jsdelivr.net/npm/world_countries_lists@latest/data/countries/en/countries.json'
|
||||
).then(function(data) {
|
||||
const $sel = $('#country');
|
||||
// Thailand first for convenience, then alphabetical
|
||||
const sorted = data.slice().sort((a, b) => a.name.localeCompare(b.name));
|
||||
const th = sorted.findIndex(c => c.alpha2 === 'th');
|
||||
if (th > -1) {
|
||||
const [thailand] = sorted.splice(th, 1);
|
||||
sorted.unshift(thailand);
|
||||
}
|
||||
sorted.forEach(function(c) {
|
||||
$sel.append(new Option(c.name, c.name));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// PROFILE — retrieve & save
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function retrieve_profile() {
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>inventory/api/engine/retrieve_category.php",
|
||||
url: '<?php echo $server_url?>setting/api/engine/retrieve_profile.php',
|
||||
autoPrepare: true,
|
||||
checkRequired: 0,
|
||||
action: 'read',
|
||||
onSuccess: function(res) {
|
||||
|
||||
$.each(res.output, function(key, item) {
|
||||
$(`#${key}`).val(item);
|
||||
});
|
||||
|
||||
$(`button[type=submit]`).text('Update');
|
||||
|
||||
$(`button[type=reset]`).hide();
|
||||
|
||||
const u = res.output;
|
||||
$('#name').val(u.name);
|
||||
$('#surname').val(u.surname);
|
||||
$('#email').val(u.email);
|
||||
$('#phone').val(u.phone);
|
||||
$('#username_display').val(u.username);
|
||||
$('#country').val(u.country); // select already populated by load_countries()
|
||||
$('#address').val(u.address);
|
||||
if (u.profile_picture) {
|
||||
$('#avatar_preview').attr('src',
|
||||
'<?php echo $server_url?>uploads/profile/' + u.profile_picture);
|
||||
}
|
||||
user_email = u.email;
|
||||
refresh_display_panel(u.name, u.surname, u.username, u.email);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function save_profile() {
|
||||
const fd = new FormData();
|
||||
const file = $('#profile_picture')[0].files[0];
|
||||
if (file) fd.append('profile_picture', file);
|
||||
|
||||
return ajax_request({
|
||||
url: '<?php echo $server_url?>setting/api/engine/manage_profile.php',
|
||||
autoPrepare: true,
|
||||
checkRequired: 1,
|
||||
action: 'update',
|
||||
formData: fd,
|
||||
onSuccess: function(res) {
|
||||
if (res.profile_picture) {
|
||||
$('#avatar_preview').attr('src',
|
||||
'<?php echo $server_url?>uploads/profile/' + res.profile_picture);
|
||||
}
|
||||
refresh_display_panel(
|
||||
$('#name').val(), $('#surname').val(),
|
||||
$('#username_display').val(), $('#email').val()
|
||||
);
|
||||
bootbox.alert('Profile updated successfully.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function refresh_display_panel(name, surname, username, email) {
|
||||
$('#display_name').text((name + ' ' + surname).trim() || '—');
|
||||
$('#display_username').text('@' + (username || '—'));
|
||||
$('#display_email').text(email || '—');
|
||||
}
|
||||
|
||||
// Live update display panel while typing
|
||||
$('#name, #surname').on('input', function() {
|
||||
$('#display_name').text(($('#name').val() + ' ' + $('#surname').val()).trim() || '—');
|
||||
});
|
||||
$('#email').on('input', function() {
|
||||
$('#display_email').text($(this).val() || '—');
|
||||
});
|
||||
|
||||
// Avatar preview
|
||||
$('#profile_picture').on('change', function() {
|
||||
const file = this.files[0];
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => $('#avatar_preview').attr('src', e.target.result);
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// CHANGE PASSWORD
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function on_new_password_input(pw) {
|
||||
clearTimeout(pw_debounce);
|
||||
if (!pw) { reset_strength_ui('pw'); update_pw_button(); return; }
|
||||
pw_debounce = setTimeout(() => check_strength(pw, 'pw'), 350);
|
||||
}
|
||||
|
||||
function check_confirm_match() {
|
||||
const np = $('#new_password').val();
|
||||
const cp = $('#confirm_password').val();
|
||||
if (!cp) { $('#pw_match_label').text(''); return; }
|
||||
if (np === cp) {
|
||||
$('#pw_match_label').html('<span style="color:#198754;">✓ Passwords match</span>');
|
||||
} else {
|
||||
$('#pw_match_label').html('<span style="color:#dc3545;">✗ Passwords do not match</span>');
|
||||
}
|
||||
update_pw_button();
|
||||
}
|
||||
|
||||
function update_pw_button() {
|
||||
const cur = $('#current_password').val();
|
||||
const np = $('#new_password').val();
|
||||
const cp = $('#confirm_password').val();
|
||||
const ready = cur && np && cp && (np === cp) && pw_score >= 3;
|
||||
$('#btn_change_password').prop('disabled', !ready);
|
||||
}
|
||||
|
||||
function change_password() {
|
||||
return ajax_request({
|
||||
url: '<?php echo $server_url?>setting/api/engine/change_password.php',
|
||||
autoPrepare: false,
|
||||
data: { json: JSON.stringify({
|
||||
otp: document.getElementById('session-context').dataset.otp,
|
||||
company_id: document.getElementById('session-context').dataset.companyId,
|
||||
action: 'update',
|
||||
current_password: $('#current_password').val(),
|
||||
new_password: $('#new_password').val(),
|
||||
confirm_password: $('#confirm_password').val(),
|
||||
})},
|
||||
onSuccess: function() {
|
||||
bootbox.alert(
|
||||
'Password changed successfully. You will be logged out now.',
|
||||
function() { window.location.href = '<?php echo $server_url?>index.php'; }
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// FORGOT PASSWORD MODAL
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function open_forgot_modal() {
|
||||
reset_forgot_modal();
|
||||
$('#modal_masked_email').text(user_email ? mask_email(user_email) : '—');
|
||||
$('#forgotModal').modal('show');
|
||||
}
|
||||
|
||||
$(async function() {
|
||||
try {
|
||||
await retrieve_for_edit();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
function reset_forgot_modal() {
|
||||
// Reset steps
|
||||
$('#forgot_step1').removeClass('d-none');
|
||||
$('#forgot_step2').addClass('d-none');
|
||||
// Clear fields
|
||||
$('#reset_otp').val('');
|
||||
$('#reset_new_password').val('');
|
||||
$('#reset_confirm_password').val('');
|
||||
$('#reset_match_label').text('');
|
||||
reset_strength_ui('reset');
|
||||
update_reset_button();
|
||||
}
|
||||
|
||||
function send_reset_otp() {
|
||||
$('#btn_send_otp').prop('disabled', true);
|
||||
|
||||
return ajax_request({
|
||||
url: '<?php echo $server_url?>setting/api/engine/request_reset_otp.php',
|
||||
autoPrepare: false,
|
||||
noLoading: false,
|
||||
data: { json: JSON.stringify({
|
||||
otp: document.getElementById('session-context').dataset.otp,
|
||||
company_id: document.getElementById('session-context').dataset.companyId,
|
||||
action: 'read',
|
||||
})},
|
||||
onSuccess: function(res) {
|
||||
// Move to step 2
|
||||
$('#forgot_step1').addClass('d-none');
|
||||
$('#forgot_step2').removeClass('d-none');
|
||||
$('#modal_masked_email2').text(res.masked_email);
|
||||
$('#modal_reference').text(res.reference);
|
||||
}
|
||||
}).catch(function() {
|
||||
$('#btn_send_otp').prop('disabled', false);
|
||||
});
|
||||
}
|
||||
|
||||
function on_reset_password_input(pw) {
|
||||
clearTimeout(reset_pw_debounce);
|
||||
if (!pw) { reset_strength_ui('reset'); update_reset_button(); return; }
|
||||
reset_pw_debounce = setTimeout(() => check_strength(pw, 'reset'), 350);
|
||||
}
|
||||
|
||||
function check_reset_confirm_match() {
|
||||
const np = $('#reset_new_password').val();
|
||||
const cp = $('#reset_confirm_password').val();
|
||||
if (!cp) { $('#reset_match_label').text(''); return; }
|
||||
if (np === cp) {
|
||||
$('#reset_match_label').html('<span style="color:#198754;">✓ Passwords match</span>');
|
||||
} else {
|
||||
$('#reset_match_label').html('<span style="color:#dc3545;">✗ Passwords do not match</span>');
|
||||
}
|
||||
update_reset_button();
|
||||
}
|
||||
|
||||
function update_reset_button() {
|
||||
const otp = $('#reset_otp').val();
|
||||
const np = $('#reset_new_password').val();
|
||||
const cp = $('#reset_confirm_password').val();
|
||||
const ready = otp.length === 6 && np && cp && (np === cp) && reset_pw_score >= 3;
|
||||
$('#btn_reset_confirm').prop('disabled', !ready);
|
||||
}
|
||||
|
||||
function confirm_reset_password() {
|
||||
return ajax_request({
|
||||
url: '<?php echo $server_url?>setting/api/engine/reset_password_otp.php',
|
||||
autoPrepare: false,
|
||||
data: { json: JSON.stringify({
|
||||
session_otp: document.getElementById('session-context').dataset.otp,
|
||||
company_id: document.getElementById('session-context').dataset.companyId,
|
||||
action: 'update',
|
||||
otp: $('#reset_otp').val(),
|
||||
new_password: $('#reset_new_password').val(),
|
||||
confirm_password: $('#reset_confirm_password').val(),
|
||||
})},
|
||||
onSuccess: function() {
|
||||
$('#forgotModal').modal('hide');
|
||||
bootbox.alert(
|
||||
'Password reset successfully. You will be logged out now.',
|
||||
function() { window.location.href = '<?php echo $server_url?>index.php'; }
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// SHARED: strength check (used by both change & reset)
|
||||
// prefix = 'pw' | 'reset'
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function check_strength(pw, prefix) {
|
||||
// Abort pending request
|
||||
if (prefix === 'pw' && pw_check_xhr) pw_check_xhr.abort();
|
||||
if (prefix === 'reset' && reset_pw_check_xhr) reset_pw_check_xhr.abort();
|
||||
|
||||
const xhr = $.ajax({
|
||||
url: '<?php echo $server_url?>setting/api/engine/check_password.php',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: { json: JSON.stringify({
|
||||
otp: document.getElementById('session-context').dataset.otp,
|
||||
company_id: document.getElementById('session-context').dataset.companyId,
|
||||
action: 'read',
|
||||
password: pw,
|
||||
})},
|
||||
headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') },
|
||||
success: function(res) {
|
||||
const score = res.score ?? -1;
|
||||
|
||||
if (prefix === 'pw') pw_score = score;
|
||||
if (prefix === 'reset') reset_pw_score = score;
|
||||
|
||||
const bar = prefix === 'pw' ? '#pw_strength_bar' : '#reset_strength_bar';
|
||||
const label = prefix === 'pw' ? '#pw_strength_label' : '#reset_strength_label';
|
||||
const feedback = prefix === 'pw' ? '#pw_feedback' : '#reset_feedback';
|
||||
|
||||
if (score < 0) { reset_strength_ui(prefix); return; }
|
||||
|
||||
const lvl = STRENGTH_LEVELS[score];
|
||||
$(bar).css({ width: lvl.pct + '%', backgroundColor: lvl.color });
|
||||
$(label).text(lvl.label).css('color', lvl.color);
|
||||
$(feedback).text(res.feedback || '');
|
||||
|
||||
if (prefix === 'pw') { check_confirm_match(); update_pw_button(); }
|
||||
if (prefix === 'reset') { check_reset_confirm_match(); update_reset_button(); }
|
||||
}
|
||||
});
|
||||
|
||||
if (prefix === 'pw') pw_check_xhr = xhr;
|
||||
if (prefix === 'reset') reset_pw_check_xhr = xhr;
|
||||
}
|
||||
|
||||
function reset_strength_ui(prefix) {
|
||||
if (prefix === 'pw') pw_score = -1;
|
||||
if (prefix === 'reset') reset_pw_score = -1;
|
||||
|
||||
const bar = prefix === 'pw' ? '#pw_strength_bar' : '#reset_strength_bar';
|
||||
const label = prefix === 'pw' ? '#pw_strength_label' : '#reset_strength_label';
|
||||
const feedback = prefix === 'pw' ? '#pw_feedback' : '#reset_feedback';
|
||||
|
||||
$(bar).css({ width: '0%', backgroundColor: '' });
|
||||
$(label).text('—').css('color', '');
|
||||
$(feedback).text('');
|
||||
}
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Shared: toggle show/hide password
|
||||
// ═══════════════════════════════════════════════════════
|
||||
$(document).on('click', '.toggle-pw', function() {
|
||||
const $input = $('#' + $(this).data('target'));
|
||||
const isText = $input.attr('type') === 'text';
|
||||
$input.attr('type', isText ? 'password' : 'text');
|
||||
$(this).find('i').toggleClass('ti-eye ti-eye-off');
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user