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