In /my_account/sign_up/index.html.jst use variables id_XXX instead of document.getEle...
[ndcode_site.git] / my_account / sign_up / index.html.jst
1 return async env => {
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_envelope = await env.site.get_min_svg('/_svg/fa_envelope.svg')
5   let fa_redo = await env.site.get_min_svg('/_svg/fa_redo.svg')
6   let fa_user_plus = await env.site.get_min_svg('/_svg/fa_user-plus.svg')
7   let get_placeholder = await _require('/_lib/get_placeholder.jst')
8   let get_session = await _require('/_lib/get_session.jst')
9   let icon_cross = await env.site.get_min_svg('/_svg/icon_cross.svg')
10   let icon_tick = await env.site.get_min_svg('/_svg/icon_tick.svg')
11   let navbar = await _require('/_lib/navbar.jst')
12
13   // preload draft details if any
14   let transaction = await env.site.database.Transaction()
15   let placeholder
16   let sign_up_draft
17   try {
18     let root = await transaction.get({})
19     let session = await get_session(env, root)
20
21     placeholder = await get_placeholder(env, session)
22
23     sign_up_draft = await session.get_json('sign_up_draft')
24     if (sign_up_draft === undefined || env.now >= sign_up_draft.expires)
25       sign_up_draft = null
26     transaction.commit()
27   }
28   catch (error) {
29     transaction.rollback()
30     throw error
31   }
32
33   await navbar(
34     env,
35     // head
36     async _out => {},
37     // body
38     async _out => {
39       await breadcrumbs(env, _out)
40
41       p {'Signing up allows you to leave comments on our blog and receive communications from us.'}
42
43       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.'}
44
45       div.accordion#accordion(role="tablist" aria-multiselectable="true") {
46         div.card#card-1 {
47           div.card-header#card-1-heading(role="tab") {
48             div.icon32-outer.mr-2#card-1-tick(hidden) {
49               div.icon32-inner {_out.push(icon_tick)}
50             }
51             div.icon32-outer.mr-2#card-1-cross(hidden) {
52               div.icon32-inner {_out.push(icon_cross)}
53             }
54             div.icon32-outer.mr-2#card-1-spinner(hidden) {
55               div.icon32-inner {
56                 div.spinner-border(role="status") {}
57               }
58             }
59             a.h5(data-toggle="collapse" data-parent="#accordion" href="#card-1-collapse" aria-expanded="true" aria-controls="card-1-collapse") {
60               'Create account'
61             }
62           }
63           div#card-1-collapse.collapse.show(role="tabpanel" aria-labelledby="card-1-heading" data-parent="#accordion") {
64             div.card-body {
65               form#form {
66                 div.row {
67                   div.col-md-6 {
68                     div.form-group {
69                       label.form-label(for="given-names") {'Given names *'}
70                       input.form-control#given-names(type="text" value=sign_up_draft !== null ? sign_up_draft.given_names : '' placeholder=placeholder.given_names required maxlength=256) {}
71                       div.invalid-feedback {'Please enter a name we can address you by.'}
72                     }
73                   }
74                   div.col-md-6 {
75                     div.form-group {
76                       label.form-label(for="family-name") {'Family name'}
77                       input.form-control#family-name(type="text" value=sign_up_draft !== null ? sign_up_draft.family_name : '' placeholder=placeholder.family_name maxlength=256) {}
78                     }
79                   }
80                 }
81                 div.row {
82                   div.col-md-6 {
83                     div.form-group {
84                       label.form-label(for="email") {'Email *'}
85                       input.form-control#email(type="email" value=sign_up_draft !== null ? sign_up_draft.email : '' placeholder=placeholder.email required maxlength=256) {}
86                       div.invalid-feedback {'Please enter an email address we can contact you on.'}
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="Choose" required minlength=8 maxlength=256) {}
93                       div.invalid-feedback {'Please choose a secure password of at least 8 characters.'}
94                     }
95                   }
96                 }
97                 div.row {
98                   div.col-md-12 {
99                     div.custom-control.custom-checkbox {
100                       if (sign_up_draft === null || sign_up_draft.contact_me)
101                         input.custom-control-input#contact-me(type="checkbox" checked) {}
102                       else
103                         input.custom-control-input#contact-me(type="checkbox") {}
104                       ' '
105                       label.custom-control-label(for="contact-me") {
106                         'Contact me by email with updates and special offers'
107                       }
108                     }
109                   }
110                 }
111                 div.row.align-items-center {
112                   div.col-md-6 {
113                     div.form-group {
114                       label.form-label(for="verification-code") {'Verification code *'}
115                       input.form-control#verification-code(type="text" placeholder=placeholder.captcha_text required minlength=6 maxlength=6) {}
116                       div.invalid-feedback {'Please enter the 6 characters from the verification image to right or below. We need this to protect us from spam and bots.'}
117                     }
118                   }
119                   div.'col-md-6'.my-3 {
120                     img#verification-image(src="/api/verification_image.png?seq=0" width=300 height=150) {}
121                   }
122                 }
123               }
124
125               button.btn.btn-outline-secondary#'card-1-new-code'(type="button") {
126                 div.icon24-outer.mr-2 {
127                   div.icon24-inner {_out.push(fa_redo)}
128                 }
129                 'New code'
130               }
131               if (sign_up_draft !== null)
132                 button.btn.btn-success.ml-3#card-1-create-account(type="button") {
133                   div.icon24-outer.mr-2 {
134                     div.icon24-inner {_out.push(fa_user_plus)}
135                   }
136                   'Create account'
137                 }
138               else
139                 button.btn.btn-success.ml-3#card-1-create-account(type="button" disabled) {
140                   div.icon24-outer.rm-2 {
141                     div.icon24-inner {_out.push(fa_user_plus)}
142                   }
143                   'Create account'
144                 }
145
146               p.'mt-3'.mb-0#card-1-message(hidden) {}
147             }
148           }
149         }
150         div.card#card-2 {
151           div.card-header#card-2-heading(role="tab") {
152             div.icon32-outer.mr-2#card-2-tick(hidden) {
153               div.icon32-inner {_out.push(icon_tick)}
154             }
155             div.icon32-outer.mr-2#card-2-cross(hidden) {
156               div.icon32-inner {_out.push(icon_cross)}
157             }
158             div.icon32-outer.mr-2#card-2-spinner(hidden) {
159               div.icon32-inner {
160                 div.spinner-border(role="status") {}
161               }
162             }
163             a.h5.collapsed(data-toggle="collapse" data-parent="#accordion" href="#card-2-collapse" aria-expanded="false" aria-controls="card-2-collapse") {
164               'Send email verification link'
165             }
166           }
167           div#card-2-collapse.collapse(role="tabpanel" aria-labelledby="card-2-heading" data-parent="#accordion") {
168             div.card-body {
169               button.btn.btn-outline-secondary#card-2-back(type="button") {
170                 div.icon24-outer.mr-2 {
171                   div.icon24-inner {_out.push(fa_arrow_circle_left)}
172                 }
173                 'Back'
174               }
175               button.btn.btn-outline-secondary.ml-3#card-2-resend-email(type="button" disabled) {
176                 div.icon24-outer.mr-2 {
177                   div.icon24-inner {_out.push(fa_envelope)}
178                 }
179                 'Re-send email'
180               }
181
182               p.'mt-3'.mb-0#card-2-message(hidden) {}
183             }
184           }
185         }
186       }
187
188       p.mt-3 {'* These fields are required.'}
189     },
190     // scripts
191     async _out => {
192       //script(src="/js/utils.js") {}
193
194       script {
195         document.addEventListener(
196           'DOMContentLoaded',
197           () => {
198             let id_accordion = document.getElementById('accordion')
199             let id_card_1 = document.getElementById('card-1')
200             let id_card_1_collapse = document.getElementById('card-1-collapse')
201             let id_card_1_create_account = document.getElementById('card-1-create-account')
202             let id_card_1_cross = document.getElementById('card-1-cross')
203             let id_card_1_heading = document.getElementById('card-1-heading')
204             let id_card_1_message = document.getElementById('card-1-message')
205             let id_card_1_new_code = document.getElementById('card-1-new-code')
206             let id_card_1_spinner = document.getElementById('card-1-spinner')
207             let id_card_1_tick = document.getElementById('card-1-tick')
208             let id_card_2 = document.getElementById('card-2')
209             let id_card_2_back = document.getElementById('card-2-back')
210             let id_card_2_collapse = document.getElementById('card-2-collapse')
211             let id_card_2_cross = document.getElementById('card-2-cross')
212             let id_card_2_heading = document.getElementById('card-2-heading')
213             let id_card_2_message = document.getElementById('card-2-message')
214             let id_card_2_resend_email = document.getElementById('card-2-resend-email')
215             let id_card_2_spinner = document.getElementById('card-2-spinner')
216             let id_card_2_tick = document.getElementById('card-2-tick')
217             let id_contact_me = document.getElementById('contact-me')
218             let id_email = document.getElementById('email')
219             let id_family_name = document.getElementById('family-name')
220             let id_form = document.getElementById('form')
221             let id_given_names = document.getElementById('given-names')
222             let id_password = document.getElementById('password')
223             let id_verification_code = document.getElementById('verification-code')
224             let id_verification_image = document.getElementById('verification-image')
225
226             let input_semaphore = new BinarySemaphore(false)
227             ;(
228               async () => {
229                 while (true) {
230                   await input_semaphore.acquire()
231                   await new Promise(resolve => setTimeout(resolve, 3000))
232                   input_semaphore.try_acquire()
233                   await api_call(
234                     '/api/account/sign_up/set_draft.json',
235                     id_card_1_create_account.disabled ?
236                       null :
237                       {
238                         email: id_email.value.slice(0, 256).toLowerCase(),
239                         given_names: id_given_names.value.slice(0, 256),
240                         family_name: id_family_name.value.slice(0, 256),
241                         contact_me: id_contact_me.checked ? true : false
242                       }
243                   )
244                 }
245               }
246             )() // ignore returned promise (start thread)
247
248             let card_1_edited = in_draft => {
249               if (in_draft)
250                 input_semaphore.release()
251
252               id_card_1_create_account.disabled =
253                 id_given_names.value.length === 0 &&
254                   id_family_name.value.length === 0 &&
255                   id_contact_me.checked &&
256                   id_email.value.length === 0 &&
257                   id_verification_code.value.length === 0
258               id_card_1_tick.hidden = true
259               id_card_1_cross.hidden = true
260               id_card_1_spinner.hidden = true
261               id_card_1_message.hidden = true
262
263               id_card_2_resend_email.disabled = true
264               id_card_2_tick.hidden = true
265               id_card_2_cross.hidden = true
266               id_card_2_spinner.hidden = true
267               id_card_2_message.hidden = true
268             }
269
270             let details
271             let card_1 = async () => {
272               id_card_1_tick.hidden = true
273               id_card_1_cross.hidden = true
274               id_card_1_spinner.hidden = true
275               // the below causes an ugly flicker, so just keep the message
276               //id_card_1_message.hidden = true
277
278               if (!id_form.checkValidity()) {
279                 id_form.classList.add('was-validated');
280                 id_card_1_cross.hidden = false
281                 return false
282               }
283               id_form.classList.remove('was-validated');
284
285               details = {
286                 email: id_email.value.slice(0, 256).toLowerCase(),
287                 given_names: id_given_names.value.slice(0, 256),
288                 family_name: id_family_name.value.slice(0, 256),
289                 password: id_password.value.slice(0, 256),
290                 contact_me: id_contact_me.checked ? true : false
291               }
292
293               id_card_1_spinner.hidden = false
294               try {
295                 await api_call(
296                   '/api/account/sign_up/create_account.json',
297                   id_verification_code.value.slice(0, 6).toLowerCase(),
298                   details
299                 )
300               }
301               catch (error) {
302                 let problem = Problem.from(error)
303
304                 id_card_1_cross.hidden = false
305                 id_card_1_spinner.hidden = true
306
307                 id_card_1_message.textContent = problem.detail
308                 //id_card_1_message.classList.remove('text-success')
309                 id_card_1_message.classList.add('text-danger')
310                 id_card_1_message.hidden = false
311
312                 $('#card-1-collapse').collapse('show')
313                 return false
314               }
315               id_card_1_tick.hidden = false
316               id_card_1_spinner.hidden = true
317               id_card_1_message.textContent = `Your account with email "${details.email}" has been created.`
318               //id_card_1_message.classList.add('text-success')
319               id_card_1_message.classList.remove('text-danger')
320               id_card_1_message.hidden = false
321
322               id_card_2_resend_email.disabled = false
323               id_card_2.scrollIntoView()
324               return true
325             }
326
327             let card_2 = async () => {
328               id_card_2_tick.hidden = true
329               id_card_2_cross.hidden = true
330               id_card_2_spinner.hidden = false
331               // the below causes an ugly flicker, so just keep the message
332               //id_card_2_message.hidden = true
333
334               try {
335                 await api_call(
336                   '/api/account/sign_up/send_email_verification_link.json',
337                   details.email
338                 )
339               }
340               catch (error) {
341                 let problem = Problem.from(error)
342
343                 id_card_2_cross.hidden = false
344                 id_card_2_spinner.hidden = true
345
346                 id_card_2_message.textContent = problem.detail
347                 //id_card_2_message.classList.remove('text-success')
348                 id_card_2_message.classList.add('text-danger')
349                 id_card_2_message.hidden = false
350
351                 $('#card-2-collapse').collapse('show')
352                 return false
353               }
354               id_card_2_tick.hidden = false
355               id_card_2_spinner.hidden = true
356
357               id_card_2_message.textContent = `Email verification link has been sent to "${details.email}". Please check your email for next steps.`
358               //id_card_2_message.classList.add('text-success')
359               id_card_2_message.classList.remove('text-danger')
360               id_card_2_message.hidden = false
361               return true
362             }
363
364             id_given_names.addEventListener(
365               'input',
366               () => {card_1_edited(true)}
367             )
368             id_family_name.addEventListener(
369               'input',
370               () => {card_1_edited(true)}
371             )
372             id_email.addEventListener(
373               'input',
374               () => {card_1_edited(true)}
375             )
376             id_password.addEventListener(
377               'input',
378               () => {card_1_edited(false)}
379             )
380             id_contact_me.addEventListener(
381               'input',
382               () => {card_1_edited(true)}
383             )
384             id_verification_code.addEventListener(
385               'input',
386               () => {card_1_edited(false)}
387             )
388
389             let image_seq = 1
390             id_card_1_new_code.addEventListener(
391               'click',
392               () => {
393                 id_verification_image.src = `/api/verification_image.png?seq=${image_seq}`
394                 image_seq += 1
395               }
396             )
397
398             id_card_1_create_account.addEventListener(
399               'click',
400               async () => {
401                 if (await card_1() && await card_2())
402                   $('#card-2-collapse').collapse('show')
403               }
404             )
405
406             id_card_2_back.addEventListener(
407               'click',
408               () => {$('#card-1-collapse').collapse('show')}
409             )
410
411             id_card_2_resend_email.addEventListener(
412               'click',
413               async () => {
414                 if (await card_2())
415                   $('#card-2-collapse').collapse('show')
416               }
417             )
418           }
419         )
420       }
421     }
422   )
423 }