import { apiPatch, apiPost, invalidateSessionCheck } from './api.js'; import { getRole, getUser, showToast, formatRoleLabel } from './app.js'; /** @typedef {'admin_reset' | 'self'} PasswordModalMode */ /** @type {{ mode: PasswordModalMode, userID?: string, username?: string, role?: string } | null} */ let modalContext = null; function esc(s) { const d = document.createElement('div'); d.textContent = s ?? ''; return d.innerHTML; } function roleLabel(role) { return formatRoleLabel(role); } function signInHintForRole(role) { if (role === 'coach') { return 'Counselors sign in through the mobile app and will be prompted to choose a new password there.'; } return 'Supervisors sign in through this website and will be prompted to choose a new password at login.'; } function ensurePasswordModal() { let modal = document.getElementById('passwordModal'); if (modal) return modal; modal = document.createElement('div'); modal.id = 'passwordModal'; modal.className = 'modal-overlay'; modal.hidden = true; modal.setAttribute('role', 'dialog'); modal.setAttribute('aria-modal', 'true'); modal.setAttribute('aria-labelledby', 'passwordModalTitle'); modal.innerHTML = ` `; document.body.appendChild(modal); modal.addEventListener('click', (e) => { if (e.target === modal) closePasswordModal(); }); modal.querySelector('[data-password-cancel]').addEventListener('click', closePasswordModal); modal.querySelector('[data-password-submit]').addEventListener('click', submitPasswordModal); modal.querySelector('#pw_new_password_confirm').addEventListener('keydown', (e) => { if (e.key === 'Enter') submitPasswordModal(); }); document.addEventListener('keydown', (e) => { const el = document.getElementById('passwordModal'); if (!el || el.hidden) return; if (e.key === 'Escape') closePasswordModal(); }); return modal; } /** * @param {{ mode: PasswordModalMode, userID?: string, username?: string, role?: string }} options */ function openPasswordModal(options) { modalContext = options; const modal = ensurePasswordModal(); const isSelf = options.mode === 'self'; const username = options.username || getUser(); const role = options.role || getRole(); modal.querySelector('#passwordModalTitle').textContent = isSelf ? 'Change your password' : 'Reset password'; modal.querySelector('#passwordModalSubtitle').innerHTML = isSelf ? `Choose a new password for ${esc(username)} (${esc(roleLabel(role))}).` : `Set a new password for ${esc(username)} (${esc(roleLabel(role))}).`; modal.querySelector('#passwordModalHint').textContent = isSelf ? 'You will stay signed in after saving. Use this password on your next login.' : signInHintForRole(role); modal.querySelector('#passwordModalTempHint').style.display = isSelf ? 'none' : ''; modal.querySelector('#passwordModalOldGroup').style.display = isSelf ? '' : 'none'; modal.querySelector('#pw_old_password').value = ''; modal.querySelector('#pw_new_password').value = ''; modal.querySelector('#pw_new_password_confirm').value = ''; const errEl = modal.querySelector('#passwordModalError'); errEl.style.display = 'none'; errEl.textContent = ''; modal.querySelector('[data-password-submit]').textContent = isSelf ? 'Change password' : 'Set password'; modal.hidden = false; document.body.classList.add('modal-open'); (isSelf ? modal.querySelector('#pw_old_password') : modal.querySelector('#pw_new_password')).focus(); } export function closePasswordModal() { const modal = document.getElementById('passwordModal'); if (modal) modal.hidden = true; document.body.classList.remove('modal-open'); modalContext = null; } export function openAdminResetPasswordModal({ userID, username, role }) { openPasswordModal({ mode: 'admin_reset', userID, username, role }); } export function openChangeOwnPasswordModal() { openPasswordModal({ mode: 'self' }); } function showModalError(el, msg) { el.textContent = msg; el.style.display = ''; } async function submitPasswordModal() { if (!modalContext) return; const modal = document.getElementById('passwordModal'); const errEl = modal.querySelector('#passwordModalError'); errEl.style.display = 'none'; const newPassword = modal.querySelector('#pw_new_password').value; const confirm = modal.querySelector('#pw_new_password_confirm').value; if (newPassword.length < 6) { showModalError(errEl, 'Password must be at least 6 characters'); return; } if (newPassword !== confirm) { showModalError(errEl, 'Passwords do not match'); return; } const submitBtn = modal.querySelector('[data-password-submit]'); submitBtn.disabled = true; const prevLabel = submitBtn.textContent; submitBtn.textContent = 'Saving…'; try { if (modalContext.mode === 'self') { const oldPassword = modal.querySelector('#pw_old_password').value; if (!oldPassword) { showModalError(errEl, 'Current password is required'); return; } const data = await apiPost('auth/change-password', { username: getUser(), old_password: oldPassword, new_password: newPassword, }); if (!data.success) throw new Error(data.error || 'Failed to change password'); if (data.token) localStorage.setItem('qdb_token', data.token); if (data.user) localStorage.setItem('qdb_user', data.user); if (data.role) localStorage.setItem('qdb_role', data.role); invalidateSessionCheck(); closePasswordModal(); showToast('Your password was changed', 'success'); return; } const { userID, username } = modalContext; const data = await apiPatch('users.php', { userID, password: newPassword, mustChangePassword: 1, }); if (!data.success) throw new Error(data.error || 'Failed to reset password'); closePasswordModal(); showToast( `Temporary password set for "${username}" — they must change it on next sign-in`, 'success' ); document.dispatchEvent(new CustomEvent('qdb:password-reset', { detail: { userID, mustChangePassword: data.mustChangePassword ?? 1 }, })); } catch (e) { showModalError(errEl, e.message); } finally { submitBtn.disabled = false; submitBtn.textContent = prevLabel; } }