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:
@@ -73,7 +73,7 @@ protocol-droid/
|
|||||||
```
|
```
|
||||||
4. The database schema auto-initializes on first API request (creates `data/protocol_droid.db`)
|
4. The database schema auto-initializes on first API request (creates `data/protocol_droid.db`)
|
||||||
5. Visit the site and register — the **first user to register becomes admin**
|
5. Visit the site and register — the **first user to register becomes admin**
|
||||||
6. Optionally load seed protocols: Go to the **About** page and click **"Load seed protocols"** (visible to admins only). This loads 6 example protocols into your library in one click. No curl needed.
|
5. Optionally load seed protocols: Go to your **profile page** (click your @username) and click **"Load Seed Protocols"** (visible to admins only). This loads 100 example protocols into your library. Can be repeated to re-import seeds.
|
||||||
|
|
||||||
**No default admin credentials exist.** A fresh deployment has an empty database. The first registration creates the admin account. Seed protocols are optional and must be explicitly loaded.
|
**No default admin credentials exist.** A fresh deployment has an empty database. The first registration creates the admin account. Seed protocols are optional and must be explicitly loaded.
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ cd protocol-droid
|
|||||||
php -S localhost:8000
|
php -S localhost:8000
|
||||||
```
|
```
|
||||||
|
|
||||||
The SQLite database is created automatically at `data/protocol_droid.db`. The first registration creates the admin account. To load the 6 sample protocols (Round Robin Check-In, Consent Decision-Making, etc.):
|
The SQLite database is created automatically at `data/protocol_droid.db`. The first registration creates the admin account. To load the 100 sample protocols, go to your **profile page** (click your @username) and click **"Load Seed Protocols"** (admins only).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# After registering as admin:
|
# After registering as admin:
|
||||||
|
|||||||
+53
-14
@@ -63,7 +63,6 @@ async function init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function route() {
|
function route() {
|
||||||
isRouting = true;
|
|
||||||
const hash = location.hash.slice(1) || 'library';
|
const hash = location.hash.slice(1) || 'library';
|
||||||
if (hash.startsWith('protocols/')) {
|
if (hash.startsWith('protocols/')) {
|
||||||
showDetail(hash.split('/')[1]);
|
showDetail(hash.split('/')[1]);
|
||||||
@@ -82,15 +81,14 @@ function route() {
|
|||||||
} else {
|
} else {
|
||||||
navigate('library');
|
navigate('library');
|
||||||
}
|
}
|
||||||
isRouting = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let searchTimer = null;
|
let searchTimer = null;
|
||||||
let currentRoute = '';
|
let currentRoute = '';
|
||||||
let isRouting = false;
|
|
||||||
|
|
||||||
function navigate(view) {
|
function navigate(view) {
|
||||||
if (currentRoute === view && view !== 'library') return;
|
// Prevent double navigation from hashchange
|
||||||
|
if (currentRoute === view) return;
|
||||||
currentRoute = view;
|
currentRoute = view;
|
||||||
document.querySelectorAll('.view').forEach(v => v.style.display = 'none');
|
document.querySelectorAll('.view').forEach(v => v.style.display = 'none');
|
||||||
document.getElementById('view-' + view).style.display = 'block';
|
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));
|
document.querySelectorAll('.mobile-panel a').forEach(a => a.classList.toggle('active', a.dataset.view === view));
|
||||||
closeMenu();
|
closeMenu();
|
||||||
updateBreadcrumb(view);
|
updateBreadcrumb(view);
|
||||||
if (!isRouting) {
|
if (view === 'library') { location.hash = 'library'; loadProtocols(); }
|
||||||
if (view === 'library') { location.hash = 'protocols'; }
|
if (view === 'collections') { location.hash = 'collections'; loadCollections(); }
|
||||||
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 === 'create') { editingSlug = null; resetForm(); }
|
if (view === 'create') { editingSlug = null; resetForm(); }
|
||||||
if (view === 'create-collection') { editingCollectionSlug = null; resetCollectionForm(); }
|
if (view === 'create-collection') { editingCollectionSlug = null; resetCollectionForm(); }
|
||||||
if (view === 'about') updateAboutPage();
|
if (view === 'about') { location.hash = 'about'; }
|
||||||
window.scrollTo(0, 0);
|
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>';
|
'<div class="info"><h3>' + escapeHtml(col.title) + '</h3><p>' + escapeHtml(col.description||'') + '</p></div></div>';
|
||||||
}).join('') +
|
}).join('') +
|
||||||
'</div>' : '');
|
'</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) {
|
} catch (e) {
|
||||||
document.getElementById('detailContent').innerHTML = '<div class="empty">User not found</div>';
|
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 ---
|
// --- Toast ---
|
||||||
let toastTimer = null;
|
let toastTimer = null;
|
||||||
function toast(msg, isError) {
|
function toast(msg, isError) {
|
||||||
|
|||||||
Reference in New Issue
Block a user