In /my_account/sign_up/index.html.jst change placeholder for captcha text to xxxxxx...
[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='xxxxxx' 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.mr-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_given_names.value.length === 0 &&
236                       id_family_name.value.length === 0 &&
237                       id_contact_me.checked &&
238                       id_email.value.length === 0 ?
239                       null :
240                       {
241                         email: id_email.value.slice(0, 256).toLowerCase(),
242                         given_names: id_given_names.value.slice(0, 256),
243                         family_name: id_family_name.value.slice(0, 256),
244                         contact_me: id_contact_me.checked ? true : false
245                       }
246                   )
247                 }
248               }
249             )() // ignore returned promise (start thread)
250
251             let card_1_edited = in_draft => {
252               if (in_draft)
253                 input_semaphore.release()
254
255               id_card_1_create_account.disabled =
256                 id_given_names.value.length === 0 &&
257                   id_family_name.value.length === 0 &&
258                   id_contact_me.checked &&
259                   id_email.value.length === 0 &&
260                   id_password.value.length === 0 &&
261                   id_verification_code.value.length === 0
262               id_card_1_tick.hidden = true
263               id_card_1_cross.hidden = true
264               id_card_1_spinner.hidden = true
265               id_card_1_message.hidden = true
266
267               id_card_2_resend_email.disabled = true
268               id_card_2_tick.hidden = true
269               id_card_2_cross.hidden = true
270               id_card_2_spinner.hidden = true
271               id_card_2_message.hidden = true
272             }
273
274             let details
275             let card_1 = async () => {
276               id_card_1_tick.hidden = true
277               id_card_1_cross.hidden = true
278               id_card_1_spinner.hidden = true
279               // the below causes an ugly flicker, so just keep the message
280               //id_card_1_message.hidden = true
281
282               if (!id_form.checkValidity()) {
283                 id_form.classList.add('was-validated');
284                 id_card_1_cross.hidden = false
285                 return false
286               }
287               id_form.classList.remove('was-validated');
288
289               details = {
290                 email: id_email.value.slice(0, 256).toLowerCase(),
291                 given_names: id_given_names.value.slice(0, 256),
292                 family_name: id_family_name.value.slice(0, 256),
293                 password: id_password.value.slice(0, 256),
294                 contact_me: id_contact_me.checked ? true : false
295               }
296
297               id_card_1_spinner.hidden = false
298               try {
299                 await api_call(
300                   '/api/account/sign_up/create_account.json',
301                   id_verification_code.value.slice(0, 6).toLowerCase(),
302                   details
303                 )
304               }
305               catch (error) {
306                 let problem = Problem.from(error)
307
308                 id_card_1_cross.hidden = false
309                 id_card_1_spinner.hidden = true
310
311                 id_card_1_message.textContent = problem.detail
312                 //id_card_1_message.classList.remove('text-success')
313                 id_card_1_message.classList.add('text-danger')
314                 id_card_1_message.hidden = false
315
316                 $('#card-1-collapse').collapse('show')
317                 return false
318               }
319               id_card_1_tick.hidden = false
320               id_card_1_spinner.hidden = true
321               id_card_1_message.textContent = `Your account with email "${details.email}" has been created.`
322               //id_card_1_message.classList.add('text-success')
323               id_card_1_message.classList.remove('text-danger')
324               id_card_1_message.hidden = false
325
326               id_card_2_resend_email.disabled = false
327               id_card_2.scrollIntoView()
328               return true
329             }
330
331             let card_2 = async () => {
332               id_card_2_tick.hidden = true
333               id_card_2_cross.hidden = true
334               id_card_2_spinner.hidden = false
335               // the below causes an ugly flicker, so just keep the message
336               //id_card_2_message.hidden = true
337
338               try {
339                 await api_call(
340                   '/api/account/sign_up/send_email_verification_link.json',
341                   details.email
342                 )
343               }
344               catch (error) {
345                 let problem = Problem.from(error)
346
347                 id_card_2_cross.hidden = false
348                 id_card_2_spinner.hidden = true
349
350                 id_card_2_message.textContent = problem.detail
351                 //id_card_2_message.classList.remove('text-success')
352                 id_card_2_message.classList.add('text-danger')
353                 id_card_2_message.hidden = false
354
355                 $('#card-2-collapse').collapse('show')
356                 return false
357               }
358               id_card_2_tick.hidden = false
359               id_card_2_spinner.hidden = true
360
361               id_card_2_message.textContent = `Email verification link has been sent to "${details.email}". Please check your email for next steps.`
362               //id_card_2_message.classList.add('text-success')
363               id_card_2_message.classList.remove('text-danger')
364               id_card_2_message.hidden = false
365               return true
366             }
367
368             id_given_names.addEventListener(
369               'input',
370               () => {card_1_edited(true)}
371             )
372             id_family_name.addEventListener(
373               'input',
374               () => {card_1_edited(true)}
375             )
376             id_email.addEventListener(
377               'input',
378               () => {card_1_edited(true)}
379             )
380             id_password.addEventListener(
381               'input',
382               () => {card_1_edited(false)}
383             )
384             id_contact_me.addEventListener(
385               'input',
386               () => {card_1_edited(true)}
387             )
388             id_verification_code.addEventListener(
389               'input',
390               () => {card_1_edited(false)}
391             )
392
393             let image_seq = 1
394             id_card_1_new_code.addEventListener(
395               'click',
396               () => {
397                 id_verification_image.src = `/api/verification_image.png?seq=${image_seq}`
398                 image_seq += 1
399               }
400             )
401
402             id_card_1_create_account.addEventListener(
403               'click',
404               async () => {
405                 if (await card_1() && await card_2())
406                   $('#card-2-collapse').collapse('show')
407               }
408             )
409
410             id_card_2_back.addEventListener(
411               'click',
412               () => {$('#card-1-collapse').collapse('show')}
413             )
414
415             id_card_2_resend_email.addEventListener(
416               'click',
417               async () => {
418                 if (await card_2())
419                   $('#card-2-collapse').collapse('show')
420               }
421             )
422           }
423         )
424       }
425     }
426   )
427 }