Disable the "Create account" button and null draft when sign up form is blank
[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(style="display: none;") {
45               span.icon-color.pr-3 {_out.push(icon_tick)}
46             }
47             span#card-1-cross(style="display: none;") {
48               span.icon-color.pr-3 {_out.push(icon_cross)}
49             }
50             //span#card-1-spinner(style="display: none;") {
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(style="display: none;") {}
130             }
131           }
132         }
133         div.card#card-2 {
134           div.card-header#card-2-heading(role="tab") {
135             span#card-2-tick(style="display: none;") {
136               span.icon-color.pr-3 {_out.push(icon_tick)}
137             }
138             span#card-2-cross(style="display: none;") {
139               span.icon-color.pr-3 {_out.push(icon_cross)}
140             }
141             span#card-2-spinner(style="display: none;") {
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(style="display: none;") {}
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           document.getElementById('card-1-create-account').disabled =
196             document.getElementById('given-names').value.length === 0 &&
197               document.getElementById('family-name').value.length === 0 &&
198               document.getElementById('contact-me').checked &&
199               document.getElementById('email').value.length === 0 &&
200               document.getElementById('verification-code').value.length === 0
201         }
202
203         let details
204         let card_1 = async () => {
205           if (!document.getElementById('form').checkValidity()) {
206             document.getElementById('form').classList.add('was-validated');
207             $('#card-1-tick').hide()
208             $('#card-1-cross').show()
209             //$('#card-1-spinner').hide()
210             return false
211           }
212           document.getElementById('form').classList.remove('was-validated');
213
214           details = {
215             email: document.getElementById('email').value.slice(0, 256).toLowerCase(),
216             given_names: document.getElementById('given-names').value.slice(0, 256),
217             family_name: document.getElementById('family-name').value.slice(0, 256),
218             password: document.getElementById('password').value.slice(0, 256),
219             contact_me: document.getElementById('contact-me').checked ? true : false
220           }
221
222           try {
223             await api_call(
224               '/api/account/sign_up/create_account.json',
225               document.getElementById('verification-code').value.slice(0, 6).toLowerCase(),
226               details
227             )
228           }
229           catch (error) {
230             let problem = Problem.from(error)
231
232             $('#card-1-tick').hide()
233             $('#card-1-cross').show()
234             $('#card-1-spinner').hide()
235
236             document.getElementById('card-1-message').textContent = problem.detail
237             //document.getElementById('card-1-message').classList.remove('text-success')
238             document.getElementById('card-1-message').classList.add('text-danger')
239             $('#card-1-message').show()
240
241             $('#card-1-collapse').collapse('show')
242             return false
243           }
244           $('#card-1-tick').show()
245           $('#card-1-cross').hide()
246           $('#card-1-spinner').hide()
247           document.getElementById('card-1-message').textContent = `Your account with email "${details.email}" has been created.`
248           //document.getElementById('card-1-message').classList.add('text-success')
249           document.getElementById('card-1-message').classList.remove('text-danger')
250           $('#card-1-message').show()
251
252           document.getElementById('card-2-resend-email').disabled = false
253           return true
254         }
255
256         let card_2 = async () => {
257           $('#card-2-tick').hide()
258           $('#card-2-cross').hide()
259           $('#card-2-spinner').show()
260           document.getElementById('card-2').scrollIntoView()
261
262           try {
263             await api_call(
264               '/api/account/sign_up/send_email_verification_link.json',
265               details.email
266             )
267           }
268           catch (error) {
269             let problem = Problem.from(error)
270
271             $('#card-2-tick').hide()
272             $('#card-2-cross').show()
273             $('#card-2-spinner').hide()
274
275             document.getElementById('card-2-message').textContent = problem.detail
276             //document.getElementById('card-2-message').classList.remove('text-success')
277             document.getElementById('card-2-message').classList.add('text-danger')
278             $('#card-2-message').show()
279
280             $('#card-2-collapse').collapse('show')
281             return false
282           }
283           $('#card-2-tick').show()
284           $('#card-2-cross').hide()
285           $('#card-2-spinner').hide()
286
287           document.getElementById('card-2-message').textContent = `Email verification link has been sent to "${details.email}". Please check your email for next steps.`
288           //document.getElementById('card-2-message').classList.add('text-success')
289           document.getElementById('card-2-message').classList.remove('text-danger')
290           $('#card-2-message').show()
291           return true
292         }
293
294         document.addEventListener(
295           'DOMContentLoaded',
296           () => {
297             document.getElementById('given-names').addEventListener(
298               'input',
299               () => {card_1_edited(true)}
300             )
301             document.getElementById('family-name').addEventListener(
302               'input',
303               () => {card_1_edited(true)}
304             )
305             document.getElementById('email').addEventListener(
306               'input',
307               () => {card_1_edited(true)}
308             )
309             document.getElementById('password').addEventListener(
310               'input',
311               () => {card_1_edited(false)}
312             )
313             document.getElementById('contact-me').addEventListener(
314               'input',
315               () => {card_1_edited(true)}
316             )
317             document.getElementById('verification-code').addEventListener(
318               'input',
319               () => {card_1_edited(false)}
320             )
321
322             let image_seq = 1
323             document.getElementById('card-1-new-code').addEventListener(
324               'click',
325               () => {
326                 document.getElementById('verification-image').src = `/api/verification_image.png?seq=${image_seq}`
327                 image_seq += 1
328               }
329             )
330
331             document.getElementById('card-1-create-account').addEventListener(
332               'click',
333               async () => {
334                 if (await card_1() && await card_2())
335                   $('#card-2-collapse').collapse('show')
336               }
337             )
338
339             document.getElementById('card-2-back').addEventListener(
340               'click',
341               () => {$('#card-1-collapse').collapse('show')}
342             )
343
344             document.getElementById('card-2-resend-email').addEventListener(
345               'click',
346               async () => {
347                 if (await card_2())
348                   $('#card-2-collapse').collapse('show')
349               }
350             )
351           }
352         )
353       }
354     }
355   )
356 }