Add random placeholder data generator (persists in session for 24 hours)
[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     placeholder = await get_placeholder(env, session)
17     sign_up_draft = await session.get_json('sign_up_draft')
18     if (sign_up_draft === undefined || env.now >= sign_up_draft.expires)
19       sign_up_draft = {}
20     transaction.commit()
21   }
22   catch (error) {
23     transaction.rollback()
24     throw error
25   }
26
27   await navbar(
28     env,
29     // head
30     async _out => {},
31     // body
32     async _out => {
33       await breadcrumbs(env, _out)
34
35       p {'Signing up allows you to leave comments on our blog and receive communications from us.'}
36
37       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.'}
38
39       div.accordion#accordion(role="tablist" aria-multiselectable="true") {
40         div.card#card-1 {
41           div.card-header#card-1-heading(role="tab") {
42             span#card-1-tick(style="display: none;") {
43               span.icon-color.pr-3 {_out.push(icon_tick)}
44             }
45             span#card-1-cross(style="display: none;") {
46               span.icon-color.pr-3 {_out.push(icon_cross)}
47             }
48             //span#card-1-spinner(style="display: none;") {
49             //  span.icon-color.pr-3 {
50             //    div.spinner-border(role="status") {
51             //      span.sr-only {'Loading...'}
52             //    }
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.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.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.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.contact_me !== false)
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               button.btn.btn-success.ml-3#card-1-create-account(type="button") {'Create account'}
123
124               p.'mt-3'.mb-0#card-1-message(style="display: none;") {}
125             }
126           }
127         }
128         div.card#card-2 {
129           div.card-header#card-2-heading(role="tab") {
130             span#card-2-tick(style="display: none;") {
131               span.icon-color.pr-3 {_out.push(icon_tick)}
132             }
133             span#card-2-cross(style="display: none;") {
134               span.icon-color.pr-3 {_out.push(icon_cross)}
135             }
136             span#card-2-spinner(style="display: none;") {
137               span.icon-color.pr-3 {
138                 div.spinner-border(role="status") {
139                   span.sr-only {'Loading...'}
140                 }
141               }
142             }
143             a.h5.collapsed(data-toggle="collapse" data-parent="#accordion" href="#card-2-collapse" aria-expanded="false" aria-controls="card-2-collapse") {
144               'Send email verification link'
145             }
146           }
147           div#card-2-collapse.collapse(role="tabpanel" aria-labelledby="card-2-heading" data-parent="#accordion") {
148             div.card-body {
149               button.btn.btn-outline-secondary#card-2-back(type="button") {'Back'}
150               button.btn.btn-outline-secondary.ml-3#card-2-resend-email(type="button") {'Re-send email'}
151
152               p.'mt-3'.mb-0#card-2-message(style="display: none;") {}
153             }
154           }
155         }
156       }
157
158       p.mt-3 {'* These fields are required.'}
159     },
160     // scripts
161     async _out => {
162       //script(src="/js/utils.js") {}
163
164       script {
165         let input_semaphore = new BinarySemaphore(false)
166         ;(
167           async () => {
168             while (true) {
169               await input_semaphore.acquire()
170               await new Promise(resolve => setTimeout(resolve, 3000))
171               input_semaphore.try_acquire()
172               await api_call(
173                 '/api/account/sign_up/set_draft.json',
174                 {
175                   email: document.getElementById('email').value.slice(0, 256).toLowerCase(),
176                   given_names: document.getElementById('given-names').value.slice(0, 256),
177                   family_name: document.getElementById('family-name').value.slice(0, 256),
178                   contact_me: document.getElementById('contact-me').checked ? true : false
179                 }
180               )
181             }
182           }
183         )() // ignore returned promise (start thread)
184
185         let details
186         let card_1 = async () => {
187           if (!document.getElementById('form').checkValidity()) {
188             document.getElementById('form').classList.add('was-validated');
189             $('#card-1-tick').hide()
190             $('#card-1-cross').show()
191             //$('#card-1-spinner').hide()
192             return false
193           }
194           document.getElementById('form').classList.remove('was-validated');
195
196           details = {
197             email: document.getElementById('email').value.slice(0, 256).toLowerCase(),
198             given_names: document.getElementById('given-names').value.slice(0, 256),
199             family_name: document.getElementById('family-name').value.slice(0, 256),
200             password: document.getElementById('password').value.slice(0, 256),
201             contact_me: document.getElementById('contact-me').checked ? true : false
202           }
203
204           try {
205             await api_call(
206               '/api/account/sign_up/create_account.json',
207               document.getElementById('verification-code').value.slice(0, 6).toLowerCase(),
208               details
209             )
210           }
211           catch (error) {
212             let problem = Problem.from(error)
213
214             $('#card-1-tick').hide()
215             $('#card-1-cross').show()
216             $('#card-1-spinner').hide()
217
218             document.getElementById('card-1-message').textContent = problem.detail
219             //document.getElementById('card-1-message').classList.remove('text-success')
220             document.getElementById('card-1-message').classList.add('text-danger')
221             $('#card-1-message').show()
222
223             $('#card-1-collapse').collapse('show')
224             return false
225           }
226           $('#card-1-tick').show()
227           $('#card-1-cross').hide()
228           $('#card-1-spinner').hide()
229           document.getElementById('card-1-message').textContent = `Your account with email "${details.email}" has been created.`
230           //document.getElementById('card-1-message').classList.add('text-success')
231           document.getElementById('card-1-message').classList.remove('text-danger')
232           $('#card-1-message').show()
233           return true
234         }
235
236         let card_2 = async () => {
237           $('#card-2-tick').hide()
238           $('#card-2-cross').hide()
239           $('#card-2-spinner').show()
240           document.getElementById('card-2').scrollIntoView()
241
242           try {
243             await api_call(
244               '/api/account/sign_up/send_email_verification_link.json',
245               details.email
246             )
247           }
248           catch (error) {
249             let problem = Problem.from(error)
250
251             $('#card-2-tick').hide()
252             $('#card-2-cross').show()
253             $('#card-2-spinner').hide()
254
255             document.getElementById('card-2-message').textContent = problem.detail
256             //document.getElementById('card-2-message').classList.remove('text-success')
257             document.getElementById('card-2-message').classList.add('text-danger')
258             $('#card-2-message').show()
259
260             $('#card-2-collapse').collapse('show')
261             return false
262           }
263           $('#card-2-tick').show()
264           $('#card-2-cross').hide()
265           $('#card-2-spinner').hide()
266
267           document.getElementById('card-2-message').textContent = `Email verification link has been sent to "${details.email}". Please check your email for next steps.`
268           //document.getElementById('card-2-message').classList.add('text-success')
269           document.getElementById('card-2-message').classList.remove('text-danger')
270           $('#card-2-message').show()
271           return true
272         }
273
274         document.addEventListener(
275           'DOMContentLoaded',
276           () => {
277             document.getElementById('given-names').addEventListener(
278               'input',
279               () => {input_semaphore.release()}
280             )
281             document.getElementById('family-name').addEventListener(
282               'input',
283               () => {input_semaphore.release()}
284             )
285             document.getElementById('email').addEventListener(
286               'input',
287               () => {input_semaphore.release()}
288             )
289             //document.getElementById('password').addEventListener(
290             //  'input',
291             //  () => {input_semaphore.release()}
292             //)
293             document.getElementById('contact-me').addEventListener(
294               'input',
295               () => {input_semaphore.release()}
296             )
297             //document.getElementById('verification-code').addEventListener(
298             //  'input',
299             //  () => {input_semaphore.release()}
300             //)
301
302             let image_seq = 1
303             document.getElementById('card-1-new-code').addEventListener(
304               'click',
305               () => {
306                 document.getElementById('verification-image').src = `/api/verification_image.png?seq=${image_seq}`
307                 image_seq += 1
308               }
309             )
310
311             document.getElementById('card-1-create-account').addEventListener(
312               'click',
313               async () => {
314                 if (await card_1() && await card_2())
315                   $('#card-2-collapse').collapse('show')
316               }
317             )
318
319             document.getElementById('card-2-back').addEventListener(
320               'click',
321               () => {$('#card-1-collapse').collapse('show')}
322             )
323
324             document.getElementById('card-2-resend-email').addEventListener(
325               'click',
326               async () => {
327                 if (await card_2())
328                   $('#card-2-collapse').collapse('show')
329               }
330             )
331           }
332         )
333       }
334     }
335   )
336 }