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_navigation = await _require('/_lib/get_navigation.jst')
11 let get_session = await _require('/_lib/get_session.jst')
12 let icon_cross = await env.site.get_min_svg('/_svg/icon_cross.svg')
13 let icon_tick = await env.site.get_min_svg('/_svg/icon_tick.svg')
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
42 await transaction.commit()
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.'}
230 let transaction = await env.site.database.Transaction()
232 let root = await transaction.get()
233 let p = await get_navigation(root, env.component_names)
234 let children = await p.get('children')
235 let menu = await p.get_json('menu')
238 for (let i = 0; i < menu.length; ++i) {
240 let q = await children.get(name)
243 a.nav-link(href=`${name}/index.html`) {
244 table.icon-and-text {
248 await env.site.get_min_svg(await q.get_json('icon'))
253 `${await q.get_json('title')}`
264 transaction.rollback()
270 console.log('details', details)
272 // this will be called by navbar logic after sign in/out
273 function sign_in_out(status) {
274 window.location.reload()
275 return true // suppresses status/dialog
279 if (signed_in_as !== undefined) {
280 //script(src="/js/utils.js") {}
283 document.addEventListener(
286 let id_accordion = document.getElementById('accordion')
287 let id_card_1 = document.getElementById('card-1')
288 let id_card_1_collapse = document.getElementById('card-1-collapse')
289 let id_card_1_cross = document.getElementById('card-1-cross')
290 let id_card_1_form = document.getElementById('card-1-form')
291 let id_card_1_heading = document.getElementById('card-1-heading')
292 let id_card_1_icon = document.getElementById('card-1-icon')
293 let id_card_1_message = document.getElementById('card-1-message')
294 let id_card_1_revert = document.getElementById('card-1-revert')
295 let id_card_1_save = document.getElementById('card-1-save')
296 let id_card_1_spinner = document.getElementById('card-1-spinner')
297 let id_card_1_tick = document.getElementById('card-1-tick')
298 let id_card_2 = document.getElementById('card-2')
299 let id_card_2_clear = document.getElementById('card-2-clear')
300 let id_card_2_collapse = document.getElementById('card-2-collapse')
301 let id_card_2_cross = document.getElementById('card-2-cross')
302 let id_card_2_form = document.getElementById('card-2-form')
303 let id_card_2_heading = document.getElementById('card-2-heading')
304 let id_card_2_icon = document.getElementById('card-2-icon')
305 let id_card_2_message = document.getElementById('card-2-message')
306 let id_card_2_save = document.getElementById('card-2-save')
307 let id_card_2_spinner = document.getElementById('card-2-spinner')
308 let id_card_2_tick = document.getElementById('card-2-tick')
309 let id_contact_me = document.getElementById('contact-me')
310 let id_family_name = document.getElementById('family-name')
311 let id_given_names = document.getElementById('given-names')
312 let id_new_password = document.getElementById('new-password')
313 let id_old_password = document.getElementById('old-password')
315 // pass original values in from server side
316 let orig_details = ${JSON.stringify(details)}
318 // change details card
319 let input_semaphore = new BinarySemaphore(false)
323 await input_semaphore.acquire()
324 await new Promise(resolve => setTimeout(resolve, 3000))
325 input_semaphore.try_acquire()
327 '/api/account/change_details/set_draft.json',
328 id_given_names.value === orig_details.given_names &&
329 id_family_name.value === orig_details.family_name &&
330 id_contact_me.checked === orig_details.contact_me ?
333 given_names: id_given_names.value.slice(0, 256),
334 family_name: id_family_name.value.slice(0, 256),
335 contact_me: id_contact_me.checked
340 )() // ignore returned promise (start thread)
342 let card_1_edited = () => {
343 input_semaphore.release()
346 id_given_names.value === orig_details.given_names &&
347 id_family_name.value === orig_details.family_name &&
348 id_contact_me.checked === orig_details.contact_me
349 id_card_1_revert.disabled = disabled
350 id_card_1_save.disabled = disabled
351 id_card_1_icon.hidden = false
352 id_card_1_tick.hidden = true
353 id_card_1_cross.hidden = true
354 id_card_1_spinner.hidden = true
355 id_card_1_message.hidden = true
358 id_given_names.addEventListener('input', card_1_edited)
359 id_family_name.addEventListener('input', card_1_edited)
360 id_contact_me.addEventListener('input', card_1_edited)
362 id_card_1_revert.addEventListener(
365 id_given_names.value = orig_details.given_names
366 id_family_name.value = orig_details.family_name
367 id_contact_me.checked = orig_details.contact_me
369 // cut down form of card_1_edited() logic:
370 input_semaphore.release()
372 id_card_1_revert.disabled = true
373 id_card_1_save.disabled = true
374 id_card_1_icon.hidden = false
375 id_card_1_tick.hidden = true
376 id_card_1_cross.hidden = true
377 id_card_1_spinner.hidden = true
378 id_card_1_message.hidden = true
382 id_card_1_save.addEventListener(
385 id_card_1_icon.hidden = false
386 id_card_1_tick.hidden = true
387 id_card_1_cross.hidden = true
388 id_card_1_spinner.hidden = true
389 // the below causes an ugly flicker, so just keep the message
390 //id_card_1_message.hidden = true
392 if (!id_card_1_form.checkValidity()) {
393 id_card_1_form.classList.add('was-validated');
395 id_card_1_icon.hidden = true
396 id_card_1_cross.hidden = false
399 id_card_1_form.classList.remove('was-validated');
401 id_card_1_icon.hidden = true
402 id_card_1_spinner.hidden = false
405 '/api/account/change_details/set.json',
407 given_names: id_given_names.value.slice(0, 256),
408 family_name: id_family_name.value.slice(0, 256),
409 contact_me: id_contact_me.checked
414 let problem = Problem.from(error)
416 id_card_1_cross.hidden = false
417 id_card_1_spinner.hidden = true
419 id_card_1_message.textContent = problem.detail
420 //id_card_1_message.classList.remove('text-success')
421 id_card_1_message.classList.add('text-danger')
422 id_card_1_message.hidden = false
425 id_card_1_tick.hidden = false
426 id_card_1_spinner.hidden = true
428 orig_details.given_names = id_given_names.value
429 orig_details.family_name = id_family_name.value
430 orig_details.contact_me = id_contact_me.checked
432 // cut down form of card_1_edited() logic:
433 input_semaphore.release()
435 id_card_1_revert.disabled = true
436 id_card_1_save.disabled = true
437 //id_card_1_icon.hidden = false
438 //id_card_1_tick.hidden = true
439 //id_card_1_cross.hidden = true
440 //id_card_1_spinner.hidden = true
441 id_card_1_message.hidden = true
445 // change password card
446 let card_2_edited = () => {
448 id_old_password.value.length === 0 &&
449 id_new_password.value.length === 0
450 id_card_2_clear.disabled = disabled
451 id_card_2_save.disabled = disabled
452 id_card_2_icon.hidden = false
453 id_card_2_tick.hidden = true
454 id_card_2_cross.hidden = true
455 id_card_2_spinner.hidden = true
456 id_card_2_message.hidden = true
459 id_old_password.addEventListener('input', card_2_edited)
460 id_new_password.addEventListener('input', card_2_edited)
462 id_card_2_clear.addEventListener(
465 id_old_password.value = ''
466 id_new_password.value = ''
468 // cut down form of card_2_edited() logic:
469 id_card_2_clear.disabled = true
470 id_card_2_save.disabled = true
471 id_card_2_icon.hidden = false
472 id_card_2_tick.hidden = true
473 id_card_2_cross.hidden = true
474 id_card_2_spinner.hidden = true
475 id_card_2_message.hidden = true
479 id_card_2_save.addEventListener(
482 id_card_2_icon.hidden = false
483 id_card_2_tick.hidden = true
484 id_card_2_cross.hidden = true
485 id_card_2_spinner.hidden = true
486 // the below causes an ugly flicker, so just keep the message
487 //id_card_2_message.hidden = true
489 if (!id_card_2_form.checkValidity()) {
490 id_card_2_form.classList.add('was-validated');
492 id_card_2_icon.hidden = true
493 id_card_2_cross.hidden = false
496 id_card_2_form.classList.remove('was-validated');
498 id_card_2_icon.hidden = true
499 id_card_2_spinner.hidden = false
502 '/api/account/change_password.json',
503 id_old_password.value.slice(0, 256),
504 id_new_password.value.slice(0, 256)
508 let problem = Problem.from(error)
510 id_card_2_cross.hidden = false
511 id_card_2_spinner.hidden = true
513 id_card_2_message.textContent = problem.detail
514 //id_card_2_message.classList.remove('text-success')
515 id_card_2_message.classList.add('text-danger')
516 id_card_2_message.hidden = false
519 id_card_2_tick.hidden = false
520 id_card_2_spinner.hidden = true
522 id_old_password.value = ''
523 id_new_password.value = ''
525 // cut down form of card_2_edited() logic:
526 id_card_2_clear.disabled = true
527 id_card_2_save.disabled = true
528 //id_card_2_icon.hidden = false
529 //id_card_2_tick.hidden = true
530 //id_card_2_cross.hidden = true
531 //id_card_2_spinner.hidden = true
532 id_card_2_message.hidden = true