use roles guards

This commit is contained in:
Thanakorn S
2026-05-22 08:45:35 +07:00
parent b76dc679af
commit e36d304521
19 changed files with 133 additions and 66 deletions
+49 -37
View File
@@ -1,10 +1,6 @@
<?php
session_start();
require '../config.php';
if (!in_array($_SESSION['login_role'] ?? 'viewer', ['owner', 'admin'], true)) {
http_response_code(403);
exit('Access denied.');
}
require '../include_header.php';
?>
@@ -23,7 +19,7 @@
<h1 class="fs-3 mb-1">Users Access</h1>
<p class="mb-0">Manage team members and their roles in your company</p>
</div>
<div class="d-flex gap-2">
<div class="d-flex gap-2" id="owner_actions" style="display:none!important;">
<button class="btn btn-primary" onclick="open_invite_modal()">
<i class="ti ti-user-plus me-1"></i>Add User
</button>
@@ -68,14 +64,15 @@
<th class="ps-4">User</th>
<th>Email</th>
<th>Role</th>
<th>License</th>
<th>App Access</th>
<th>Joined</th>
<th class="text-end pe-4">Actions</th>
<th class="text-end pe-4 owner-only-col">Actions</th>
</tr>
</thead>
<tbody id="users_tbody">
<tr>
<td colspan="6" class="text-center py-5 text-muted">
<td colspan="7" class="text-center py-5 text-muted">
<i class="ti ti-loader-2 fs-2 d-block mb-2"></i>Loading…
</td>
</tr>
@@ -202,6 +199,7 @@
const avatar_ph = '<?php echo $server_url?>assets/images/logo.svg';
const owner_app_access = '<?php echo htmlspecialchars($_SESSION['login_app_access'] ?? 'wms', ENT_QUOTES); ?>';
const APP_REGISTRY = <?php echo json_encode($app_registry); ?>;
const is_owner = user_role === 'owner';
let _users_data = [];
@@ -209,6 +207,8 @@
// On load
// ═══════════════════════════════════════════════
$(function () {
if (is_owner) $('#owner_actions').show();
else $('.owner-only-col').hide();
load_users();
});
@@ -239,7 +239,7 @@
if (!rows.length) {
tbody.html(`
<tr>
<td colspan="6" class="text-center py-5 text-muted">
<td colspan="${is_owner ? 7 : 6}" class="text-center py-5 text-muted">
<i class="ti ti-users-group fs-2 d-block mb-2"></i>No users found.
</td>
</tr>`);
@@ -252,8 +252,9 @@
: `<span class="avatar-initials rounded-circle d-inline-flex align-items-center justify-content-center bg-light border fw-semibold text-secondary"
style="width:36px;height:36px;font-size:13px;">${initials(u.name, u.surname)}</span>`;
const role_badge = role_html(u.role);
const access_badge = app_access_html(u.app_access);
const role_badge = role_html(u.role);
const license_badge = license_html(u.license);
const access_badge = app_access_html(u.app_access);
const joined = u.created_at
? new Date(u.created_at).toLocaleDateString('en-GB', {day:'2-digit', month:'short', year:'numeric'})
@@ -262,30 +263,30 @@
const is_pending = u.status === 'pending' && u.is_pending_invite == 1;
let actions = '';
if (u.role === 'owner') {
// owner — no actions
} else if (is_pending) {
actions += `
<button class="btn btn-sm btn-ghost-secondary" title="Resend invitation"
onclick="resend_invite(${u.map_id}, '${esc(u.email)}')">
<i class="ti ti-send"></i>
</button>`;
actions += `
<button class="btn btn-sm btn-ghost-danger" title="Cancel invitation"
onclick="remove_user(${u.map_id}, '${esc(u.email)}')">
<i class="ti ti-user-minus"></i>
</button>`;
} else {
actions += `
<button class="btn btn-sm btn-ghost-secondary" title="Edit access"
onclick="open_edit_modal(${u.map_id})">
<i class="ti ti-shield-half"></i>
</button>`;
actions += `
<button class="btn btn-sm btn-ghost-danger" title="Remove user"
onclick="remove_user(${u.map_id}, '${esc(u.name)} ${esc(u.surname)}')">
<i class="ti ti-user-minus"></i>
</button>`;
if (is_owner && u.role !== 'owner') {
if (is_pending) {
actions += `
<button class="btn btn-sm btn-ghost-secondary" title="Resend invitation"
onclick="resend_invite(${u.map_id}, '${esc(u.email)}')">
<i class="ti ti-send"></i>
</button>`;
actions += `
<button class="btn btn-sm btn-ghost-danger" title="Cancel invitation"
onclick="remove_user(${u.map_id}, '${esc(u.email)}')">
<i class="ti ti-user-minus"></i>
</button>`;
} else {
actions += `
<button class="btn btn-sm btn-ghost-secondary" title="Edit access"
onclick="open_edit_modal(${u.map_id})">
<i class="ti ti-shield-half"></i>
</button>`;
actions += `
<button class="btn btn-sm btn-ghost-danger" title="Remove user"
onclick="remove_user(${u.map_id}, '${esc(u.name)} ${esc(u.surname)}')">
<i class="ti ti-user-minus"></i>
</button>`;
}
}
const display_role = is_pending
@@ -306,15 +307,17 @@
</td>
<td style="color:#495057;">${esc(u.email)}</td>
<td>${display_role}</td>
<td>${license_badge}</td>
<td>${is_pending ? '—' : access_badge}</td>
<td style="color:#495057;">${joined}</td>
<td class="text-end pe-4">
<td class="text-end pe-4 owner-only-col">
<div class="d-flex justify-content-end gap-1">${actions}</div>
</td>
</tr>`;
}).join('');
tbody.html(html);
if (!is_owner) $('.owner-only-col').hide();
}
@@ -460,6 +463,15 @@
.replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
function license_html(license) {
const map = {
owner: ['bg-success text-white', 'ti-crown', 'Owner'],
user: ['bg-light text-dark border', 'ti-user', 'User'],
};
const [cls, icon, label] = map[license] || ['bg-light text-dark border', 'ti-user', license || '—'];
return `<span class="badge ${cls}"><i class="ti ${icon} me-1"></i>${label}</span>`;
}
function role_html(role) {
const map = {
owner: ['bg-dark', 'ti-crown', 'Owner'],
@@ -467,7 +479,7 @@
staff: ['bg-info', 'ti-tool', 'Staff'],
viewer: ['bg-secondary', 'ti-eye', 'Viewer'],
};
const [cls, icon, label] = map[role] || ['bg-label-secondary', 'ti-user', 'Unknown'];
const [cls, icon, label] = map[role] || ['bg-secondary', 'ti-user', 'Unknown'];
return `<span class="badge ${cls}"><i class="ti ${icon} me-1"></i>${label}</span>`;
}
@@ -481,7 +493,7 @@
return `<span class="badge bg-secondary"><i class="ti ti-apps me-1"></i>All Apps</span>`;
}
const app = APP_REGISTRY[access];
if (!app) return `<span class="badge bg-label-secondary">${access || '—'}</span>`;
if (!app) return `<span class="badge bg-secondary">${access || '—'}</span>`;
return `<span class="badge ${app.color}"><i class="ti ${app.icon} me-1"></i>${app.label}</span>`;
}