Autosave: 20260215-194817
This commit is contained in:
parent
98888d0370
commit
e3984686cb
@ -121,6 +121,43 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$stmt = db()->prepare("DELETE ur FROM user_roles ur JOIN roles r ON ur.role_id = r.id WHERE ur.user_id = ? AND ur.role_id = ? AND r.server_id = ?");
|
||||
$stmt->execute([$target_user_id, $role_id, $server_id]);
|
||||
echo json_encode(['success' => true]);
|
||||
} elseif ($action === 'reorder') {
|
||||
$orders = $data['orders'] ?? [];
|
||||
foreach ($orders as $order) {
|
||||
$stmt = db()->prepare("UPDATE roles SET position = ? WHERE id = ? AND server_id = ?");
|
||||
$stmt->execute([$order['position'], $order['id'], $server_id]);
|
||||
}
|
||||
echo json_encode(['success' => true]);
|
||||
} elseif ($action === 'set_user_roles') {
|
||||
$target_user_id = $data['user_id'] ?? 0;
|
||||
$role_ids = $data['role_ids'] ?? [];
|
||||
|
||||
// Begin transaction
|
||||
$db = db();
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
// Remove all existing roles for this user in this server
|
||||
$stmt = $db->prepare("DELETE ur FROM user_roles ur JOIN roles r ON ur.role_id = r.id WHERE ur.user_id = ? AND r.server_id = ?");
|
||||
$stmt->execute([$target_user_id, $server_id]);
|
||||
|
||||
// Add new roles
|
||||
if (!empty($role_ids)) {
|
||||
$stmt = $db->prepare("INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)");
|
||||
foreach ($role_ids as $rid) {
|
||||
// Verify role belongs to server
|
||||
$check = $db->prepare("SELECT id FROM roles WHERE id = ? AND server_id = ?");
|
||||
$check->execute([$rid, $server_id]);
|
||||
if ($check->fetch()) {
|
||||
$stmt->execute([$target_user_id, $rid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
$db->commit();
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (Exception $e) {
|
||||
$db->rollBack();
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
@ -230,10 +230,30 @@ body {
|
||||
}
|
||||
|
||||
.sortable-ghost {
|
||||
background-color: var(--hover) !important;
|
||||
opacity: 0.5;
|
||||
opacity: 0.4;
|
||||
background-color: var(--blurple) !important;
|
||||
}
|
||||
|
||||
.role-drag-handle:hover {
|
||||
opacity: 1 !important;
|
||||
color: var(--blurple);
|
||||
}
|
||||
|
||||
.member-context-menu {
|
||||
animation: menuFadeIn 0.1s ease-out;
|
||||
}
|
||||
|
||||
@keyframes menuFadeIn {
|
||||
from { opacity: 0; transform: translateY(-5px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.member-menu-action:hover {
|
||||
background-color: var(--blurple) !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
|
||||
.add-channel-btn {
|
||||
cursor: pointer;
|
||||
font-size: 1.2em;
|
||||
|
||||
@ -628,17 +628,63 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start DM
|
||||
const dmBtn = e.target.closest('.start-dm-btn');
|
||||
if (dmBtn) {
|
||||
const userId = dmBtn.dataset.userId;
|
||||
const formData = new FormData();
|
||||
formData.append('user_id', userId);
|
||||
const resp = await fetch('api_v1_dms.php', { method: 'POST', body: formData });
|
||||
const result = await resp.json();
|
||||
if (result.success) {
|
||||
window.location.href = `?server_id=dms&channel_id=${result.channel_id}`;
|
||||
}
|
||||
// Member Menu
|
||||
const memberItem = e.target.closest('.member-item');
|
||||
if (memberItem) {
|
||||
const userId = memberItem.dataset.userId;
|
||||
const username = memberItem.dataset.username;
|
||||
const avatar = memberItem.dataset.avatar;
|
||||
|
||||
// Create or show member menu
|
||||
document.querySelector('.member-context-menu')?.remove();
|
||||
const menu = document.createElement('div');
|
||||
menu.className = 'member-context-menu bg-dark border border-secondary rounded p-2';
|
||||
menu.style.position = 'fixed';
|
||||
menu.style.zIndex = '1000';
|
||||
menu.style.boxShadow = '0 4px 12px rgba(0,0,0,0.5)';
|
||||
menu.style.minWidth = '150px';
|
||||
|
||||
const rect = memberItem.getBoundingClientRect();
|
||||
menu.style.top = `${rect.top}px`;
|
||||
menu.style.left = `${rect.left - 160}px`;
|
||||
|
||||
menu.innerHTML = `
|
||||
<div class="mb-2 p-1 border-bottom border-secondary d-flex align-items-center">
|
||||
<div class="message-avatar me-2" style="width: 24px; height: 24px; ${avatar ? `background-image: url('${avatar}');` : ''}"></div>
|
||||
<span class="small fw-bold">${escapeHTML(username)}</span>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-dark w-100 text-start mb-1 member-menu-action" data-action="message">Message</button>
|
||||
${(window.isServerOwner || window.canManageServer) ? `<button class="btn btn-sm btn-dark w-100 text-start member-menu-action" data-action="edit-roles">Éditer son rôle</button>` : ''}
|
||||
`;
|
||||
|
||||
document.body.appendChild(menu);
|
||||
|
||||
// Close menu on click outside
|
||||
const closeMenu = (e) => {
|
||||
if (!menu.contains(e.target)) {
|
||||
menu.remove();
|
||||
document.removeEventListener('mousedown', closeMenu);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', closeMenu);
|
||||
|
||||
menu.querySelectorAll('.member-menu-action').forEach(btn => {
|
||||
btn.onclick = async () => {
|
||||
const action = btn.dataset.action;
|
||||
if (action === 'message') {
|
||||
const formData = new FormData();
|
||||
formData.append('user_id', userId);
|
||||
const resp = await fetch('api_v1_dms.php', { method: 'POST', body: formData });
|
||||
const result = await resp.json();
|
||||
if (result.success) {
|
||||
window.location.href = `?server_id=dms&channel_id=${result.channel_id}`;
|
||||
}
|
||||
} else if (action === 'edit-roles') {
|
||||
openEditUserRolesModal(userId, username, avatar);
|
||||
}
|
||||
menu.remove();
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -1010,10 +1056,88 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
serverPermissions = data.permissions_list;
|
||||
if (rolesList) renderRoles(data.roles);
|
||||
if (membersList) renderMembers(data.members);
|
||||
updateGlobalUI(data.members);
|
||||
}
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
|
||||
function renderRoleIconJS(icon, size = '12px') {
|
||||
if (!icon) return '';
|
||||
const isUrl = icon.startsWith('http') || icon.startsWith('/');
|
||||
if (isUrl) {
|
||||
return `<img src="${escapeHTML(icon)}" class="role-icon ms-1" style="width: ${size}; height: ${size}; vertical-align: middle; object-fit: contain;">`;
|
||||
} else {
|
||||
return `<span class="ms-1" style="font-size: ${size}; vertical-align: middle;">${escapeHTML(icon)}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
function updateGlobalUI(members) {
|
||||
// Update members sidebar
|
||||
const sidebar = document.querySelector('.members-sidebar');
|
||||
if (sidebar) {
|
||||
const countEl = sidebar.querySelector('div[style*="text-transform: uppercase"]');
|
||||
if (countEl) countEl.textContent = `Members — ${members.length}`;
|
||||
|
||||
// We need to keep the "Members - X" div and replace everything else
|
||||
const header = sidebar.firstElementChild;
|
||||
sidebar.innerHTML = '';
|
||||
sidebar.appendChild(header);
|
||||
|
||||
members.forEach(m => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'channel-item member-item';
|
||||
item.dataset.userId = m.id;
|
||||
item.dataset.username = m.username;
|
||||
item.dataset.avatar = m.avatar_url || '';
|
||||
item.style.color = 'var(--text-primary)';
|
||||
item.style.marginBottom = '8px';
|
||||
item.style.cursor = 'pointer';
|
||||
|
||||
const roleIconHtml = renderRoleIconJS(m.role_icon, '12px');
|
||||
const avatarBg = m.avatar_url ? `background-image: url('${m.avatar_url}');` : '';
|
||||
const statusColor = m.status === 'online' ? '#23a559' : '#80848e';
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="message-avatar" style="width: 32px; height: 32px; background-color: ${statusColor}; position: relative; ${avatarBg}">
|
||||
${m.status === 'online' ? `<div style="position: absolute; bottom: 0; right: 0; width: 10px; height: 10px; background-color: #23a559; border-radius: 50%; border: 2px solid var(--bg-members);"></div>` : ''}
|
||||
</div>
|
||||
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; ${m.role_color ? `color: ${m.role_color};` : ''}">
|
||||
${escapeHTML(m.username)}
|
||||
${roleIconHtml}
|
||||
</span>
|
||||
`;
|
||||
sidebar.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
// Update chat colors
|
||||
document.querySelectorAll('.message-author').forEach(authorEl => {
|
||||
const username = authorEl.childNodes[0].textContent.trim();
|
||||
const member = members.find(m => m.username === username);
|
||||
if (member) {
|
||||
authorEl.style.color = member.role_color || 'inherit';
|
||||
// Try to update icon if it exists or add it if it doesn't
|
||||
let iconEl = authorEl.querySelector('.role-icon, span.ms-1');
|
||||
const newIconHtml = renderRoleIconJS(member.role_icon, '12px');
|
||||
|
||||
if (newIconHtml) {
|
||||
if (iconEl) {
|
||||
const temp = document.createElement('div');
|
||||
temp.innerHTML = newIconHtml;
|
||||
iconEl.replaceWith(temp.firstChild);
|
||||
} else {
|
||||
const temp = document.createElement('div');
|
||||
temp.innerHTML = newIconHtml;
|
||||
// Insert after the text node
|
||||
authorEl.insertBefore(temp.firstChild, authorEl.childNodes[1]);
|
||||
}
|
||||
} else if (iconEl) {
|
||||
iconEl.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderRoles(roles) {
|
||||
rolesList.innerHTML = '';
|
||||
if (roles.length === 0) {
|
||||
@ -1021,10 +1145,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
roles.forEach(role => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'list-group-item bg-transparent text-white border-secondary d-flex justify-content-between align-items-center p-2 mb-1 rounded';
|
||||
item.className = 'list-group-item bg-transparent text-white border-secondary d-flex justify-content-between align-items-center p-2 mb-1 rounded role-sortable-item';
|
||||
item.dataset.id = role.id;
|
||||
const isUrl = role.icon_url && (role.icon_url.startsWith('http') || role.icon_url.startsWith('/'));
|
||||
item.innerHTML = `
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="role-drag-handle me-3" style="cursor: grab; opacity: 0.5;">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="5" x2="8" y2="5.01"></line><line x1="16" y1="5" x2="16" y2="5.01"></line><line x1="8" y1="12" x2="8" y2="12.01"></line><line x1="16" y1="12" x2="16" y2="12.01"></line><line x1="8" y1="19" x2="8" y2="19.01"></line><line x1="16" y1="19" x2="16" y2="19.01"></line></svg>
|
||||
</div>
|
||||
<div style="width: 14px; height: 14px; border-radius: 50%; background-color: ${role.color}; margin-right: 12px; box-shadow: 0 0 5px ${role.color}88;"></div>
|
||||
<span class="fw-medium">${role.name}</span>
|
||||
${role.icon_url ? (isUrl ? `<img src="${role.icon_url}" class="ms-1" style="width: 12px; height: 12px; object-fit: contain;">` : `<span class="ms-1" style="font-size: 12px;">${role.icon_url}</span>`) : ''}
|
||||
@ -1036,28 +1164,49 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
`;
|
||||
rolesList.appendChild(item);
|
||||
});
|
||||
|
||||
// Initialize Sortable for roles
|
||||
if (typeof Sortable !== 'undefined' && rolesList) {
|
||||
new Sortable(rolesList, {
|
||||
animation: 150,
|
||||
handle: '.role-drag-handle',
|
||||
ghostClass: 'sortable-ghost',
|
||||
onEnd: () => saveRolePositions()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function saveRolePositions() {
|
||||
const orders = [];
|
||||
const items = rolesList.querySelectorAll('.role-sortable-item');
|
||||
// Invert the order because we ORDER BY position DESC in SQL
|
||||
let position = items.length - 1;
|
||||
items.forEach(item => {
|
||||
orders.push({
|
||||
id: item.dataset.id,
|
||||
position: position--
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
await fetch('api_v1_roles.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'reorder',
|
||||
server_id: activeServerId,
|
||||
orders: orders
|
||||
})
|
||||
});
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
|
||||
function renderMembers(members) {
|
||||
membersList.innerHTML = '';
|
||||
members.forEach(member => {
|
||||
const memberRoles = member.role_ids ? member.role_ids.split(',') : [];
|
||||
const item = document.createElement('div');
|
||||
item.className = 'list-group-item bg-transparent text-white border-secondary d-flex justify-content-between align-items-center p-2 mb-2 rounded bg-dark';
|
||||
|
||||
let rolesHtml = '';
|
||||
serverRoles.forEach(role => {
|
||||
const isAssigned = memberRoles.includes(role.id.toString());
|
||||
rolesHtml += `
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input role-assign-check" type="checkbox"
|
||||
data-user-id="${member.id}" data-role-id="${role.id}"
|
||||
${isAssigned ? 'checked' : ''}>
|
||||
<label class="form-check-label small" style="color: ${role.color}">${role.name}</label>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
const isIconUrl = member.role_icon && (member.role_icon.startsWith('http') || member.role_icon.startsWith('/'));
|
||||
const roleIconHtml = member.role_icon ? (isIconUrl ? `<img src="${member.role_icon}" class="role-icon ms-1" style="width: 12px; height: 12px; vertical-align: middle; object-fit: contain;">` : `<span class="ms-1" style="font-size: 12px; vertical-align: middle;">${member.role_icon}</span>`) : '';
|
||||
|
||||
@ -1069,16 +1218,28 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
${escapeHTML(member.username)}
|
||||
${roleIconHtml}
|
||||
</div>
|
||||
<div class="member-roles-assign-list">
|
||||
${rolesHtml || '<span class="text-muted small">No roles available</span>'}
|
||||
<div class="text-muted small">
|
||||
${member.role_names ? member.role_names.split(',').join(', ') : 'No roles'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${(window.isServerOwner || window.canManageServer) ? `
|
||||
<button class="btn btn-sm btn-outline-light edit-user-roles-settings-btn" data-id="${member.id}" data-username="${member.username}" data-avatar="${member.avatar_url || ''}">Roles</button>
|
||||
` : ''}
|
||||
`;
|
||||
membersList.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
// Add listener for the button in members list tab
|
||||
membersList?.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.edit-user-roles-settings-btn');
|
||||
if (btn) {
|
||||
openEditUserRolesModal(btn.dataset.id, btn.dataset.username, btn.dataset.avatar);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Role Editing Modal Logic
|
||||
rolesList?.addEventListener('click', (e) => {
|
||||
if (e.target.classList.contains('edit-role-btn-v2')) {
|
||||
@ -1108,7 +1269,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('save-role-btn')?.addEventListener('click', async () => {
|
||||
document.getElementById('save-role-btn')?.addEventListener('click', async (e) => {
|
||||
const btn = e.target;
|
||||
const originalText = btn.textContent;
|
||||
const id = document.getElementById('edit-role-id').value;
|
||||
const name = document.getElementById('edit-role-name').value;
|
||||
const color = document.getElementById('edit-role-color').value;
|
||||
@ -1127,31 +1290,92 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (data.success) {
|
||||
bootstrap.Modal.getInstance(document.getElementById('roleEditorModal')).hide();
|
||||
btn.textContent = 'Saved ✅';
|
||||
btn.classList.replace('btn-primary', 'btn-success');
|
||||
setTimeout(() => {
|
||||
btn.textContent = originalText;
|
||||
btn.classList.replace('btn-success', 'btn-primary');
|
||||
}, 2000);
|
||||
loadRoles();
|
||||
}
|
||||
} catch (e) { console.error(e); }
|
||||
});
|
||||
|
||||
membersList?.addEventListener('change', async (e) => {
|
||||
if (e.target.classList.contains('role-assign-check')) {
|
||||
const userId = e.target.dataset.userId;
|
||||
const roleId = e.target.dataset.roleId;
|
||||
const action = e.target.checked ? 'assign' : 'unassign';
|
||||
async function openEditUserRolesModal(userId, username, avatar) {
|
||||
const modal = document.getElementById('editUserRolesModal');
|
||||
document.getElementById('edit-user-roles-user-id').value = userId;
|
||||
document.getElementById('edit-user-roles-username').textContent = username;
|
||||
const avatarEl = document.getElementById('edit-user-roles-avatar');
|
||||
avatarEl.style.backgroundImage = avatar ? `url('${avatar}')` : 'none';
|
||||
|
||||
const list = document.getElementById('user-roles-selection-list');
|
||||
list.innerHTML = '<div class="text-center p-3 text-muted">Loading roles...</div>';
|
||||
|
||||
const bsModal = new bootstrap.Modal(modal);
|
||||
bsModal.show();
|
||||
|
||||
try {
|
||||
// We need to fetch roles and the current user's roles
|
||||
// We can reuse loadRoles or make a specific call
|
||||
const resp = await fetch(`api_v1_roles.php?server_id=${activeServerId}`);
|
||||
const data = await resp.json();
|
||||
|
||||
try {
|
||||
const resp = await fetch('api_v1_roles.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action, server_id: activeServerId, user_id: userId, role_id: roleId })
|
||||
if (data.success) {
|
||||
const member = data.members.find(m => m.id == userId);
|
||||
const assignedRoles = member && member.role_ids ? member.role_ids.split(',') : [];
|
||||
|
||||
list.innerHTML = '';
|
||||
// Sort roles by position descending for display
|
||||
data.roles.sort((a, b) => b.position - a.position).forEach(role => {
|
||||
const isChecked = assignedRoles.includes(role.id.toString());
|
||||
const item = document.createElement('div');
|
||||
item.className = 'list-group-item bg-dark text-white border-secondary p-2 d-flex align-items-center';
|
||||
item.innerHTML = `
|
||||
<input class="form-check-input me-3 user-role-checkbox" type="checkbox" value="${role.id}" id="user-role-${role.id}" ${isChecked ? 'checked' : ''}>
|
||||
<label class="form-check-label flex-grow-1" for="user-role-${role.id}" style="color: ${role.color}; cursor: pointer;">
|
||||
${role.name}
|
||||
</label>
|
||||
`;
|
||||
list.appendChild(item);
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (!data.success) {
|
||||
alert(data.error || 'Failed to update role');
|
||||
e.target.checked = !e.target.checked;
|
||||
|
||||
if (data.roles.length === 0) {
|
||||
list.innerHTML = '<div class="text-center p-3 text-muted">No roles defined for this server.</div>';
|
||||
}
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
}
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
|
||||
document.getElementById('save-user-roles-btn')?.addEventListener('click', async (e) => {
|
||||
const btn = e.target;
|
||||
const originalText = btn.textContent;
|
||||
const userId = document.getElementById('edit-user-roles-user-id').value;
|
||||
const roleIds = Array.from(document.querySelectorAll('.user-role-checkbox:checked')).map(cb => cb.value);
|
||||
|
||||
try {
|
||||
const resp = await fetch('api_v1_roles.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'set_user_roles',
|
||||
server_id: activeServerId,
|
||||
user_id: userId,
|
||||
role_ids: roleIds
|
||||
})
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (data.success) {
|
||||
btn.textContent = 'Saved ✅';
|
||||
btn.classList.replace('btn-primary', 'btn-success');
|
||||
setTimeout(() => {
|
||||
btn.textContent = originalText;
|
||||
btn.classList.replace('btn-success', 'btn-primary');
|
||||
}, 2000);
|
||||
loadRoles();
|
||||
} else {
|
||||
alert(data.error || 'Failed to update roles');
|
||||
}
|
||||
} catch (e) { console.error(e); }
|
||||
});
|
||||
|
||||
addRoleBtn?.addEventListener('click', async () => {
|
||||
|
||||
31
index.php
31
index.php
@ -776,7 +776,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
Members — <?php echo count($members); ?>
|
||||
</div>
|
||||
<?php foreach($members as $m): ?>
|
||||
<div class="channel-item start-dm-btn" data-user-id="<?php echo $m['id']; ?>" style="color: var(--text-primary); margin-bottom: 8px;">
|
||||
<div class="channel-item member-item" data-user-id="<?php echo $m['id']; ?>" data-username="<?php echo htmlspecialchars($m['username']); ?>" data-avatar="<?php echo htmlspecialchars($m['avatar_url'] ?? ''); ?>" style="color: var(--text-primary); margin-bottom: 8px; cursor: pointer;">
|
||||
<div class="message-avatar" style="width: 32px; height: 32px; background-color: <?php echo $m['status'] == 'online' ? '#23a559' : '#80848e'; ?>; position: relative; <?php echo $m['avatar_url'] ? "background-image: url('{$m['avatar_url']}');" : ""; ?>">
|
||||
<?php if($m['status'] == 'online'): ?>
|
||||
<div style="position: absolute; bottom: 0; right: 0; width: 10px; height: 10px; background-color: #23a559; border-radius: 50%; border: 2px solid var(--bg-members);"></div>
|
||||
@ -1379,6 +1379,35 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit User Roles Modal -->
|
||||
<div class="modal fade" id="editUserRolesModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Edit Member Roles</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
<div id="edit-user-roles-avatar" class="message-avatar me-3" style="width: 48px; height: 48px;"></div>
|
||||
<div>
|
||||
<h5 id="edit-user-roles-username" class="mb-0">Username</h5>
|
||||
<div class="text-muted small">Select roles to assign to this member</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" id="edit-user-roles-user-id">
|
||||
<div id="user-roles-selection-list" class="list-group list-group-flush bg-dark rounded">
|
||||
<!-- Roles checkboxes populated by JS -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" id="save-user-roles-btn" class="btn btn-primary">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
window.currentUserId = <?php echo $current_user_id; ?>;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user