Major refactoring of get_session(), get_account(), get_nodemailer(), introduces ...
[ndcode_site.git] / my_account / sign_up / index.html.jst
1 let XDate = require('xdate')
2
3 return async env => {
4   let breadcrumbs = await _require('/_lib/breadcrumbs.jst')
5   let get_session = await _require('/_lib/get_session.jst')
6   let icon_cross = await env.site.get_min_svg('/_svg/icon_cross.svg')
7   let icon_tick = await env.site.get_min_svg('/_svg/icon_tick.svg')
8   let navbar = await _require('/_lib/navbar.jst')
9
10   // preload draft details if any
11   let transaction = await env.site.database.Transaction()
12   let draft_details
13   try {
14     let root = await transaction.get({})
15     let session = await get_session(env, root)
16
17     let sign_up_draft = await session.get('sign_up_draft')
18     draft_details =
19       sign_up_draft !== undefined &&
20         XDate.now() < await sign_up_draft.get_json('expires') ?
21         {
22           email: await sign_up_draft.get_json('email'),
23           given_names: await sign_up_draft.get_json('given_names'),
24           family_name: await sign_up_draft.get_json('family_name'),
25           contact_me: await sign_up_draft.get_json('contact_me')
26         } :
27         null
28   }
29   finally {
30     transaction.rollback()
31   }
32   console.log('draft_details', JSON.stringify(draft_details))
33
34   await navbar(
35     env,
36     // head
37     async _out => {},
38     // body
39     async _out => {
40       await breadcrumbs(env, _out)
41
42       p {'Signing up allows you to leave comments on our blog and receive communications from us.'}
43
44       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.'}
45
46       div.accordion#accordion.mb-5(role="tablist" aria-multiselectable="true") {
47         div.card#step-1 {
48           div.card-header#step-1-heading(role="tab") {
49             span#step-1-tick(style="display: none;") {
50               span.icon-color.pr-3 {_out.push(icon_tick)}
51             }
52             span#step-1-cross(style="display: none;") {
53               span.icon-color.pr-3 {_out.push(icon_cross)}
54             }
55             //span#step-1-spinner(style="display: none;") {
56             //  span.icon-color.pr-3 {
57             //    div.spinner-border(role="status") {
58             //      span.sr-only {'Loading...'}
59             //    }
60             //  }
61             //}
62             a.h5(data-toggle="collapse" data-parent="#accordion" href="#step-1-collapse" aria-expanded="true" aria-controls="step-1-collapse") {
63               'Your details'
64             }
65           }
66           div#step-1-collapse.collapse.show(role="tabpanel" aria-labelledby="step-1-heading" data-parent="#accordion") {
67             div.card-body {
68               div.row {
69                 div.col-md-6 {
70                   div.form-group {
71                     label.form-label(for="given-names") {'Given names *'}
72                     input.form-control#given-names(type="text" value=draft_details ? draft_details.given_names : '' placeholder="Your given names" required="required" maxlength=256) {}
73                   }
74                 }
75                 div.col-md-6 {
76                   div.form-group {
77                     label.form-label(for="family-name") {'Family name'}
78                     input.form-control#family-name(type="text" value=draft_details ? draft_details.family_name : '' placeholder="Your family name" maxlength=256) {}
79                   }
80                 }
81               }
82               div.row {
83                 div.col-md-6 {
84                   div.form-group {
85                     label.form-label(for="email") {'Email *'}
86                     input.form-control#email(type="email" value=draft_details ? draft_details.email : '' placeholder="Your email address" required="required" maxlength=256) {}
87                   }
88                 }
89                 div.col-md-6 {
90                   div.form-group {
91                     label.form-label(for="password") {'Password *'}
92                     input.form-control#password(type="password" placeholder="New password" required="required" minlength=8 maxlength=256) {}
93                   }
94                 }
95               }
96               div.row {
97                 div.col-md-12 {
98                   div.custom-control.custom-checkbox {
99                     if (!draft_details || draft_details.contact_me)
100                       input.custom-control-input#contact-me(type="checkbox" checked="checked") {}
101                     else
102                       input.custom-control-input#contact-me(type="checkbox") {}
103                     ' '
104                     label.custom-control-label(for="contact-me") {
105                       'Contact me by email with updates and special offers'
106                     }
107                   }
108                 }
109               }
110               div.row.align-items-center.mb-3 {
111                 div.'col-md-6' {
112                   div.form-group {
113                     label.form-label(for="verification-code") {'Verification code *'}
114                     input.form-control#verification-code(type="text" placeholder="Type the code shown to the right" required="required" minlength=6 maxlength=6) {}
115                   }
116                 }
117                 div.'col-md-4' {
118                   img#verification-image(src="/api/verification_image.png?seq=0" width=300 height=150) {}
119                 }
120                 div.'col-md-2'.my-auto.text-center {
121                   button.btn.btn-outline-secondary#'step-1-new-code'(type="button") {'New code'}
122                 }
123               }
124
125               button.btn.btn-success#step-1-continue(type="button") {'Continue'}
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               'Create account'
148             }
149           }
150           div#step-2-collapse.collapse(role="tabpanel" aria-labelledby="step-2-heading" data-parent="#accordion") {
151             div.card-body {
152               p#step-2-message {'Please enter your details first.'}
153
154               button.btn.btn-outline-secondary#step-2-back(type="button") {'Back'}
155               button.btn.btn-outline-secondary.ml-3#step-2-continue(type="button") {'Continue'}
156             }
157           }
158         }
159         div.card#step-3 {
160           div.card-header#step-3-heading(role="tab") {
161             span#step-3-tick(style="display: none;") {
162               span.icon-color.pr-3 {_out.push(icon_tick)}
163             }
164             span#step-3-cross(style="display: none;") {
165               span.icon-color.pr-3 {_out.push(icon_cross)}
166             }
167             span#step-3-spinner(style="display: none;") {
168               span.icon-color.pr-3 {
169                 div.spinner-border(role="status") {
170                   span.sr-only {'Loading...'}
171                 }
172               }
173             }
174             a.h5.collapsed(data-toggle="collapse" data-parent="#accordion" href="#step-3-collapse" aria-expanded="false" aria-controls="step-3-collapse") {
175               'Send email verification link'
176             }
177           }
178           div#step-3-collapse.collapse(role="tabpanel" aria-labelledby="step-3-heading" data-parent="#accordion") {
179             div.card-body {
180               p#step-3-message {'Please create your account first.'}
181
182               button.btn.btn-outline-secondary#step-3-back(type="button") {'Back'}
183               button.btn.btn-outline-secondary.ml-3#step-3-resend-email(type="button") {'Re-send email'}
184             }
185           }
186         }
187       }
188     },
189     // scripts
190     async _out => {
191       //script(src="/js/api_call.js") {}
192
193       script {
194         let api_account_sign_up_create_account = async (...args) => api_call(
195           '/api/account/sign_up/create_account.json',
196           ...args
197         )
198         //let api_account_sign_up_get_draft = async (...args) => api_call(
199         //  '/api/account/sign_up/get_draft.json',
200         //  ...args
201         //)
202         let api_account_sign_up_set_draft = async (...args) => api_call(
203           '/api/account/sign_up/set_draft.json',
204           ...args
205         )
206         let api_account_sign_up_send_email_verification_link = async (...args) => api_call(
207           '/api/account/sign_up/send_email_verification_link.json',
208           ...args
209         )
210
211         let draft_timeout_running = false
212         let draft_timeout_handler = async () => {
213           draft_timeout_running = false
214           await api_account_sign_up_set_draft(
215             {
216               email: document.getElementById('email').value.slice(0, 256).toLowerCase(),
217               given_names: document.getElementById('given-names').value.slice(0, 256),
218               family_name: document.getElementById('family-name').value.slice(0, 256),
219               contact_me: document.getElementById('contact-me').checked ? true : false
220             }
221           )
222           //console.log('draft', await api_account_sign_up_get_draft())
223         }
224         let draft_change_handler = () => {
225           if (!draft_timeout_running) {
226             draft_timeout_running = true
227             setTimeout(draft_timeout_handler, 5000)
228           }
229         }
230
231         let details
232         let step_1 = async () => {
233           if (
234             !document.getElementById('given-names').reportValidity() ||
235               !document.getElementById('family-name').reportValidity() ||
236               !document.getElementById('email').reportValidity() ||
237               !document.getElementById('password').reportValidity() ||
238               !document.getElementById('verification-code').reportValidity()
239           ) {
240             $('#step-1-tick').hide()
241             $('#step-1-cross').show()
242             //$('#step-1-spinner').hide()
243             return false
244           }
245           $('#step-1-tick').show()
246           $('#step-1-cross').hide()
247           //$('#step-1-spinner').hide()
248
249           details = {
250             email: document.getElementById('email').value.slice(0, 256).toLowerCase(),
251             given_names: document.getElementById('given-names').value.slice(0, 256),
252             family_name: document.getElementById('family-name').value.slice(0, 256),
253             password: document.getElementById('password').value.slice(0, 256),
254             contact_me: document.getElementById('contact-me').checked ? true : false
255           }
256           return true
257         }
258
259         let step_2 = async () => {
260           $('#step-2-tick').hide()
261           $('#step-2-cross').hide()
262           $('#step-2-spinner').show()
263           document.getElementById('step-2').scrollIntoView()
264
265           try {
266             await api_account_sign_up_create_account(
267               // verification_code
268               document.getElementById('verification-code').value.slice(0, 6).toLowerCase(),
269               // details
270               details
271             )
272           }
273           catch (error) {
274             let problem =
275               error instanceof Problem ?
276                 error :
277                 new Problem(
278                   // title
279                   'Bad request',
280                   // detail
281                   (error.stack || error.message).toString()
282                   // status
283                   400
284                 )
285
286             $('#step-2-tick').hide()
287             $('#step-2-cross').show()
288             $('#step-2-spinner').hide()
289
290             document.getElementById('step-2-message').textContent = problem.detail
291             $('#step-2-collapse').collapse('show')
292             return false
293           }
294           $('#step-2-tick').show()
295           $('#step-2-cross').hide()
296           $('#step-2-spinner').hide()
297           document.getElementById('step-2-message').textContent = `Your account with email "${details.email}" has been created.`
298           return true
299         }
300
301         let step_3 = async () => {
302           $('#step-3-tick').hide()
303           $('#step-3-cross').hide()
304           $('#step-3-spinner').show()
305           document.getElementById('step-3').scrollIntoView()
306
307           try {
308             await api_account_sign_up_send_email_verification_link(details.email)
309           }
310           catch (error) {
311             let problem =
312               error instanceof Problem ?
313                 error :
314                 new Problem(
315                   // title
316                   'Bad request',
317                   // detail
318                   (error.stack || error.message).toString()
319                   // status
320                   400
321                 )
322
323             $('#step-3-tick').hide()
324             $('#step-3-cross').show()
325             $('#step-3-spinner').hide()
326
327             document.getElementById('step-3-message').textContent = problem.detail
328             $('#step-3-collapse').collapse('show')
329             return false
330           }
331           $('#step-3-tick').show()
332           $('#step-3-cross').hide()
333           $('#step-3-spinner').hide()
334
335           document.getElementById('step-3-message').textContent = `Email verification link has been sent to "${details.email}". Please check your email for next steps.`
336           return true
337         }
338
339         document.addEventListener(
340           'DOMContentLoaded',
341           () => {
342             document.getElementById('given-names').addEventListener(
343               'change',
344               draft_change_handler
345             )
346             document.getElementById('family-name').addEventListener(
347               'change',
348               draft_change_handler
349             )
350             document.getElementById('email').addEventListener(
351               'change',
352               draft_change_handler
353             )
354             document.getElementById('password').addEventListener(
355               'change',
356               draft_change_handler
357             )
358             document.getElementById('contact-me').addEventListener(
359               'change',
360               draft_change_handler
361             )
362
363             let image_seq = 1
364             document.getElementById('step-1-new-code').addEventListener(
365               'click',
366               () => {
367                 document.getElementById('verification-image').src = `/api/verification_image.png?seq=${image_seq}`
368                 image_seq += 1
369               }
370             )
371
372             document.getElementById('step-1-continue').addEventListener(
373               'click',
374               async () => {
375                 if (await step_1() && await step_2() && await step_3())
376                   $('#step-3-collapse').collapse('show')
377               }
378             )
379
380             document.getElementById('step-2-back').addEventListener(
381               'click',
382               () => {$('#step-1-collapse').collapse('show')}
383             )
384
385             document.getElementById('step-2-continue').addEventListener(
386               'click',
387               async () => {
388                 if (await step_3())
389                   $('#step-3-collapse').collapse('show')
390               }
391             )
392
393             document.getElementById('step-3-back').addEventListener(
394               'click',
395               () => {$('#step-2-collapse').collapse('show')}
396             )
397
398             document.getElementById('step-3-resend-email').addEventListener(
399               'click',
400               async () => {
401                 if (await step_3())
402                   $('#step-3-collapse').collapse('show')
403               }
404             )
405           }
406         )
407       }
408     }
409   )
410 }