2 let breadcrumbs = await _require('/_lib/breadcrumbs.jst')
3 let fa_arrow_circle_left = await env.site.get_min_svg('/_svg/fa_arrow-circle-left.svg')
4 let fa_cloud_upload_alt = await env.site.get_min_svg('/_svg/fa_cloud-upload-alt.svg')
5 let fa_envelope = await env.site.get_min_svg('/_svg/fa_envelope.svg')
6 let fa_redo = await env.site.get_min_svg('/_svg/fa_redo.svg')
7 let fa_trash = await env.site.get_min_svg('/_svg/fa_trash.svg')
8 let get_placeholder = await _require('/_lib/get_placeholder.jst')
9 let get_account = await _require('/_lib/get_account.jst')
10 let get_session = await _require('/_lib/get_session.jst')
11 let icon_cross = await env.site.get_min_svg('/_svg/icon_cross.svg')
12 let icon_tick = await env.site.get_min_svg('/_svg/icon_tick.svg')
13 let menu = await env.site.get_menu('/my_account/_menu.json')
14 let navbar = await _require('/_lib/navbar.jst')
16 // preload draft details if any
17 let transaction = await env.site.database.Transaction()
19 let signed_in_as, details, change_details_draft
21 let root = await transaction.get({})
22 let session = await get_session(env, root)
24 placeholder = await get_placeholder(env, session)
26 signed_in_as = await session.get_json('signed_in_as')
28 account = await get_account(root, session)
30 account === undefined ?
33 given_names: await account.get_json('given_names'),
34 family_name: await account.get_json('family_name'),
35 contact_me: await account.get_json('contact_me')
38 change_details_draft = await session.get_json('change_details_draft')
39 if (change_details_draft === undefined || env.now >= change_details_draft.expires)
40 change_details_draft = null
45 transaction.rollback()
49 // cheat a little by ignoring the draft if it matches the original details,
50 // because user might have saved the details and left page before the timeout
52 change_details_draft &&
53 change_details_draft.given_names === details.given_names &&
54 change_details_draft.family_name === details.family_name &&
55 change_details_draft.contact_me === details.contact_me
57 change_details_draft = null
65 await breadcrumbs(env, _out)
67 if (signed_in_as !== undefined) {
69 p {'Your given names are visible to other users if you comment on our blog. Your email and family name remain private. If your name is one word or does not fit given names/family name pattern, then please enter given names only.'}
71 div.accordion#accordion(role="tablist" aria-multiselectable="true") {
73 div.card-header#card-1-heading(role="tab") {
74 a.h5(data-toggle="collapse" data-parent="#accordion" href="#card-1-collapse" aria-expanded="true" aria-controls="card-1-collapse") {
78 div#card-1-collapse.collapse.show(role="tabpanel" aria-labelledby="card-1-heading" data-parent="#accordion") {
84 label.form-label(for="given-names") {'Given names *'}
85 input.form-control#given-names(type="text" value=change_details_draft ? change_details_draft.given_names : details.given_names placeholder=placeholder.given_names required maxlength=256) {}
86 div.invalid-feedback {'Please enter a name we can address you by.'}
91 label.form-label(for="family-name") {'Family name'}
92 input.form-control#family-name(type="text" value=change_details_draft ? change_details_draft.family_name : details.family_name placeholder=placeholder.family_name maxlength=256) {}
98 div.custom-control.custom-checkbox {
99 if (change_details_draft ? change_details_draft.contact_me : details.contact_me)
100 input.custom-control-input#contact-me(type="checkbox" checked) {}
102 input.custom-control-input#contact-me(type="checkbox") {}
104 label.custom-control-label(for="contact-me") {
105 'Contact me by email with updates and special offers'
112 if (change_details_draft)
113 button.btn.btn-outline-secondary#card-1-revert(type="button") {
114 div.icon24-outer.mr-2 {
115 div.icon24-inner {_out.push(fa_trash)}
120 button.btn.btn-outline-secondary#card-1-revert(type="button" disabled) {
121 div.icon24-outer.mr-2 {
122 div.icon24-inner {_out.push(fa_trash)}
126 if (change_details_draft)
127 button.btn.btn-success.ml-3#card-1-save(type="button") {
128 div.icon24-outer.mr-2#card-1-icon {
129 div.icon24-inner {_out.push(fa_cloud_upload_alt)}
131 div.icon24-outer.mr-2#card-1-tick(hidden) {
132 div.icon24-inner {_out.push(icon_tick)}
134 div.icon24-outer.mr-2#card-1-cross(hidden) {
135 div.icon24-inner {_out.push(icon_cross)}
137 div.icon24-outer.mr-2#card-1-spinner(hidden) {
139 div.spinner-border.spinner-border-sm(role="status") {}
145 button.btn.btn-success.ml-3#card-1-save(type="button" disabled) {
146 div.icon24-outer.mr-2#card-1-icon {
147 div.icon24-inner {_out.push(fa_cloud_upload_alt)}
149 div.icon24-outer.mr-2#card-1-tick(hidden) {
150 div.icon24-inner {_out.push(icon_tick)}
152 div.icon24-outer.mr-2#card-1-cross(hidden) {
153 div.icon24-inner {_out.push(icon_cross)}
155 div.icon24-outer.mr-2#card-1-spinner(hidden) {
157 div.spinner-border.spinner-border-sm(role="status") {}
163 p.'mt-3'.mb-0#card-1-message(hidden) {}
168 div.card-header#card-2-heading(role="tab") {
169 a.h5.collapsed(data-toggle="collapse" data-parent="#accordion" href="#card-2-collapse" aria-expanded="false" aria-controls="card-2-collapse") {
173 div#card-2-collapse.collapse(role="tabpanel" aria-labelledby="step-2-heading" data-parent="#accordion") {
179 label.form-label(for="old-password") {'Old password *'}
180 input.form-control#old-password(type="password" placeholder="Verify" required minlength=8 maxlength=256) {}
181 div.invalid-feedback {'Please enter your account\'s password of at least 8 characters.'}
186 label.form-label(for="new-password") {'New password *'}
187 input.form-control#'new-password'(type="password" placeholder="Choose" required minlength=8 maxlength=256) {}
188 div.invalid-feedback {'Please choose a secure password of at least 8 characters.'}
194 button.btn.btn-outline-secondary#card-2-clear(type="button" disabled) {
195 div.icon24-outer.mr-2 {
196 div.icon24-inner {_out.push(fa_trash)}
200 button.btn.btn-success.ml-3#card-2-save(type="button" disabled) {
201 div.icon24-outer.mr-2#card-2-icon {
202 div.icon24-inner {_out.push(fa_cloud_upload_alt)}
204 div.icon24-outer.mr-2#card-2-tick(hidden) {
205 div.icon24-inner {_out.push(icon_tick)}
207 div.icon24-outer.mr-2#card-2-cross(hidden) {
208 div.icon24-inner {_out.push(icon_cross)}
210 div.icon24-outer.mr-2#card-2-spinner(hidden) {
212 div.spinner-border.spinner-border-sm(role="status") {}
218 p.'mt-3'.mb-0#card-2-message(hidden) {}
224 p.text-muted.mt-3 {'* These fields are required.'}
228 p {'For account maintenance, please click on one of the options below.'}
231 let entries = menu.entries
232 for (let i = 0; i < entries.length; ++i) {
233 let entry = entries[i]
234 if (Object.prototype.hasOwnProperty.call(entry, 'icon'))
236 a.nav-link(href=`${entry.dir}/index.html`) {
237 table.icon-and-text {
240 _out.push(await env.site.get_min_svg(entry.icon))
243 span.h2{`${entry.name}`}
255 console.log('details', details)
257 // this will be called by navbar logic after sign in/out
258 function sign_in_out(status) {
259 window.location.reload()
260 return true // suppresses status/dialog
264 if (signed_in_as !== undefined) {
265 //script(src="/js/utils.js") {}
268 document.addEventListener(
271 let id_accordion = document.getElementById('accordion')
272 let id_card_1 = document.getElementById('card-1')
273 let id_card_1_collapse = document.getElementById('card-1-collapse')
274 let id_card_1_cross = document.getElementById('card-1-cross')
275 let id_card_1_form = document.getElementById('card-1-form')
276 let id_card_1_heading = document.getElementById('card-1-heading')
277 let id_card_1_icon = document.getElementById('card-1-icon')
278 let id_card_1_message = document.getElementById('card-1-message')
279 let id_card_1_revert = document.getElementById('card-1-revert')
280 let id_card_1_save = document.getElementById('card-1-save')
281 let id_card_1_spinner = document.getElementById('card-1-spinner')
282 let id_card_1_tick = document.getElementById('card-1-tick')
283 let id_card_2 = document.getElementById('card-2')
284 let id_card_2_clear = document.getElementById('card-2-clear')
285 let id_card_2_collapse = document.getElementById('card-2-collapse')
286 let id_card_2_cross = document.getElementById('card-2-cross')
287 let id_card_2_form = document.getElementById('card-2-form')
288 let id_card_2_heading = document.getElementById('card-2-heading')
289 let id_card_2_icon = document.getElementById('card-2-icon')
290 let id_card_2_message = document.getElementById('card-2-message')
291 let id_card_2_save = document.getElementById('card-2-save')
292 let id_card_2_spinner = document.getElementById('card-2-spinner')
293 let id_card_2_tick = document.getElementById('card-2-tick')
294 let id_contact_me = document.getElementById('contact-me')
295 let id_family_name = document.getElementById('family-name')
296 let id_given_names = document.getElementById('given-names')
297 let id_new_password = document.getElementById('new-password')
298 let id_old_password = document.getElementById('old-password')
300 // pass original values in from server side
301 let orig_details = ${JSON.stringify(details)}
303 // change details card
304 let input_semaphore = new BinarySemaphore(false)
308 await input_semaphore.acquire()
309 await new Promise(resolve => setTimeout(resolve, 3000))
310 input_semaphore.try_acquire()
312 '/api/account/change_details/set_draft.json',
313 id_given_names.value === orig_details.given_names &&
314 id_family_name.value === orig_details.family_name &&
315 id_contact_me.checked === orig_details.contact_me ?
318 given_names: id_given_names.value.slice(0, 256),
319 family_name: id_family_name.value.slice(0, 256),
320 contact_me: id_contact_me.checked
325 )() // ignore returned promise (start thread)
327 let card_1_edited = () => {
328 input_semaphore.release()
331 id_given_names.value === orig_details.given_names &&
332 id_family_name.value === orig_details.family_name &&
333 id_contact_me.checked === orig_details.contact_me
334 id_card_1_revert.disabled = disabled
335 id_card_1_save.disabled = disabled
336 id_card_1_icon.hidden = false
337 id_card_1_tick.hidden = true
338 id_card_1_cross.hidden = true
339 id_card_1_spinner.hidden = true
340 id_card_1_message.hidden = true
343 id_given_names.addEventListener('input', card_1_edited)
344 id_family_name.addEventListener('input', card_1_edited)
345 id_contact_me.addEventListener('input', card_1_edited)
347 id_card_1_revert.addEventListener(
350 id_given_names.value = orig_details.given_names
351 id_family_name.value = orig_details.family_name
352 id_contact_me.checked = orig_details.contact_me
354 // cut down form of card_1_edited() logic:
355 input_semaphore.release()
357 id_card_1_revert.disabled = true
358 id_card_1_save.disabled = true
359 id_card_1_icon.hidden = false
360 id_card_1_tick.hidden = true
361 id_card_1_cross.hidden = true
362 id_card_1_spinner.hidden = true
363 id_card_1_message.hidden = true
367 id_card_1_save.addEventListener(
370 id_card_1_icon.hidden = false
371 id_card_1_tick.hidden = true
372 id_card_1_cross.hidden = true
373 id_card_1_spinner.hidden = true
374 // the below causes an ugly flicker, so just keep the message
375 //id_card_1_message.hidden = true
377 if (!id_card_1_form.checkValidity()) {
378 id_card_1_form.classList.add('was-validated');
380 id_card_1_icon.hidden = true
381 id_card_1_cross.hidden = false
384 id_card_1_form.classList.remove('was-validated');
386 id_card_1_icon.hidden = true
387 id_card_1_spinner.hidden = false
390 '/api/account/change_details/set.json',
392 given_names: id_given_names.value.slice(0, 256),
393 family_name: id_family_name.value.slice(0, 256),
394 contact_me: id_contact_me.checked
399 let problem = Problem.from(error)
401 id_card_1_cross.hidden = false
402 id_card_1_spinner.hidden = true
404 id_card_1_message.textContent = problem.detail
405 //id_card_1_message.classList.remove('text-success')
406 id_card_1_message.classList.add('text-danger')
407 id_card_1_message.hidden = false
410 id_card_1_tick.hidden = false
411 id_card_1_spinner.hidden = true
413 orig_details.given_names = id_given_names.value
414 orig_details.family_name = id_family_name.value
415 orig_details.contact_me = id_contact_me.checked
417 // cut down form of card_1_edited() logic:
418 input_semaphore.release()
420 id_card_1_revert.disabled = true
421 id_card_1_save.disabled = true
422 //id_card_1_icon.hidden = false
423 //id_card_1_tick.hidden = true
424 //id_card_1_cross.hidden = true
425 //id_card_1_spinner.hidden = true
426 id_card_1_message.hidden = true
430 // change password card
431 let card_2_edited = () => {
433 id_old_password.value.length === 0 &&
434 id_new_password.value.length === 0
435 id_card_2_clear.disabled = disabled
436 id_card_2_save.disabled = disabled
437 id_card_2_icon.hidden = false
438 id_card_2_tick.hidden = true
439 id_card_2_cross.hidden = true
440 id_card_2_spinner.hidden = true
441 id_card_2_message.hidden = true
444 id_old_password.addEventListener('input', card_2_edited)
445 id_new_password.addEventListener('input', card_2_edited)
447 id_card_2_clear.addEventListener(
450 id_old_password.value = ''
451 id_new_password.value = ''
453 // cut down form of card_2_edited() logic:
454 id_card_2_clear.disabled = true
455 id_card_2_save.disabled = true
456 id_card_2_icon.hidden = false
457 id_card_2_tick.hidden = true
458 id_card_2_cross.hidden = true
459 id_card_2_spinner.hidden = true
460 id_card_2_message.hidden = true
464 id_card_2_save.addEventListener(
467 id_card_2_icon.hidden = false
468 id_card_2_tick.hidden = true
469 id_card_2_cross.hidden = true
470 id_card_2_spinner.hidden = true
471 // the below causes an ugly flicker, so just keep the message
472 //id_card_2_message.hidden = true
474 if (!id_card_2_form.checkValidity()) {
475 id_card_2_form.classList.add('was-validated');
477 id_card_2_icon.hidden = true
478 id_card_2_cross.hidden = false
481 id_card_2_form.classList.remove('was-validated');
483 id_card_2_icon.hidden = true
484 id_card_2_spinner.hidden = false
487 '/api/account/change_password.json',
488 id_old_password.value.slice(0, 256),
489 id_new_password.value.slice(0, 256)
493 let problem = Problem.from(error)
495 id_card_2_cross.hidden = false
496 id_card_2_spinner.hidden = true
498 id_card_2_message.textContent = problem.detail
499 //id_card_2_message.classList.remove('text-success')
500 id_card_2_message.classList.add('text-danger')
501 id_card_2_message.hidden = false
504 id_card_2_tick.hidden = false
505 id_card_2_spinner.hidden = true
507 id_old_password.value = ''
508 id_new_password.value = ''
510 // cut down form of card_2_edited() logic:
511 id_card_2_clear.disabled = true
512 id_card_2_save.disabled = true
513 //id_card_2_icon.hidden = false
514 //id_card_2_tick.hidden = true
515 //id_card_2_cross.hidden = true
516 //id_card_2_spinner.hidden = true
517 id_card_2_message.hidden = true