diff --git a/flask_setup/routes.py b/flask_setup/routes.py index 5295923..46e00d1 100644 --- a/flask_setup/routes.py +++ b/flask_setup/routes.py @@ -135,6 +135,20 @@ def _register_auth_routes(app: Flask, adapters: dict) -> None: endpoint="auth.change_role", ) + app.add_url_rule( + "/admin/users//ban", + view_func=acc.ban_account, + methods=["POST"], + endpoint="auth.ban_account", + ) + + app.add_url_rule( + "/admin/users//unban", + view_func=acc.unban_account, + methods=["POST"], + endpoint="auth.unban_account", + ) + def _register_file_routes(app: Flask, adapters: dict) -> None: fad = adapters["file_adapter"] diff --git a/frontend/static/css/base.css b/frontend/static/css/base.css index d938702..89ac95f 100644 --- a/frontend/static/css/base.css +++ b/frontend/static/css/base.css @@ -562,6 +562,62 @@ button:hover, margin: 0; } +#ban-modal { + max-width: 32rem; +} + +#ban-modal .modal-input-group { + gap: 0; +} + +#ban-modal .modal-input-group label { + margin-bottom: var(--spacing-sm); + font-family: var(--font-primary); +} + +#ban-modal .char-count { + font-family: var(--font-primary); +} + +#ban-modal .ban-reason-textarea { + margin-top: var(--spacing-xs); +} + +.ban-reason-textarea { + width: 100%; + min-height: 4.5rem; + height: auto; + padding: var(--spacing-sm) var(--spacing-md); + border: 1px solid var(--outline-variant); + border-radius: var(--radius-default); + background: var(--surface-container); + color: var(--on-surface); + font-family: var(--font-primary); + font-size: var(--font-body-base); + line-height: 1.5; + resize: vertical; + outline: none; + box-sizing: border-box; +} + +.ban-reason-textarea:focus { + border-color: var(--primary); +} + +.hint-row { + display: flex; + justify-content: space-between; + align-items: center; + font-size: var(--font-body-sm); + color: var(--on-surface-variant); +} + +.char-count { + font-size: var(--font-body-sm); + color: var(--on-surface-variant); + font-family: var(--font-mono); +} + button:focus-visible, .btn:focus-visible, a:focus-visible { diff --git a/frontend/static/css/profile.css b/frontend/static/css/profile.css index de4d23c..1049ca3 100644 --- a/frontend/static/css/profile.css +++ b/frontend/static/css/profile.css @@ -154,9 +154,12 @@ .profile-actions { flex-direction: column; + align-items: stretch; + width: fit-content; + margin-left: auto; + margin-right: auto; } - .profile-actions .btn-secondary, - .profile-actions .btn-danger { + .profile-actions .btn { width: 100%; } } diff --git a/frontend/static/scripts/confirm-dialog.js b/frontend/static/scripts/confirm-dialog.js index 0a6b2b4..3d53051 100644 --- a/frontend/static/scripts/confirm-dialog.js +++ b/frontend/static/scripts/confirm-dialog.js @@ -62,4 +62,36 @@ if (e.key === 'Escape') close(); }); }); + + const BAN_TRIGGER_SELECTOR = '.ban-trigger'; + + document.addEventListener('click', (e) => { + const trigger = e.target.closest(BAN_TRIGGER_SELECTOR); + if (!trigger) return; + + const userId = trigger.getAttribute('data-user-id'); + const username = trigger.getAttribute('data-username'); + const modal = document.getElementById('ban-modal'); + if (!modal) return; + + const form = document.getElementById('ban-form'); + form.action = '/admin/users/' + userId + '/ban'; + + document.getElementById('ban-username').textContent = username; + document.getElementById('ban-reason-input').value = ''; + document.getElementById('ban-reason-count').textContent = '0'; + modal.showModal(); + }); + + document.addEventListener('click', (e) => { + if (e.target.id === 'ban-modal-cancel') { + document.getElementById('ban-modal')?.close(); + } + }); + + document.addEventListener('input', (e) => { + if (e.target.id === 'ban-reason-input') { + document.getElementById('ban-reason-count').textContent = e.target.value.length; + } + }); })(); diff --git a/frontend/templates/base.html b/frontend/templates/base.html index 10d7b31..5ed5f2a 100644 --- a/frontend/templates/base.html +++ b/frontend/templates/base.html @@ -110,6 +110,31 @@ + + +
+ + +
+
+