Assets from purchase invoice lines, straight-line depreciation/amortization with salvage value, disposals, and [Asset Mgmt] sources in Batch GL Entries. Green third workspace with an app access guard.
275 lines
11 KiB
PHP
275 lines
11 KiB
PHP
<?php
|
|
require '../session.php';
|
|
require '../config.php';
|
|
|
|
// Redirect if already logged in
|
|
if (!empty($_SESSION['login_status'])) {
|
|
require_once '../assets/utils/app_registry.php';
|
|
$login_app = app_default_for_access($app_registry, $_SESSION['login_app_access'] ?? 'wms');
|
|
header('Location: ' . $server_url . app_home_path($app_registry, $login_app));
|
|
exit;
|
|
}
|
|
|
|
// Generate CSRF token for unauthenticated form
|
|
if (empty($_SESSION['csrf_token'])) {
|
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
|
}
|
|
|
|
require '../include_header.php';
|
|
?>
|
|
|
|
<body>
|
|
|
|
<!-- zxcvbn JS: client-side password strength scoring, no server round-trip -->
|
|
<script src="https://cdn.jsdelivr.net/npm/zxcvbn@4.4.2/dist/zxcvbn.js"></script>
|
|
|
|
<div class="container d-flex align-items-center justify-content-center min-vh-100 py-5">
|
|
<div class="card" style="max-width:520px; width:100%;">
|
|
<div class="card-body p-5">
|
|
|
|
<div class="text-center mb-4">
|
|
<a href="<?php echo $server_url?>login/index.php" class="mb-4 d-inline-block">
|
|
<img src="<?php echo $server_url?>assets/images/logo.svg" alt="">
|
|
</a>
|
|
<h1 class="h5 mb-1">Create your account</h1>
|
|
<p class="text-muted small mb-0">Fill in the details below to get started</p>
|
|
</div>
|
|
|
|
<!-- Name row -->
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-6">
|
|
<label 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-6">
|
|
<label 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>
|
|
|
|
<!-- Username -->
|
|
<div class="mb-3">
|
|
<label class="form-label">Username <span class="text-danger">*</span></label>
|
|
<div class="input-group">
|
|
<span class="input-group-text"><i class="ti ti-at"></i></span>
|
|
<input type="text" class="form-control" id="username"
|
|
placeholder="Choose a username" required
|
|
oninput="this.value=this.value.toLowerCase().replace(/[^a-z0-9_]/g,'')">
|
|
</div>
|
|
<div class="form-text">Lowercase letters, numbers and underscores only.</div>
|
|
</div>
|
|
|
|
<!-- Email -->
|
|
<div class="mb-3">
|
|
<label class="form-label">Email <span class="text-danger">*</span></label>
|
|
<input type="email" class="form-control" id="email" placeholder="your@email.com" required>
|
|
</div>
|
|
|
|
<!-- Password -->
|
|
<div class="mb-3">
|
|
<label class="form-label">Password <span class="text-danger">*</span></label>
|
|
<div class="input-group">
|
|
<input type="password" class="form-control" id="password"
|
|
placeholder="Choose a strong password"
|
|
oninput="on_password_input(this.value)">
|
|
<button class="btn btn-outline-secondary toggle-pw" type="button" data-target="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 mt-1">
|
|
<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>
|
|
|
|
<!-- Confirm password -->
|
|
<div class="mb-4">
|
|
<label 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 your 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>
|
|
|
|
<button class="btn btn-primary w-100 mb-3" id="btn_register" onclick="register()">
|
|
<i class="ti ti-user-plus me-1"></i>Create Account
|
|
</button>
|
|
|
|
<!-- Success state (hidden until submit) -->
|
|
<div id="success_panel" class="d-none text-center py-3">
|
|
<div class="mb-3">
|
|
<span class="d-inline-flex align-items-center justify-content-center rounded-circle bg-label-success"
|
|
style="width:56px;height:56px;">
|
|
<i class="ti ti-mail-check fs-3 text-success"></i>
|
|
</span>
|
|
</div>
|
|
<h6 class="mb-1">Check your inbox!</h6>
|
|
<p class="text-muted small mb-0">We've sent a verification link to your email address. Click it to activate your account.</p>
|
|
</div>
|
|
|
|
<!-- Fallback: email failed, show verify link directly -->
|
|
<div id="verify_link_panel" class="d-none py-3">
|
|
<div class="alert alert-warning small mb-3">
|
|
<i class="ti ti-alert-triangle me-1"></i>
|
|
Account created, but the verification email could not be sent (server email is not configured).
|
|
Use the link below to verify your account:
|
|
</div>
|
|
<div class="input-group">
|
|
<input type="text" class="form-control form-control-sm font-monospace" id="verify_link_input" readonly>
|
|
<button class="btn btn-sm btn-outline-secondary" onclick="copy_verify_link()">
|
|
<i class="ti ti-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="text-center text-muted small mb-0">
|
|
Already have an account?
|
|
<a href="<?php echo $server_url?>login/index.php" class="link-primary">Sign in</a>
|
|
</p>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<script>
|
|
// ═══════════════════════════════════════════════
|
|
// State
|
|
// ═══════════════════════════════════════════════
|
|
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 },
|
|
];
|
|
|
|
var pw_score = -1;
|
|
|
|
|
|
// ═══════════════════════════════════════════════
|
|
// On load
|
|
// ═══════════════════════════════════════════════
|
|
$(function () {
|
|
$(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');
|
|
});
|
|
});
|
|
|
|
|
|
// ═══════════════════════════════════════════════
|
|
// Password strength (JS zxcvbn — no server round-trip)
|
|
// ═══════════════════════════════════════════════
|
|
function on_password_input(pw) {
|
|
if (!pw) { reset_strength_ui(); check_confirm_match(); return; }
|
|
check_strength(pw);
|
|
}
|
|
|
|
function check_strength(pw) {
|
|
const user_inputs = [
|
|
$('#name').val(), $('#surname').val(),
|
|
$('#username').val(), $('#email').val()
|
|
].filter(Boolean);
|
|
|
|
const result = zxcvbn(pw, user_inputs);
|
|
pw_score = result.score;
|
|
|
|
const lvl = STRENGTH_LEVELS[pw_score];
|
|
$('#pw_strength_bar').css({ width: lvl.pct + '%', backgroundColor: lvl.color });
|
|
$('#pw_strength_label').text(lvl.label).css('color', lvl.color);
|
|
$('#pw_feedback').text(result.feedback.warning || result.feedback.suggestions[0] || '');
|
|
check_confirm_match();
|
|
}
|
|
|
|
function reset_strength_ui() {
|
|
pw_score = -1;
|
|
$('#pw_strength_bar').css({ width: '0%', backgroundColor: '' });
|
|
$('#pw_strength_label').text('—').css('color', '');
|
|
$('#pw_feedback').text('');
|
|
}
|
|
|
|
function check_confirm_match() {
|
|
const np = $('#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>');
|
|
}
|
|
}
|
|
|
|
|
|
// ═══════════════════════════════════════════════
|
|
// Register
|
|
// ═══════════════════════════════════════════════
|
|
function register() {
|
|
const name = $('#name').val().trim();
|
|
const surname = $('#surname').val().trim();
|
|
const username = $('#username').val().trim();
|
|
const email = $('#email').val().trim();
|
|
const password = $('#password').val();
|
|
const confirm = $('#confirm_password').val();
|
|
|
|
if (!name || !surname || !username || !email || !password || !confirm) {
|
|
bootbox.alert('Please fill in all required fields.');
|
|
return;
|
|
}
|
|
if (password !== confirm) {
|
|
bootbox.alert('Passwords do not match.');
|
|
return;
|
|
}
|
|
if (pw_score < 3) {
|
|
bootbox.alert('Please choose a stronger password.');
|
|
return;
|
|
}
|
|
|
|
ajax_request({
|
|
url: '<?php echo $server_url?>login/api/engine/register.php',
|
|
autoPrepare: false,
|
|
data: { json: JSON.stringify({
|
|
otp: '',
|
|
company_id: 0,
|
|
action: 'create',
|
|
name: name,
|
|
surname: surname,
|
|
username: username,
|
|
email: email,
|
|
password: password,
|
|
confirm_password: confirm,
|
|
})},
|
|
onSuccess: function (r) {
|
|
$('input, button, .row, .mb-3, .mb-4').hide();
|
|
if (r.verify_url) {
|
|
$('#verify_link_input').val(r.verify_url);
|
|
$('#verify_link_panel').removeClass('d-none');
|
|
} else {
|
|
$('#success_panel').removeClass('d-none');
|
|
}
|
|
},
|
|
});
|
|
}
|
|
function copy_verify_link() {
|
|
const val = $('#verify_link_input').val();
|
|
navigator.clipboard.writeText(val).then(function () {
|
|
bootbox.alert('Verification link copied to clipboard.');
|
|
});
|
|
}
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|