From ffacdcbedbe724ea73317fbd46fc108bb16ab41f Mon Sep 17 00:00:00 2001 From: Daniel Onyejesi Date: Mon, 23 Mar 2026 20:37:35 -0400 Subject: [PATCH] Fix loading race condition, improve quiz UX, editor toolbar, settings page - Fix: component loader now fires tabChanged AFTER HTML is in DOM (fixes CMS/settings stuck at loading) - CMS: separate "New Article", "New Quiz", "New Pearl" buttons - CMS: editor toolbar with formatting buttons (bold, italic, headings, lists, links, tables, quotes, code, hr) - CMS: quiz questions have numbered headers (Q1, Q2...), larger inputs, clear "check correct answer" labels - CMS: validation prevents saving questions without correct answer marked - Quiz UX: large card-style question boxes with gradient number badges, bigger option buttons with hover effects and selection feedback, checkmark/cross icons on results - Settings: improved card spacing, input styling, section headers with bottom borders for visual hierarchy - Settings: updated compliance section (removed HIPAA references, added BAA/institution guidelines language) --- public/components/cms.html | 29 +++++++-- public/components/settings.html | 17 +++-- public/css/styles.css | 54 ++++++++++------ public/js/app.js | 9 ++- public/js/learningHub.js | 106 ++++++++++++++++++++++++-------- 5 files changed, 155 insertions(+), 60 deletions(-) diff --git a/public/components/cms.html b/public/components/cms.html index 39ad990..2c366ce 100644 --- a/public/components/cms.html +++ b/public/components/cms.html @@ -45,7 +45,11 @@
- +
+ + + +
- +
- - + +
+ + + + + + + + + + + + + + + +
+
diff --git a/public/components/settings.html b/public/components/settings.html index 4c69fe7..29e2066 100644 --- a/public/components/settings.html +++ b/public/components/settings.html @@ -78,20 +78,19 @@
- +
-

HIPAA & Privacy

+

Compliance & Usage

-

Current Status: This tool processes data through third-party AI APIs.

+

AWS Bedrock is available with a Business Associate Agreement (BAA) for HIPAA-eligible workloads.

  • ✅ All connections use HTTPS/TLS encryption
  • -
  • ✅ Authentication required
  • -
  • ✅ No patient data stored on server
  • -
  • ✅ 2FA available
  • -
  • ⚠️ OpenRouter does not currently offer BAA
  • -
  • ⚠️ For full HIPAA: use Azure OpenAI or AWS Bedrock with BAA
  • +
  • ✅ Authentication with optional 2FA
  • +
  • ✅ No patient data stored on server beyond session
  • +
  • ✅ AWS Bedrock supports BAA for HIPAA compliance
  • +
  • ✅ Azure OpenAI supports BAA for HIPAA compliance
-

Recommendation: Do not enter real PHI until your organization has executed BAAs with all AI providers.

+

Important: Check with your institution's guidelines and policies before use. This tool is not intended for production clinical use without proper organizational authorization and provider BAAs in place. Use with caution.

diff --git a/public/css/styles.css b/public/css/styles.css index 822e04c..cf0672f 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -439,12 +439,16 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(-- .shadess-flag-btn{font-size:11px;padding:2px 6px;} /* Settings page */ -.settings-page{display:flex;flex-direction:column;gap:16px;max-width:800px;} -.settings-page .settings-section{padding:20px;} -.settings-page .settings-section h3{font-size:16px;font-weight:700;color:var(--g800);margin-bottom:12px;display:flex;align-items:center;gap:8px;} -.settings-page .settings-section p{font-size:13px;color:var(--g600);margin-bottom:12px;} +.settings-page{display:flex;flex-direction:column;gap:0;max-width:800px;} +.settings-page .settings-section{padding:20px;margin-bottom:12px;border-radius:var(--radius);} +.settings-page .settings-section h3{font-size:16px;font-weight:700;color:var(--g800);margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid var(--g100);display:flex;align-items:center;gap:8px;} +.settings-page .settings-section p{font-size:13px;color:var(--g600);margin-bottom:12px;line-height:1.6;} .settings-page .hipaa-info ul{margin:10px 0 10px 20px;font-size:13px;} -.settings-page .hipaa-info li{margin-bottom:4px;} +.settings-page .hipaa-info li{margin-bottom:6px;line-height:1.5;} +.settings-page .form-group{margin-bottom:14px;} +.settings-page .form-group label{font-size:13px;font-weight:600;color:var(--g600);margin-bottom:4px;display:block;} +.settings-page .form-group input{width:100%;padding:8px 12px;border:1.5px solid var(--g300);border-radius:6px;font-size:13px;box-sizing:border-box;} +.settings-page .form-group input:focus{border-color:var(--blue);outline:none;box-shadow:0 0 0 2px var(--blue-light);} /* Inline load popover — compact dropdown, right-aligned under Load button */ .enc-load-popover{position:absolute;right:0;top:calc(100% + 4px);width:360px;max-width:calc(100vw - 24px);background:white;border:1px solid var(--g300);border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,0.15);z-index:600;display:flex;flex-direction:column;} @@ -531,20 +535,23 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(-- .lh-content-body th,.lh-content-body td{padding:8px 12px;border:1px solid var(--g200);font-size:13px;} .lh-content-body th{background:var(--g50);font-weight:600;} -/* Quiz */ -.lh-quiz-q{margin-bottom:20px;padding-bottom:16px;border-bottom:1px solid var(--g100);} -.lh-quiz-q:last-child{border-bottom:none;margin-bottom:0;} -.lh-quiz-q-header{display:flex;gap:8px;align-items:center;margin-bottom:6px;} -.lh-quiz-q-num{font-size:12px;font-weight:700;color:var(--blue);background:var(--blue-light);padding:2px 8px;border-radius:6px;} -.lh-quiz-q-type{font-size:11px;color:var(--g400);} -.lh-quiz-q-text{font-size:14px;font-weight:500;margin-bottom:10px;color:var(--g800);} -.lh-quiz-options{display:flex;flex-direction:column;gap:6px;} -.lh-quiz-option{display:flex;align-items:center;gap:10px;padding:10px 14px;border:1.5px solid var(--g200);border-radius:8px;cursor:pointer;transition:all 0.15s;font-size:13px;} -.lh-quiz-option:hover{border-color:var(--blue-light);background:var(--g50);} -.lh-quiz-option input[type="radio"]{accent-color:var(--blue);width:16px;height:16px;} +/* Quiz — large, visual question cards */ +.lh-quiz-q{margin-bottom:24px;padding:20px;background:white;border:1.5px solid var(--g200);border-radius:12px;box-shadow:0 1px 4px rgba(0,0,0,0.04);} +.lh-quiz-q:last-child{margin-bottom:0;} +.lh-quiz-q-header{display:flex;gap:10px;align-items:center;margin-bottom:10px;} +.lh-quiz-q-num{font-size:13px;font-weight:700;color:white;background:linear-gradient(135deg,var(--blue),var(--purple));padding:4px 12px;border-radius:8px;min-width:32px;text-align:center;} +.lh-quiz-q-type{font-size:11px;color:var(--g400);background:var(--g100);padding:2px 8px;border-radius:4px;} +.lh-quiz-q-text{font-size:16px;font-weight:600;margin-bottom:14px;color:var(--g800);line-height:1.5;} +.lh-quiz-options{display:flex;flex-direction:column;gap:8px;} +.lh-quiz-option{display:flex;align-items:center;gap:12px;padding:14px 18px;border:2px solid var(--g200);border-radius:10px;cursor:pointer;transition:all 0.2s;font-size:14px;line-height:1.4;background:white;} +.lh-quiz-option:hover{border-color:var(--blue);background:var(--blue-light);transform:translateY(-1px);box-shadow:0 2px 8px rgba(37,99,235,0.1);} +.lh-quiz-option input[type="radio"]{accent-color:var(--blue);width:18px;height:18px;flex-shrink:0;} .lh-quiz-option span{flex:1;} -.lh-opt-correct{border-color:var(--green) !important;background:var(--green-light) !important;} -.lh-opt-wrong{border-color:var(--red) !important;background:var(--red-light) !important;} +.lh-opt-correct{border-color:var(--green) !important;background:var(--green-light) !important;box-shadow:0 0 0 2px rgba(16,185,129,0.2) !important;} +.lh-opt-correct::after{content:'\\2713';position:absolute;right:14px;color:var(--green);font-weight:700;font-size:18px;} +.lh-quiz-option.lh-opt-correct,.lh-quiz-option.lh-opt-wrong{position:relative;} +.lh-opt-wrong{border-color:var(--red) !important;background:var(--red-light) !important;box-shadow:0 0 0 2px rgba(239,68,68,0.2) !important;} +.lh-opt-wrong::after{content:'\\2717';position:absolute;right:14px;color:var(--red);font-weight:700;font-size:18px;} /* Quiz results */ .lh-result-item{margin-bottom:14px;padding-bottom:14px;border-bottom:1px solid var(--g100);} @@ -556,7 +563,7 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(-- .lh-expl-general{background:var(--blue-light);color:#1e40af;} /* CMS question builder */ -.lh-question-block{padding:12px;border:1px solid var(--g200);border-radius:8px;margin-bottom:8px;background:var(--g50);} +.lh-question-block{padding:16px;border:1.5px solid var(--g200);border-radius:10px;margin-bottom:12px;background:white;box-shadow:0 1px 3px rgba(0,0,0,0.04);transition:outline 0.2s;} /* ============================================================ CONTENT MANAGER (WordPress-like CMS) @@ -628,6 +635,13 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(-- .cms-quiz-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;} .cms-quiz-header h3{margin:0;font-size:15px;color:var(--g700);} +/* Editor toolbar */ +.cms-editor-toolbar{display:flex;gap:2px;padding:6px 8px;background:var(--g50);border:1px solid var(--g300);border-bottom:none;border-radius:8px 8px 0 0;flex-wrap:wrap;} +.cms-tb-btn{width:32px;height:28px;border:none;background:transparent;cursor:pointer;border-radius:4px;font-size:12px;color:var(--g600);display:flex;align-items:center;justify-content:center;font-family:inherit;font-weight:600;} +.cms-tb-btn:hover{background:var(--g200);color:var(--g800);} +.cms-tb-sep{width:1px;background:var(--g300);margin:2px 4px;align-self:stretch;} +.cms-body-section .cms-body-editor{border-radius:0 0 8px 8px;} + /* Mobile responsive */ @media(max-width:768px){ .cms-layout{grid-template-columns:1fr;} @@ -640,7 +654,7 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(-- .cms-stat{padding:8px 10px;} .cms-stat-value{font-size:18px;} } -.lh-option-row{display:flex;align-items:center;gap:6px;margin-bottom:4px;} +.lh-option-row{display:flex;align-items:center;gap:8px;margin-bottom:6px;padding:6px 8px;border-radius:6px;background:var(--g50);} @media(max-width:640px){ .lh-feed-item-header{flex-direction:column;gap:6px;} diff --git a/public/js/app.js b/public/js/app.js index 12a2a19..9265564 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -74,12 +74,15 @@ document.addEventListener('DOMContentLoaded', function() { var tabEl = document.getElementById(tabName + '-tab'); if (tabEl) { tabEl.classList.add('active'); - // Lazy-load component HTML if not yet loaded - loadComponent(tabEl); + // Lazy-load component HTML, then fire tabChanged after DOM is ready + loadComponent(tabEl).then(function() { + document.dispatchEvent(new CustomEvent('tabChanged', { detail: { tab: tabName } })); + }); + } else { + document.dispatchEvent(new CustomEvent('tabChanged', { detail: { tab: tabName } })); } // Persist last tab in localStorage (restored after login in auth.js) try { localStorage.setItem('ped_last_tab', tabName); } catch(e) {} - document.dispatchEvent(new CustomEvent('tabChanged', { detail: { tab: tabName } })); // Close sidebar on mobile after tab click var sidebar = document.getElementById('sidebar'); if (sidebar && window.innerWidth <= 768) { diff --git a/public/js/learningHub.js b/public/js/learningHub.js index 983f9d6..615ac72 100644 --- a/public/js/learningHub.js +++ b/public/js/learningHub.js @@ -56,12 +56,18 @@ // ── CMS events ───────────────────────────── if (e.target.closest('#btn-lh-add-cat')) { addCategory(); return; } - if (e.target.closest('#btn-lh-new-content')) { openEditor(null); return; } + if (e.target.closest('#btn-lh-new-content')) { openEditor(null, 'article'); return; } + if (e.target.closest('#btn-lh-new-quiz')) { openEditor(null, 'quiz'); return; } + if (e.target.closest('#btn-lh-new-pearl')) { openEditor(null, 'pearl'); return; } if (e.target.closest('#btn-lh-close-editor')) { closeEditor(); return; } if (e.target.closest('#btn-lh-save-content')) { saveContent(); return; } if (e.target.closest('#btn-lh-delete-content')) { deleteContent(); return; } if (e.target.closest('#btn-lh-add-question')) { addQuestionBlock(); return; } + // Editor toolbar buttons + var tbBtn = e.target.closest('.cms-tb-btn'); + if (tbBtn && tbBtn.dataset.insert) { insertTag(tbBtn.dataset.insert); return; } + // CMS content item edit var cmsItem = e.target.closest('.lh-cms-content-item'); if (cmsItem && cmsItem.dataset.id) { openEditor(cmsItem.dataset.id); return; } @@ -554,7 +560,33 @@ renderCmsContentList(filtered); } - function openEditor(contentId) { + // Editor toolbar — insert HTML tags at cursor in textarea + function insertTag(tag) { + var ta = document.getElementById('lh-cms-edit-body'); + if (!ta) return; + var start = ta.selectionStart, end = ta.selectionEnd; + var sel = ta.value.substring(start, end); + var insert = ''; + switch(tag) { + case 'b': case 'i': case 'u': case 'code': case 'h2': case 'h3': + insert = '<' + tag + '>' + (sel || 'text') + ''; break; + case 'a': + var url = prompt('Enter URL:'); + if (!url) return; + insert = '' + (sel || 'link text') + ''; break; + case 'ul': insert = ''; break; + case 'ol': insert = '
    \n
  1. ' + (sel || 'item') + '
  2. \n
  3. \n
'; break; + case 'blockquote': insert = '
' + (sel || 'quote') + '
'; break; + case 'table': insert = '\n\n\n
Header 1Header 2
' + (sel || 'data') + '
'; break; + case 'hr': insert = '
'; break; + default: return; + } + ta.value = ta.value.substring(0, start) + insert + ta.value.substring(end); + ta.focus(); + ta.selectionStart = ta.selectionEnd = start + insert.length; + } + + function openEditor(contentId, contentType) { var editor = document.getElementById('lh-cms-editor'); var listView = document.getElementById('cms-list-view'); if (!editor) return; @@ -567,11 +599,16 @@ document.getElementById('lh-cms-edit-subject').value = ''; document.getElementById('lh-cms-edit-body').value = ''; document.getElementById('lh-cms-edit-category').value = ''; - document.getElementById('lh-cms-edit-type').value = 'article'; + document.getElementById('lh-cms-edit-type').value = contentType || 'article'; document.getElementById('lh-cms-edit-published').value = 'false'; document.getElementById('lh-cms-questions').innerHTML = ''; document.getElementById('btn-lh-delete-content').classList.add('hidden'); + // For quiz type, add a starter question + if (contentType === 'quiz' && !contentId) { + addQuestionBlock(); + } + if (!contentId) return; // Load existing content @@ -618,11 +655,14 @@ if (!title) { showToast('Title required', 'error'); return; } - // Gather questions + // Gather questions with validation var questions = []; - document.querySelectorAll('#lh-cms-questions .lh-question-block').forEach(function(qBlock) { + var validationError = false; + var qBlocks = document.querySelectorAll('#lh-cms-questions .lh-question-block'); + for (var qi = 0; qi < qBlocks.length; qi++) { + var qBlock = qBlocks[qi]; var qText = qBlock.querySelector('.lh-q-text').value.trim(); - if (!qText) return; + if (!qText) continue; var qType = qBlock.querySelector('.lh-q-type').value; var qExpl = qBlock.querySelector('.lh-q-explanation').value.trim(); var qId = qBlock.dataset.questionId || null; @@ -638,6 +678,17 @@ }); }); + // Validate: must have a correct answer marked + var hasCorrect = options.some(function(o) { return o.is_correct; }); + if (options.length > 0 && !hasCorrect) { + showToast('Question "' + qText.substring(0, 50) + '..." needs a correct answer marked', 'error'); + qBlock.style.outline = '2px solid var(--red)'; + qBlock.scrollIntoView({ behavior: 'smooth', block: 'center' }); + validationError = true; + break; + } + qBlock.style.outline = ''; + questions.push({ id: qId, question_text: qText, @@ -645,7 +696,8 @@ explanation: qExpl, options: options }); - }); + } + if (validationError) return; var payload = { title: title, body: body, category_id: category_id, subject: subject, content_type: content_type, published: published }; @@ -733,21 +785,28 @@ block.className = 'lh-question-block'; if (existingQ && existingQ.id) block.dataset.questionId = existingQ.id; + var qNum = container.querySelectorAll('.lh-question-block').length + 1; block.innerHTML = - '
' + - 'Question' + - '' + + '
' + + '
' + + 'Q' + qNum + '' + + '' + + '
' + + '' + '
' + - '' + - '
' + - '' + - '' + + '' + + '
' + + '' + '
' + '
' + - ''; + '' + + '
' + + '' + + '' + + '
'; container.appendChild(block); @@ -768,13 +827,12 @@ var row = document.createElement('div'); row.className = 'lh-option-row'; row.innerHTML = - '