feat: move seed loading to admin profile page, add password change
- 'Load Seed Protocols' button now appears on the admin's own profile page instead of the About page. Can be repeated to re-import seeds. - After loading, the profile refreshes to show updated protocol list. - 'Change Password' button also appears on own profile page for all users (not just admin), opening a modal with current/new/confirm fields. - Password modal added to HTML. - README updated: seed loading instructions point to profile page, mentions 100 protocols, notes repeat seeding is supported.
This commit is contained in:
+53
-14
@@ -63,7 +63,6 @@ async function init() {
|
||||
}
|
||||
|
||||
function route() {
|
||||
isRouting = true;
|
||||
const hash = location.hash.slice(1) || 'library';
|
||||
if (hash.startsWith('protocols/')) {
|
||||
showDetail(hash.split('/')[1]);
|
||||
@@ -82,15 +81,14 @@ function route() {
|
||||
} else {
|
||||
navigate('library');
|
||||
}
|
||||
isRouting = false;
|
||||
}
|
||||
|
||||
let searchTimer = null;
|
||||
let currentRoute = '';
|
||||
let isRouting = false;
|
||||
|
||||
function navigate(view) {
|
||||
if (currentRoute === view && view !== 'library') return;
|
||||
// Prevent double navigation from hashchange
|
||||
if (currentRoute === view) return;
|
||||
currentRoute = view;
|
||||
document.querySelectorAll('.view').forEach(v => v.style.display = 'none');
|
||||
document.getElementById('view-' + view).style.display = 'block';
|
||||
@@ -98,17 +96,11 @@ function navigate(view) {
|
||||
document.querySelectorAll('.mobile-panel a').forEach(a => a.classList.toggle('active', a.dataset.view === view));
|
||||
closeMenu();
|
||||
updateBreadcrumb(view);
|
||||
if (!isRouting) {
|
||||
if (view === 'library') { location.hash = 'protocols'; }
|
||||
if (view === 'collections') { location.hash = 'collections'; }
|
||||
if (view === 'about') { location.hash = 'about'; }
|
||||
if (view === 'terms') { location.hash = 'terms'; }
|
||||
}
|
||||
if (view === 'library') loadProtocols();
|
||||
if (view === 'collections') loadCollections();
|
||||
if (view === 'library') { location.hash = 'library'; loadProtocols(); }
|
||||
if (view === 'collections') { location.hash = 'collections'; loadCollections(); }
|
||||
if (view === 'create') { editingSlug = null; resetForm(); }
|
||||
if (view === 'create-collection') { editingCollectionSlug = null; resetCollectionForm(); }
|
||||
if (view === 'about') updateAboutPage();
|
||||
if (view === 'about') { location.hash = 'about'; }
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
@@ -823,12 +815,59 @@ async function showUser(username) {
|
||||
'<div class="info"><h3>' + escapeHtml(col.title) + '</h3><p>' + escapeHtml(col.description||'') + '</p></div></div>';
|
||||
}).join('') +
|
||||
'</div>' : '');
|
||||
document.getElementById('detailActions').innerHTML = '';
|
||||
// Add actions if viewing own profile
|
||||
var actionsHtml = '';
|
||||
if (currentUser && currentUser.username === username) {
|
||||
actionsHtml = '<button class="btn" onclick="showChangePassword()">Change Password</button>';
|
||||
if (currentUser.role === 'admin') {
|
||||
actionsHtml += '<button class="btn primary" id="seedBtn" onclick="loadSeedProtocols()">Load Seed Protocols</button>';
|
||||
}
|
||||
}
|
||||
document.getElementById('detailActions').innerHTML = actionsHtml;
|
||||
} catch (e) {
|
||||
document.getElementById('detailContent').innerHTML = '<div class="empty">User not found</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// --- Password change ---
|
||||
function showChangePassword() {
|
||||
document.getElementById('pwModal').style.display = 'flex';
|
||||
}
|
||||
function closePwModal() { document.getElementById('pwModal').style.display = 'none'; }
|
||||
|
||||
async function handleChangePassword(e) {
|
||||
e.preventDefault();
|
||||
var current = document.getElementById('pwCurrent').value;
|
||||
var newPassword = document.getElementById('pwNew').value;
|
||||
var confirmPw = document.getElementById('pwConfirm').value;
|
||||
if (newPassword !== confirmPw) { toast('Passwords do not match', true); return; }
|
||||
if (newPassword.length < 8) { toast('Password must be at least 8 characters', true); return; }
|
||||
try {
|
||||
await api('/auth/password', { method: 'POST', body: JSON.stringify({ current_password: current, new_password: newPassword }) });
|
||||
toast('Password changed');
|
||||
closePwModal();
|
||||
document.getElementById('pwForm').reset();
|
||||
} catch (err) {
|
||||
toast(err.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Seed protocols ---
|
||||
async function loadSeedProtocols() {
|
||||
var btn = document.getElementById('seedBtn');
|
||||
if (btn) { btn.disabled = true; btn.textContent = 'Loading...'; }
|
||||
try {
|
||||
var result = await api('/seed', { method: 'POST' });
|
||||
toast('Loaded ' + result.imported + ' seed protocols');
|
||||
if (btn) { btn.textContent = 'Load Seed Protocols'; btn.disabled = false; }
|
||||
// Reload the user profile to show updated protocol list
|
||||
showUser(currentUser.username);
|
||||
} catch (e) {
|
||||
toast(e.message, true);
|
||||
if (btn) { btn.textContent = 'Load Seed Protocols'; btn.disabled = false; }
|
||||
}
|
||||
}
|
||||
|
||||
// --- Toast ---
|
||||
let toastTimer = null;
|
||||
function toast(msg, isError) {
|
||||
|
||||
Reference in New Issue
Block a user