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