4e88f56a9260f783f3478e40f2802a84f0023429
[ndcode_site.git] / my_account / index.html.jst
1 let XDate = require('xdate')
2
3 return async env => {
4   let breadcrumbs = await _require('/_lib/breadcrumbs.jst')
5   let icon_cross = await env.site.get_min_svg('/_svg/icon_cross.svg')
6   let icon_tick = await env.site.get_min_svg('/_svg/icon_tick.svg')
7   let menu = await env.site.get_menu('/my_account/_menu.json')
8   let navbar = await _require('/_lib/navbar.jst')
9   let get_session = await _require('/_lib/get_session.jst')
10
11   // see whether signed in, if so preload details, and draft details if any
12   let
13     transaction = await env.site.database.Transaction(),
14     details,
15     draft_details
16   try {
17     // initialize env.session_key, set cookie in env.response
18     let session = await get_session(env, transaction)
19     if (env.signed_in_as) {
20       account = await (
21         await (
22           await transaction.get({})
23         ).get('accounts')
24       ).get(env.signed_in_as)
25       details = {
26         given_names: await account.get_json('given_names'),
27         family_name: await account.get_json('family_name'),
28         contact_me: await account.get_json('contact_me')
29       }
30
31       let change_details_draft = await session.get('change_details_draft')
32       draft_details =
33         change_details_draft !== undefined &&
34           XDate.now() < await change_details_draft.get_json('expires') ?
35           {
36             given_names: await change_details_draft.get_json('given_names'),
37             family_name: await change_details_draft.get_json('family_name'),
38             contact_me: await change_details_draft.get_json('contact_me')
39           } :
40           null
41     }
42     await transaction.commit()
43   }
44   catch (error) {
45     transaction.rollback()
46     throw error
47   }
48   console.log(
49     'details',
50     JSON.stringify(details),
51     'draft_details',
52     JSON.stringify(draft_details)
53   )
54
55   await navbar(
56     env,
57     // head
58     async _out => {},
59     // body
60     async _out => {
61       await breadcrumbs(env, _out)
62
63       if (env.signed_in_as) {
64         // signed in
65         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.'}
66
67         div.accordion#accordion.mb-5(role="tablist" aria-multiselectable="true") {
68           div.card#step-1 {
69             div.card-header#step-1-heading(role="tab") {
70               span#step-1-tick(style="display: none;") {
71                 span.icon-color.pr-3 {_out.push(icon_tick)}
72               }
73               span#step-1-cross(style="display: none;") {
74                 span.icon-color.pr-3 {_out.push(icon_cross)}
75               }
76               //span#step-1-spinner(style="display: none;") {
77               //  span.icon-color.pr-3 {
78               //    div.spinner-border(role="status") {
79               //      span.sr-only {'Loading...'}
80               //    }
81               //  }
82               //}
83               a.h5(data-toggle="collapse" data-parent="#accordion" href="#step-1-collapse" aria-expanded="true" aria-controls="step-1-collapse") {
84                 'Change details'
85               }
86             }
87             div#step-1-collapse.collapse.show(role="tabpanel" aria-labelledby="step-1-heading" data-parent="#accordion") {
88               div.card-body {
89                 div.row {
90                   div.col-md-6 {
91                     div.form-group {
92                       label.form-label(for="given-names") {'Given names *'}
93                       input.form-control#given-names(type="text" value=draft_details ? draft_details.given_names : details.given_names placeholder="Your given names" required="required" maxlength=256) {}
94                     }
95                   }
96                   div.col-md-6 {
97                     div.form-group {
98                       label.form-label(for="family-name") {'Family name'}
99                       input.form-control#family-name(type="text" value=draft_details ? draft_details.family_name : details.family_name placeholder="Your family name" maxlength=256) {}
100                     }
101                   }
102                 }
103                 div.row.mb-3 {
104                   div.col-md-12 {
105                     div.custom-control.custom-checkbox {
106                       if (draft_details ? draft_details.contact_me : details.contact_me)
107                         input.custom-control-input#contact-me(type="checkbox" checked="checked") {}
108                       else
109                         input.custom-control-input#contact-me(type="checkbox") {}
110                       ' '
111                       label.custom-control-label(for="contact-me") {
112                         'Contact me by email with updates and special offers'
113                       }
114                     }
115                   }
116                 }
117
118                 if (draft_details)
119                   button.btn.btn-outline-secondary#step-1-revert(type="button") {'Revert'}
120                 else
121                   button.btn.btn-outline-secondary#step-1-revert(type="button" disabled="disabled") {'Revert'}
122                 if (draft_details)
123                   button.btn.btn-success.ml-3#step-1-save(type="button") {'Save'}
124                 else
125                   button.btn.btn-success.ml-3#step-1-save(type="button" disabled="disabled") {'Save'}
126
127                 p.'mt-3'.mb-0 {'* These fields are required.'}
128               }
129             }
130           }
131           div.card#step-2 {
132             div.card-header#step-2-heading(role="tab") {
133               span#step-2-tick(style="display: none;") {
134                 span.icon-color.pr-3 {_out.push(icon_tick)}
135               }
136               span#step-2-cross(style="display: none;") {
137                 span.icon-color.pr-3 {_out.push(icon_cross)}
138               }
139               span#step-2-spinner(style="display: none;") {
140                 span.icon-color.pr-3 {
141                   div.spinner-border(role="status") {
142                     span.sr-only {'Loading...'}
143                   }
144                 }
145               }
146               a.h5.collapsed(data-toggle="collapse" data-parent="#accordion" href="#step-2-collapse" aria-expanded="false" aria-controls="step-2-collapse") {
147                 'Change password'
148               }
149             }
150             div#step-2-collapse.collapse(role="tabpanel" aria-labelledby="step-2-heading" data-parent="#accordion") {
151               div.card-body {
152                 div.row.mb-3 {
153                   div.col-md-6 {
154                     div.form-group {
155                       label.form-label(for="old-password") {'Old password *'}
156                       input.form-control#old-password(type="password" placeholder="Old password" required="required" minlength=8 maxlength=256) {}
157                     }
158                   }
159                   div.col-md-6 {
160                     div.form-group {
161                       label.form-label(for="new-password") {'New password *'}
162                       input.form-control#'new-password'(type="password" placeholder="New password" required="required" minlength=8 maxlength=256) {}
163                     }
164                   }
165                 }
166
167                 button.btn.btn-outline-secondary#step-2-clear(type="button" disabled="disabled") {'Clear'}
168                 button.btn.btn-success.ml-3#step-2-save(type="button" disabled="disabled") {'Save'}
169
170                 p.'mt-3'.mb-0 {'* These fields are required.'}
171               }
172             }
173           }
174         }
175       }
176       else {
177         // signed out
178         p {'For account maintenance, please click on one of the options below.'}
179
180         ul.nav.flex-column {
181           let entries = menu.entries
182           for (let i = 0; i < entries.length; ++i) {
183             let entry = entries[i]
184             if (Object.prototype.hasOwnProperty.call(entry, 'icon'))
185               li.nav-item {
186                 a.nav-link(href=`${entry.dir}/index.html`) {
187                   table.icon-and-text {
188                     tr {
189                       td {
190                         _out.push(await env.site.get_min_svg(entry.icon))
191                       }
192                       td {
193                         span.h2{`${entry.name}`}
194                       }
195                     }
196                   }
197                 }
198               }
199           }
200         }
201       }
202     },
203     // scripts
204     async _out => {
205       script {
206         // this will be called by navbar logic after sign in/out
207         function sign_in_out(status) {
208           window.location.reload()
209         }
210       }
211
212       if (env.signed_in_as) {
213         //script(src="/js/api_call.js") {}
214
215         script {
216           let api_account_change_details_get = async (...args) => api_call(
217             '/api/account/change_details/get.json',
218             ...args
219           )
220           let api_account_change_details_set = async (...args) => api_call(
221             '/api/account/change_details/set.json',
222             ...args
223           )
224           //let api_account_change_details_get_draft = async (...args) => api_call(
225           //  '/api/account/change_details/get_draft.json',
226           //  ...args
227           //)
228           let api_account_change_details_set_draft = async (...args) => api_call(
229             '/api/account/change_details/set_draft.json',
230             ...args
231           )
232           let api_account_change_password = async (...args) => api_call(
233             '/api/account/change_password.json',
234             ...args
235           )
236
237           let step_1_dirty = ${JSON.stringify(draft_details !== null)}
238           let draft_timeout_running = false
239           let draft_timeout_handler = async () => {
240             draft_timeout_running = false
241             await api_account_change_details_set_draft(
242               step_1_dirty ?
243                 {
244                   given_names: document.getElementById('given-names').value.slice(0, 256),
245                   family_name: document.getElementById('family-name').value.slice(0, 256),
246                   contact_me: document.getElementById('contact-me').checked ? true : false
247                 } :
248                 null
249             )
250             //console.log('draft', await api_account_change_details_get_draft())
251           }
252
253           document.addEventListener(
254             'DOMContentLoaded',
255             () => {
256               let step_1_change_handler = () => {
257                 step_1_dirty = true
258                 document.getElementById('step-1-revert').disabled = false
259                 document.getElementById('step-1-save').disabled = false
260     
261                 if (!draft_timeout_running) {
262                   draft_timeout_running = true
263                   setTimeout(draft_timeout_handler, 5000)
264                 }
265               }
266               document.getElementById('given-names').addEventListener(
267                 'change',
268                 step_1_change_handler
269               )
270               document.getElementById('family-name').addEventListener(
271                 'change',
272                 step_1_change_handler
273               )
274               document.getElementById('contact-me').addEventListener(
275                 'change',
276                 step_1_change_handler
277               )
278
279               document.getElementById('step-1-revert').addEventListener(
280                 'click',
281                 async () => {
282                   $('#step-1-tick').hide()
283                   $('#step-1-cross').hide()
284                   $('#step-1-spinner').show()
285
286                   let details      
287                   try {
288                     details = await api_account_change_details_get()
289                   }
290                   catch (error) {
291                     let problem =
292                       error instanceof Problem ?
293                         error :
294                         new Problem(
295                           // title
296                           'Bad request',
297                           // detail
298                           (error.stack || error.message).toString()
299                           // status
300                           400
301                         )
302                     console.log(problem.detail)
303       
304                     $('#step-1-tick').hide()
305                     $('#step-1-cross').show()
306                     $('#step-1-spinner').hide()
307                     return
308                   }
309                   $('#step-1-tick').hide()
310                   $('#step-1-cross').hide()
311                   $('#step-1-spinner').hide()
312
313                   step_1_dirty = false
314                   document.getElementById('step-1-revert').disabled = true
315                   document.getElementById('step-1-save').disabled = true
316
317                   document.getElementById('given-names').value = details.given_names
318                   document.getElementById('family-name').value = details.family_name
319                   document.getElementById('contact-me').checked = details.contact_me
320
321                   if (!draft_timeout_running) {
322                     draft_timeout_running = true
323                     setTimeout(draft_timeout_handler, 5000)
324                   }
325                 }
326               )
327
328               document.getElementById('step-1-save').addEventListener(
329                 'click',
330                 async () => {
331                   if (
332                     !document.getElementById('given-names').reportValidity() ||
333                       !document.getElementById('family-name').reportValidity()
334                   ) {
335                     $('#step-1-tick').hide()
336                     $('#step-1-cross').show()
337                     $('#step-1-spinner').hide()
338                     return false
339                   }
340                   $('#step-1-tick').hide()
341                   $('#step-1-cross').hide()
342                   $('#step-1-spinner').show()
343       
344                   try {
345                     await api_account_change_details_set(
346                       {
347                         given_names: document.getElementById('given-names').value.slice(0, 256),
348                         family_name: document.getElementById('family-name').value.slice(0, 256),
349                         contact_me: document.getElementById('contact-me').checked ? true : false
350                       }
351                     )
352                   }
353                   catch (error) {
354                     let problem =
355                       error instanceof Problem ?
356                         error :
357                         new Problem(
358                           // title
359                           'Bad request',
360                           // detail
361                           (error.stack || error.message).toString()
362                           // status
363                           400
364                         )
365                     console.log(problem.detail)
366       
367                     $('#step-1-tick').hide()
368                     $('#step-1-cross').show()
369                     $('#step-1-spinner').hide()
370                     return
371                   }
372                   $('#step-1-tick').show()
373                   $('#step-1-cross').hide()
374                   $('#step-1-spinner').hide()
375
376                   step_1_dirty = false
377                   document.getElementById('step-1-revert').disabled = true
378                   document.getElementById('step-1-save').disabled = true
379
380                   // SHOULD execute immediately here
381                   // (because user is likely to leave the page after save)
382                   if (!draft_timeout_running) {
383                     draft_timeout_running = true
384                     setTimeout(draft_timeout_handler, 5000)
385                   }
386                 }
387               )
388
389               let step_2_change_handler = () => {
390                 document.getElementById('step-2-clear').disabled = false
391                 document.getElementById('step-2-save').disabled = false
392               }
393               document.getElementById('old-password').addEventListener(
394                 'change',
395                 step_2_change_handler
396               )
397               document.getElementById('new-password').addEventListener(
398                 'change',
399                 step_2_change_handler
400               )
401
402               document.getElementById('step-2-clear').addEventListener(
403                 'click',
404                 () => {
405                   document.getElementById('step-2-clear').disabled = true
406                   document.getElementById('step-2-save').disabled = true
407
408                   document.getElementById('old-password').value = ''
409                   document.getElementById('new-password').value = ''
410                 }
411               )
412
413               document.getElementById('step-2-save').addEventListener(
414                 'click',
415                 async () => {
416                   if (
417                     !document.getElementById('old-password').reportValidity() ||
418                       !document.getElementById('new-password').reportValidity()
419                   ) {
420                     $('#step-2-tick').hide()
421                     $('#step-2-cross').show()
422                     $('#step-2-spinner').hide()
423                     return false
424                   }
425                   $('#step-2-tick').hide()
426                   $('#step-2-cross').hide()
427                   $('#step-2-spinner').show()
428       
429                   try {
430                     await api_account_change_password(
431                       // old_password
432                       document.getElementById('old-password').value.slice(0, 256),
433                       // new_password
434                       document.getElementById('new-password').value.slice(0, 256)
435                     )
436                   }
437                   catch (error) {
438                     let problem =
439                       error instanceof Problem ?
440                         error :
441                         new Problem(
442                           // title
443                           'Bad request',
444                           // detail
445                           (error.stack || error.message).toString()
446                           // status
447                           400
448                         )
449                     console.log(problem.detail)
450       
451                     $('#step-2-tick').hide()
452                     $('#step-2-cross').show()
453                     $('#step-2-spinner').hide()
454                     return
455                   }
456                   $('#step-2-tick').show()
457                   $('#step-2-cross').hide()
458                   $('#step-2-spinner').hide()
459
460                   document.getElementById('step-2-clear').disabled = true
461                   document.getElementById('step-2-save').disabled = true
462
463                   document.getElementById('old-password').value = ''
464                   document.getElementById('new-password').value = ''
465                 }
466               )
467             }
468           )
469         }
470       }
471     }
472   )
473 }