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')
13 // preload draft details if any
14 let transaction = await env.site.database.Transaction()
18 let root = await transaction.get()
19 let session = await get_session(env, root)
21 placeholder = await get_placeholder(env, session)
23 sign_up_draft = await session.get_json('sign_up_draft')
24 if (sign_up_draft === undefined || env.now >= sign_up_draft.expires)
27 await transaction.commit()
30 transaction.rollback()
40 await breadcrumbs(env, _out)
42 p/*.mt-3*/ {'Signing up allows you to leave comments on our blog and receive communications from us.'}
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.'}
46 div.accordion#accordion(role="tablist" aria-multiselectable="true") {
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)}
52 div.icon32-outer.mr-2#card-1-cross(hidden) {
53 div.icon32-inner {_out.push(icon_cross)}
55 div.icon32-outer.mr-2#card-1-spinner(hidden) {
57 div.spinner-border(role="status") {}
60 a.h5(data-toggle="collapse" data-parent="#accordion" href="#card-1-collapse" aria-expanded="true" aria-controls="card-1-collapse") {
64 div#card-1-collapse.collapse.show(role="tabpanel" aria-labelledby="card-1-heading" data-parent="#accordion") {
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.'}
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) {}
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.'}
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.'}
98 div.row/*.align-items-center*/ {
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) {}
104 input.custom-control-input#contact-me(type="checkbox") {}
106 label.custom-control-label(for="contact-me") {
107 'Contact me by email with updates and special offers'
113 label.form-label(for="verification-code") {'Verification code *'}
115 img#verification-image(src="/api/verification_image.png?seq=0" width="300px" height="150px") {}
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)}
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.'}
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)}
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)}
149 div.alert.'mt-3'.mb-0#card-1-alert(hidden) {}
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)}
158 div.icon32-outer.mr-2#card-2-cross(hidden) {
159 div.icon32-inner {_out.push(icon_cross)}
161 div.icon32-outer.mr-2#card-2-spinner(hidden) {
163 div.spinner-border(role="status") {}
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'
170 div#card-2-collapse.collapse(role="tabpanel" aria-labelledby="card-2-heading" data-parent="#accordion") {
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)}
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)}
185 div.alert.'mt-3'.mb-0#card-2-alert(hidden) {}
191 p.text-muted.mt-3 {'* These fields are required.'}
195 //script(src="/js/utils.js") {}
198 document.addEventListener(
201 let id_accordion = document.getElementById('accordion')
202 let id_card_1 = document.getElementById('card-1')
203 let id_card_1_alert = document.getElementById('card-1-alert')
204 let id_card_1_collapse = document.getElementById('card-1-collapse')
205 let id_card_1_create_account = document.getElementById('card-1-create-account')
206 let id_card_1_cross = document.getElementById('card-1-cross')
207 let id_card_1_heading = document.getElementById('card-1-heading')
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_alert = document.getElementById('card-2-alert')
213 let id_card_2_back = document.getElementById('card-2-back')
214 let id_card_2_collapse = document.getElementById('card-2-collapse')
215 let id_card_2_cross = document.getElementById('card-2-cross')
216 let id_card_2_heading = document.getElementById('card-2-heading')
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')
230 let input_semaphore = new BinarySemaphore(false)
234 await input_semaphore.acquire()
235 await new Promise(resolve => setTimeout(resolve, 3000))
236 input_semaphore.try_acquire()
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 ?
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
253 )() // ignore returned promise (start thread)
255 let card_1_edited = in_draft => {
257 input_semaphore.release()
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_alert.hidden = true
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_alert.hidden = true
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 alert
284 //id_card_1_alert.hidden = true
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')
293 id_valid_workaround.classList.remove('is-valid')
294 id_valid_workaround.classList.add('is-invalid')
296 id_form.classList.add('was-validated');
298 id_card_1_cross.hidden = false
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');
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
314 id_card_1_spinner.hidden = false
317 '/api/account/sign_up/create_account.json',
318 id_verification_code.value.slice(0, 6).toLowerCase(),
323 let problem = Problem.from(error)
325 id_card_1_cross.hidden = false
326 id_card_1_spinner.hidden = true
328 id_card_1_alert.textContent = problem.detail
329 id_card_1_alert.classList.remove('alert-success')
330 id_card_1_alert.classList.add('alert-danger')
331 id_card_1_alert.hidden = false
333 $('#card-1-collapse').collapse('show')
336 id_card_1_tick.hidden = false
337 id_card_1_spinner.hidden = true
338 id_card_1_alert.textContent = `Your account with email "${details.email}" has been created.`
339 id_card_1_alert.classList.add('alert-success')
340 id_card_1_alert.classList.remove('alert-danger')
341 id_card_1_alert.hidden = false
343 id_card_2_resend_email.disabled = false
344 id_card_2.scrollIntoView()
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 alert
353 //id_card_2_alert.hidden = true
357 '/api/account/send_email_verification_link.json',
362 let problem = Problem.from(error)
364 id_card_2_cross.hidden = false
365 id_card_2_spinner.hidden = true
367 id_card_2_alert.textContent = problem.detail
368 id_card_2_alert.classList.remove('alert-success')
369 id_card_2_alert.classList.add('alert-danger')
370 id_card_2_alert.hidden = false
372 $('#card-2-collapse').collapse('show')
375 id_card_2_tick.hidden = false
376 id_card_2_spinner.hidden = true
378 id_card_2_alert.textContent = `Email verification link has been sent to "${details.email}". Please check your email for next steps.`
379 id_card_2_alert.classList.add('alert-success')
380 id_card_2_alert.classList.remove('alert-danger')
381 id_card_2_alert.hidden = false
385 id_given_names.addEventListener(
387 () => {card_1_edited(true)}
389 id_family_name.addEventListener(
391 () => {card_1_edited(true)}
393 id_email.addEventListener(
395 () => {card_1_edited(true)}
397 id_password.addEventListener(
399 () => {card_1_edited(false)}
401 id_contact_me.addEventListener(
403 () => {card_1_edited(true)}
405 id_verification_code.addEventListener(
407 () => {card_1_edited(false)}
411 id_card_1_new.addEventListener(
414 id_verification_image.src =
415 `/api/verification_image.png?seq=${image_seq}`
420 id_card_1_create_account.addEventListener(
423 if (await card_1() && await card_2())
424 $('#card-2-collapse').collapse('show')
428 id_card_2_back.addEventListener(
430 () => {$('#card-1-collapse').collapse('show')}
433 id_card_2_resend_email.addEventListener(
437 $('#card-2-collapse').collapse('show')