Files

222 lines
8.4 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}Users — Bid Checker{% endblock %}
{% block content %}
<div class="page-header">
<h1 class="page-title">User Management</h1>
<div class="flex gap-1 items-center">
<div class="search-wrap">
<input type="text" id="user-search" placeholder="Search users…" autocomplete="off">
<span class="search-icon">🔍</span>
</div>
<button class="btn btn-primary" onclick="openModal('modal-create-user')"> Add User</button>
</div>
</div>
<div class="card">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th><th>Username</th><th>Full Name</th><th>Email</th>
<th>Role</th><th>Active</th><th>Created</th><th>Actions</th>
</tr>
</thead>
<tbody>
{% for u in users %}
<tr>
<td>{{ u.id }}</td>
<td><strong>{{ u.username }}</strong></td>
<td>{{ u.full_name or '—' }}</td>
<td>{{ u.email or '—' }}</td>
<td><span class="badge badge-{{ 'accent' if u.role == 'admin' else 'neutral' }}">{{ u.role }}</span></td>
<td><span class="badge badge-{{ 'success' if u.is_active else 'danger' }}">{{ 'Yes' if u.is_active else 'No' }}</span></td>
<td class="text-muted">{{ u.created_at.strftime('%Y-%m-%d') if u.created_at else '—' }}</td>
<td class="actions">
<button class="btn btn-ghost btn-sm js-edit-user"
data-user='{{ u|tojson }}'>✎ Edit</button>
{% if u.email %}
<button class="btn btn-ghost btn-sm js-send-reset"
data-id="{{ u.id }}" data-username="{{ u.username }}" data-email="{{ u.email }}"
title="Send password reset link to {{ u.email }}">✉ Reset</button>
{% endif %}
<form method="post" action="{{ url_for('admin_users.delete', user_id=u.id) }}"
style="display:inline">
<button class="btn btn-danger btn-sm js-delete-user" type="button"
data-username="{{ u.username }}"></button>
</form>
</td>
</tr>
{% else %}
<tr><td colspan="8" class="text-center text-muted">No users found.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Create User Modal -->
<div class="modal-overlay" id="modal-create-user">
<div class="modal-dialog">
<div class="modal-header">
<h3>Add User</h3>
<button class="modal-close" onclick="closeModal('modal-create-user')"></button>
</div>
<form method="post" action="{{ url_for('admin_users.create') }}">
<div class="modal-body">
<div class="form-group">
<label class="form-label">Username *</label>
<input class="form-control" name="username" required>
</div>
<div class="form-group">
<label class="form-label">Full Name</label>
<input class="form-control" name="full_name">
</div>
<div class="form-group">
<label class="form-label">Email</label>
<input class="form-control" type="email" name="email">
</div>
<div class="form-group">
<label class="form-label">Role</label>
<select class="form-control" name="role">
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Password *</label>
<input class="form-control" type="password" name="password" id="create-user-pw" required>
<div class="pw-strength-wrap">
<div class="pw-strength-bar"><div class="pw-strength-fill" id="create-user-pw-fill"></div></div>
<small class="pw-strength-label" id="create-user-pw-label"></small>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-ghost" onclick="closeModal('modal-create-user')">Cancel</button>
<button type="submit" class="btn btn-primary">Create User</button>
</div>
</form>
</div>
</div>
<!-- Edit User Modal -->
<div class="modal-overlay" id="modal-edit-user">
<div class="modal-dialog">
<div class="modal-header">
<h3>Edit User</h3>
<button class="modal-close" onclick="closeModal('modal-edit-user')"></button>
</div>
<form method="post" id="edit-user-form">
<div class="modal-body">
<div class="form-group">
<label class="form-label">Username *</label>
<input class="form-control" name="username" id="edit-username" required>
</div>
<div class="form-group">
<label class="form-label">Full Name</label>
<input class="form-control" name="full_name" id="edit-full-name">
</div>
<div class="form-group">
<label class="form-label">Email</label>
<input class="form-control" type="email" name="email" id="edit-email">
</div>
<div class="form-group">
<label class="form-label">Role</label>
<select class="form-control" name="role" id="edit-role">
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Active</label>
<select class="form-control" name="is_active" id="edit-is-active">
<option value="1">Yes</option>
<option value="0">No</option>
</select>
</div>
<div class="form-group">
<label class="form-label">New Password (leave blank to keep current)</label>
<input class="form-control" type="password" name="password">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-ghost" onclick="closeModal('modal-edit-user')">Cancel</button>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</form>
</div>
</div>
<script>
/* User search */
(function() {
var timer = null;
document.getElementById('user-search').addEventListener('input', function() {
var val = this.value;
clearTimeout(timer);
timer = setTimeout(function() {
var q = val.trim().toLowerCase();
document.querySelectorAll('tbody tr').forEach(function(row) {
row.style.display = !q || row.textContent.toLowerCase().includes(q) ? '' : 'none';
});
}, 250);
});
}());
/* Delete user — safe confirm via data-attribute */
document.querySelectorAll('.js-delete-user').forEach(function(btn) {
btn.addEventListener('click', function() {
if (confirm('Delete user ' + btn.dataset.username + '?')) {
btn.closest('form').submit();
}
});
});
/* Edit user — data-user attribute avoids single-quote apostrophe breakage */
document.querySelectorAll('.js-edit-user').forEach(function(btn) {
btn.addEventListener('click', function() {
openEditUser(JSON.parse(btn.dataset.user));
});
});
function openEditUser(u) {
document.getElementById('edit-username').value = u.username;
document.getElementById('edit-full-name').value = u.full_name || '';
document.getElementById('edit-email').value = u.email || '';
document.getElementById('edit-role').value = u.role;
document.getElementById('edit-is-active').value = u.is_active ? '1' : '0';
document.getElementById('edit-user-form').action =
'/admin/users/' + u.id + '/edit';
openModal('modal-edit-user');
}
document.querySelectorAll('.js-send-reset').forEach(function(btn) {
btn.addEventListener('click', function() {
var username = btn.dataset.username;
var email = btn.dataset.email;
if (!confirm('Send a password reset link to ' + username + ' (' + email + ')?')) return;
btn.disabled = true;
btn.textContent = '…';
fetch('/admin/users/' + btn.dataset.id + '/send-reset', {
method: 'POST',
headers: { 'X-CSRFToken': getCsrfToken() },
}).then(function(r) { return r.json(); })
.then(function(d) {
if (d.ok) {
btn.textContent = '✓ Sent';
btn.style.color = 'var(--success, #16a34a)';
} else {
alert('Error: ' + d.error);
btn.disabled = false;
btn.textContent = '✉ Reset';
}
})
.catch(function() {
alert('Request failed. Please try again.');
btn.disabled = false;
btn.textContent = '✉ Reset';
});
});
});
</script>
{% endblock %}
{% block scripts %}<script>attachPasswordStrength('create-user-pw', 'create-user-pw-fill', 'create-user-pw-label');</script>{% endblock %}