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