diff --git a/README.md b/README.md index 3062850..93f992b 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ protocol-droid/ ``` 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** -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. @@ -122,7 +122,7 @@ cd protocol-droid 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 # After registering as admin: diff --git a/frontend/app.js b/frontend/app.js index f6fdf5e..671c314 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -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) { '

' + escapeHtml(col.title) + '

' + escapeHtml(col.description||'') + '

'; }).join('') + '' : ''); - document.getElementById('detailActions').innerHTML = ''; + // Add actions if viewing own profile + var actionsHtml = ''; + if (currentUser && currentUser.username === username) { + actionsHtml = ''; + if (currentUser.role === 'admin') { + actionsHtml += ''; + } + } + document.getElementById('detailActions').innerHTML = actionsHtml; } catch (e) { document.getElementById('detailContent').innerHTML = '
User not found
'; } } +// --- 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) {