1<!DOCTYPE html> 2<html lang="en"> 3 <head> 4 <meta charset="utf-8"> 5 <title>Account management – CatholicLibrary.org</title> 6 <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 <link rel="stylesheet" type="text/css" href="/assets/css/library.css"></link> 8 <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /> 9
9<script src="/assets/js/auth.js"></script>
9 10
10<script defer src="/assets/js/library.js?v=4"></script>
10 11 </head> 12 <body> 13 <img id="libimg" alt="Catholic Library Home" src="/images/cathlib2.png" style="height:44px;margin:0; position:fixed; top:0; left:0;" /> 14 <header id="header" class="header"> 15<nav> 16<ul> 17 <li><a href="/library/search">Search</a></li> 18 <li><span id='browse'>Browse</span> 19 <ul> 20 <li><a href="/library/browse">Centuries</a></li> 21 <li><a href="/library/browse/name/a">Authors</a></li> 22 <li><a href="/library/search?&sort=auth-year&smode=universal&label=doctor">Doctors</a></li> 23 <li><a href="/library/search?&sort=auth-year&smode=universal&label=father">Fathers</a></li> 24 <li><a href="/library/search?&sort=auth-year&smode=universal&label=pucmagisterium">Magisterium</a></li> 25 <li><a href="/library/search?&sort=auth-year&smode=universal&label=reftext">Reference</a></li> 26 </ul> 27 </li> 28 <li><a href="/bible/search">Scripture</a></li> 29 <li><a href="/art">Art</a></li> 30 <li><span class='loggedout'>Register / Login</span><span class='loggedin'>Account</span> 31 <ul> 32 <li id="nav_login" class='loggedout'><a href="/clauth/login">Login</a></li> 33 <li><a href="/userprefs.php">Preferences</a></li> 34 <li><a href="/notes.php">Notes</a></li> 35 <li class='loggedout'><a href="/clauth/registration">Register</a></li> 36 <li class='loggedin'><a href="/clauth/manage_account">Account management</a></li> 37 <li class='loggedin'><a href="/clauth/logout">Log out</a></li> 38 <li class='loggedout'><a href="/institutions">Catholic Organizations</a></li> 39 </ul> 40 </li> 41 <li><a href="/institutions/sponsor.php">Sponsor us</a></li> 42 <li><span>...</span> 43 <ul> 44 <li><a href="/prayers/">Prayers and Devotions</a></li> 45<li><a href="/about/Catholic_Library">About CatholicLibrary.org</a></li> 46 <li><a href="/about/Catholic_Library/updates.php">Latest updates</a></li> 47 <li><a href="/about/Prof_Seth_Cherney">About Seth Cherney</a></li> 48 <li><a href="/help/">Site Help</a></li> 49 <li><a href="/terms/">Terms</a></li> 50 <li><a href="/privacy/">Privacy</a></li> 51 <!-- <li><a href="/institutions/parishes.php">Parishes</a></li> 52 <li><a href="/institutions/libraries.php">Libraries / MARC</a></li> 53 <li><a href="/institutions/universities.php">Universities</a></li> 54 <li><a href="/institutions/schools.php">Schools</a></li> --> 55 </ul> 56 </li> 57</ul> 58</nav> 59</header> <h1>Account Management</h1> 60 <div id="status-msg"></div> 61 62 <div id="reset-password-form" class="card" style="display:none;"> 63 <h3>Set New Password</h3> 64 <input type="password" id="new_password" placeholder="New Password"> 65 <button onclick="completeReset()" class="btn">Update Password</button> 66 </div> 67 68 <div id="loading">Loading...</div> 69 70 <div id="main-content" style="display:none;"> 71 <div class="card"> 72 <h3>Security</h3> 73 <p>Update your password by requesting a secure link.</p> 74 <button onclick="handleAction('reset_password_request')" class="btn btn-outline">Send Password Reset Link</button> 75 </div> 76 77 <div id="billing-section" class="card" style="display:none;"> 78 <h3>Billing & Subscription</h3> 79 <div id="billing-auth-lock"> 80 <p>To access billing, please re-enter your password for security.</p> 81 <input type="password" id="confirm_pass_billing" placeholder="Account Password"> 82 <button onclick="getBillingAccess()" class="btn">Confirm & Manage Billing</button> 83 </div> 84 <div id="billing-link-container" style="display:none;"> 85 <p>Access granted. You can now manage your subscription on Stripe.</p> 86 <a href="#" id="stripe-portal-btn" class="btn">Open Stripe Billing Portal</a> 87 </div> 88 </div> 89 90 <div class="card"> 91 <h3>Site preferences</h3> 92 <button onclick="window.location.href = '/userprefs.php';" class="btn btn-outline">Set your preferences</button> 93 </div> 94 95 <div id="family-section" class="card" style="display:none;"> 96 <h3>Family Emails - allows free registration</h3> 97 <p> They will receive an email with instructions to register for free. If you want to register for your kids, use "plus notation" like [email protected] etc, and you will get the email in your inbox. If they are old enough, they will want to do this for themselves so they don't need to worry their notes are not private.</p> 98 <p>It is not strictly immediate family. Friends are fine too!</p> 99 <div id="family-inputs"></div> 100 <button onclick="updateFamily()" class="btn btn-black" style="margin-top:15px;">Save Family List</button> 101 </div> 102 <div id="group-section" class="card" style="display:none;"> 103 <h3>Group Management</h3> 104 <p style="margin: 0 0 12px 0;"> 105 <strong>Your Group Code:</strong>
106 <span id="display-group-code" style="font-family: monospace; padding: 4px 8px; border-radius: 5px; border: 1px solid #ccc; line-height: 24px;"></span> 107 <button onclick="copyGroupCode(event)" class="btn-outline" style="width: auto; padding: 4px 10px; margin-left: 10px; font-size: 12px; line-height: 24px; border-radius: 5px; display: inline-block;">Copy</button> 108 </p> 109 <strong>Existing Members:</strong> 110 <ul id="group-list" style="list-style: none; padding: 0;"></ul> 111 112 <hr style="border: 0; border-top: 1px solid #eee; margin: 20px 0;"> 113 114 <p style="margin: 0 0 12px 0;"><b>Bulk Add Members</b> (One email per line). New members receive an email with intructions to complete their signup. You don't need to send them the code.</p> 115 <textarea id="group-bulk-input" rows="5" style="width:100%; border: 1px solid #ddd; border-radius: 4px; padding: 10px;"></textarea> 116 <button onclick="bulkAddGroup()" class="btn btn-black" style="margin-top:10px;">Add Members</button> 117 </div> 118 <div id="institution-section" class="card" style="display:none;"> 119 <h3>Institution Authentication</h3> 120 <p><small>Your users can use the library's advanced features for free by registering with these email domains.</small></p> 121 122 <!-- <div style="margin-bottom: 20px;"> 123 <label><strong>Authorized IP Ranges (CIDR notation, max 5)</strong></label> 124 <div id="inst-ip-container"></div> 125 </div> --> 126 127 <div style="margin-bottom: 20px;"> 128 <label><strong>Authorized Email Domains</strong></label> 129 <div id="inst-domain-container"></div> 130 </div> 131 132 <button onclick="updateInstitution()" class="btn btn-black">Save Institution Settings</button> 133 </div> 134 <p><a onclick="logout()" style="color: #8898aa; text-decoration: none; cursor: pointer;">← Logout</a></p> 135 </div><!-- end main content --> 136 <div id="guest-content" style="display:none;"> 137 <div class="card"> 138 <h3>Patron Access</h3> 139 <button onclick="login()" class="btn">Login</button> 140 <p style="text-align: center;">or</p> 141 <button onclick="window.location.href='/clauth/forgot_password.php';" class="btn">Reset Password</button> 142 <!-- <p style="text-align: center;">or</p> 143 <input type="email" id="lookup_email" placeholder="Email address"> 144 <button onclick="handleAction('patron_lookup')" class="btn">Email Stripe Link</button> --> 145 146 </div> 147 </div> 148 <style> 149 body { font-family: -apple-system, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 40px auto; padding: 20px; background: #f4f7f9; } 150 .card { border: 1px solid #e3e8ee; padding: 30px; border-radius: 8px; background: #fff; box-shadow: 0 2px 5px rgba(0,0,0,0.05); margin-bottom: 20px; } 151 .btn { background: #635bff; color: white; padding: 12px 20px; border-radius: 5px; border: none; font-size: 15px; cursor: pointer; display: inline-block; width: 100%; box-sizing: border-box; font-weight: 600; text-align: center; } 152 .btn-black { background: #24292e; } 153 .btn-outline { background: #fff; color: #635bff; border: 1px solid #635bff; } 154 input { width: 100%; padding: 12px; margin: 8px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; } 155 .error { color: #d93025; background: #fce8e6; padding: 15px; border-radius: 4px; margin-bottom: 20px; border: 1px solid #f1c4c2; } 156 .success { color: #0d652d; background: #e6f4ea; padding: 15px; border-radius: 4px; margin-bottom: 20px; border: 1px solid #c2e7cd; } 157 label { font-size: 0.85em; font-weight: bold; color: #666; text-transform: uppercase; display: block; margin-top: 10px; } 158 h3 { margin-top: 0; border-bottom: 1px solid #eee; padding-bottom: 10px; margin-bottom: 20px; } 159 </style> 160
160<script> 161const urlParams = new URLSearchParams(window.location.search); 162//const jwt = sessionStorage.getItem('cl_auth'); 163 164// THIS WILL BREAK if using httponly manage_account_handler has been updated so can change this to credientials:include ... 165const jwt = document.cookie.split('; ').find(row => row.startsWith('cl_auth='))?.split('=')[1] || null; 166 167async function init() { 168 if (urlParams.has('selector') && urlParams.has('token')) { 169 document.getElementById('loading').style.display = 'none'; 170 document.getElementById('reset-password-form').style.display = 'block'; 171 return; 172 } 173 174 try { 175 const res = await fetch('manage_account_handler.php?fetch=1', { 176 credentials: 'include' 177 }); 178 const data = await res.json(); 179 document.getElementById('loading').style.display = 'none'; 180 181 if (data.loggedIn) { 182 document.getElementById('main-content').style.display = 'block'; 183 if (!data.isMemberType || data.hasPatronage) { 184 document.getElementById('billing-section').style.display = 'block'; 185 } 186 if (data.isFamilyPlan) { 187 document.getElementById('family-section').style.display = 'block'; 188 let html = ''; 189 for (let i = 0; i < 7; i++) { 190 html += `<label>Member ${i+1}</label><input type="email" class="fam-email" value="${data.familyMembers[i] || ''}">`; 191 } 192 document.getElementById('family-inputs').innerHTML = html; 193 } else if (data.isGroupPlan) { 194 document.getElementById('group-section').style.display = 'block'; 195 document.getElementById('display-group-code').innerText = data.groupCode || 'No code assigned'; 196 renderGroupList(data.groupMembers); 197 } else if (data.isInstitution) { 198 document.getElementById('institution-section').style.display = 'block'; 199 // renderInstInputs('inst-ip-container', data.instIps, 'e.g. 192.168.1.0/24'); 200 renderInstInputs('inst-domain-container', data.instDomains, 'e.g. school.edu'); 201 } 202 } else { 203 document.getElementById('guest-content').style.display = 'block'; 204 } 205 } catch (e) { document.getElementById('loading').innerText = "Error loading page."; } 206} 207 208async function getBillingAccess() { 209 const password = document.getElementById('confirm_pass_billing').value.trim(); 210 console.log(password); 211 const res = await fetch('manage_account_handler.php', { 212 method: 'POST', 213 headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' }, 214 body: JSON.stringify({ action: 'get_billing_access', confirm_password: password }) 215 }); 216 const result = await res.json(); 217 if (result.success) { 218 document.getElementById('billing-auth-lock').style.display = 'none'; 219 document.getElementById('billing-link-container').style.display = 'block'; 220 document.getElementById('stripe-portal-btn').href = result.url; 221 } else { 222 showStatus(result.message, 'error'); 223 } 224} 225 226async function completeReset() { 227 const res = await fetch('manage_account_handler.php', { 228 method: 'POST', 229 headers: { 'Content-Type': 'application/json' }, 230 body: JSON.stringify({ 231 action: 'complete_password_reset', 232 selector: urlParams.get('selector'), 233 token: urlParams.get('token'), 234 new_password: document.getElementById('new_password').value 235 }) 236 }); 237 const result = await res.json(); 238 showStatus(result.message, result.success ? 'success' : 'error'); 239 if (result.success) document.getElementById('reset-password-form').style.display = 'none'; 240} 241 242async function updateFamily() { 243 const emails = Array.from(document.querySelectorAll('.fam-email')).map(i => i.value); 244 const res = await fetch('manage_account_handler.php', { 245 method: 'POST', 246 headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' }, 247 body: JSON.stringify({ action: 'update_family', family_emails: emails }) 248 }); 249 const result = await res.json(); 250 showStatus(result.message, result.success ? 'success' : 'error'); 251} 252 253// --- START GROUP FUNCTIONS --- 254function renderGroupList(members) { 255 const list = document.getElementById('group-list'); 256 list.innerHTML = members.map(m => ` 257 <li style="display:flex; justify-content:space-between; padding: 8px 0; border-bottom: 1px solid #f9f9f9;"> 258 ${m} <span onclick="removeMember('${m}')" style="color:red; cursor:pointer; font-weight:bold;">×</span> 259 </li> 260 `).join(''); 261} 262 263async function bulkAddGroup() { 264 const blob = document.getElementById('group-bulk-input').value; 265 const res = await fetch('manage_account_handler.php', { 266 method: 'POST', 267 headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' }, 268 body: JSON.stringify({ action: 'bulk_add_group', emails_blob: blob }) 269 }); 270 const result = await res.json(); 271 showStatus(result.message, result.success ? 'success' : 'error'); 272 if (result.success) setTimeout(() => location.reload(), 1500); 273} 274 275async function removeMember(email) { 276 if (!confirm(`Remove ${email}?`)) return; 277 const res = await fetch('manage_account_handler.php', { 278 method: 'POST', 279 headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' }, 280 body: JSON.stringify({ action: 'remove_group_member', member_email: email }) 281 }); 282 const result = await res.json(); 283 if (result.success) location.reload(); 284} 285 286async function handleAction(action) { 287 const res = await fetch('manage_account_handler.php', { 288 method: 'POST', 289 headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' }, 290 body: JSON.stringify({ action, lookup_email: document.getElementById('lookup_email')?.value }) 291 }); 292 const result = await res.json(); 293 showStatus(result.message, result.success ? 'success' : 'error'); 294} 295 296function showStatus(msg, type) { 297 document.getElementById('status-msg').innerHTML = `<div class="${type}">${msg}</div>`; 298 window.scrollTo(0,0); 299} 300function login() { 301 window.location.href = "login.php?redirect=" + btoa(window.location.href); 302} 303async function logout() { 304 await fetch('logout.php?mode=json'); 305 location.reload(); 306} 307function copyGroupCode(e) { 308 const code = document.getElementById('display-group-code').innerText; 309 if (!code || code === 'No code assigned') return; 310 311 navigator.clipboard.writeText(code).then(() => { 312 const btn = e.target; 313 const originalText = btn.innerText; 314 btn.innerText = 'Copied!'; 315 btn.style.color = '#0d652d'; 316 btn.style.borderColor = '#0d652d'; 317 318 setTimeout(() => { 319 btn.innerText = originalText; 320 btn.style.color = ''; 321 btn.style.borderColor = ''; 322 }, 2000); 323 }).catch(err => { 324 console.error('Failed to copy: ', err); 325 }); 326} 327 328 // --- START INSTITUTION JS --- 329function renderInstInputs(containerId, values, placeholder) { 330 const container = document.getElementById(containerId); 331 container.innerHTML = ''; 332 // Ensure we always show at least one blank row, up to 5 333 const rows = [...values]; 334 while(rows.length < 5) rows.push(''); 335 336 rows.forEach(val => { 337 const input = document.createElement('input'); 338 input.type = 'text'; 339 input.value = val; 340 input.placeholder = placeholder; 341 input.className = containerId === 'inst-ip-container' ? 'inst-ip-input' : 'inst-dom-input'; 342 input.style.marginBottom = '5px'; 343 container.appendChild(input); 344 }); 345} 346async function updateInstitution() { 347 const ips = Array.from(document.querySelectorAll('.inst-ip-input')).map(i => i.value); 348 const domains = Array.from(document.querySelectorAll('.inst-dom-input')).map(i => i.value); 349 350 const res = await fetch('manage_account_handler.php', { 351 method: 'POST', 352 headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' }, 353 body: JSON.stringify({ action: 'update_institution', ips, domains }) 354 }); 355 const result = await res.json(); 356 showStatus(result.message, result.success ? 'success' : 'error'); 357} 358 359 init(); 360 </script>
360 361</body> 362</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.