Get contact form working again with new style form and API
[ndcode_site.git] / contact / index.html.jst
index d74ae78..bb28ef7 100644 (file)
-let querystring = require('querystring')
-let stream_buffers = require('stream-buffers')
+let logjson = (await import('@ndcode/logjson')).default
 let XDate = require('xdate')
 
 return async env => {
   let breadcrumbs = await _require('/_lib/breadcrumbs.jst')
-  let globals = await env.site.get_json('/_config/globals.json')
+  let icon_cross = await env.site.get_min_svg('/_svg/icon_cross.svg')
+  let icon_tick = await env.site.get_min_svg('/_svg/icon_tick.svg')
   let navbar = await _require('/_lib/navbar.jst')
+  let session_cookie = await _require('/_lib/session_cookie.jst')
+
+  // preload draft details if any
+  let transaction = await env.site.database.Transaction(), draft_details
+  try {
+    // initialize env.session_key, set cookie in env.response
+    let session = await session_cookie(env, transaction)
+
+    let contact_draft = await session.get('contact_draft')
+    draft_details =
+      contact_draft !== undefined &&
+        XDate.now() < await logjson.logjson_to_json(
+          await contact_draft.get('expires')
+        ) ? {
+          email: await logjson.logjson_to_json(
+            await contact_draft.get('email')
+          ),
+          given_names: await logjson.logjson_to_json(
+            await contact_draft.get('given_names')
+          ),
+          family_name: await logjson.logjson_to_json(
+            await contact_draft.get('family_name')
+          ),
+          company: await logjson.logjson_to_json(
+            await contact_draft.get('company')
+          ),
+          email: await logjson.logjson_to_json(
+            await contact_draft.get('email')
+          ),
+          message: await logjson.logjson_to_json(
+            await contact_draft.get('message')
+          )
+        } : null
+
+    await transaction.commit()
+  }
+  catch (error) {
+    transaction.rollback()
+    throw error
+  }
+  console.log('draft_details', JSON.stringify(draft_details))
 
   await navbar(
     env,
+    // head
     async _out => {},
+    // body
     async _out => {
       await breadcrumbs(env, _out)
 
-      if (env.request.method == 'POST') {
-        let write_stream = new stream_buffers.WritableStreamBuffer()
-        let data = new Promise(
-          (resolve, reject) => {
-            write_stream.
-            on('finish', () => {resolve(write_stream.getContents())}).
-            on('error', () => {reject()})
-          }
-        )
-        env.request.pipe(write_stream)
-        let query = querystring.parse((await data).toString())
-        console.log('received contact form:', query.email)
-
-        // save the form contents in a dated logfile, so that we can
-        // recover manually if the email doesn't send for some reason
-        date = new XDate()
-        query.date = date.toUTCString()
-
-        let transaction = await env.site.database.Transaction()
-        ;(
-          await (
-            await (
-              await (
-                await transaction.get({})
-              ).get('logs', {})
-            ).get(date.toUTCString('yyyyMMdd'), {})
-          ).get('contact', [])
-        ).push(transaction.json_to_logjson(query))
-        transaction.commit()
-
-        // send email (asynchronously)
-        let emailjs_contact = await env.site.get_emailjs(
-          '/_config/email_contact.json'
-        )
-        emailjs_contact.send(
-          {
-            from: globals.contact_from,
-            'reply-to':
-              `${query.first_name} ${query.last_name} <${query.email}>`,
-            to: globals.contact_to,
-            subject:
-              Object.prototype.hasOwnProperty.call(query, 'company') ?
-              'Enquiry: ' + query.company :
-              'Enquiry',
-            text: query.message
-          },
-          (err, message) => {
-            if (err)
-              console.error(err.stack || err.message)
-            else
-              console.log('sent contact email:', query.email)
-          }
-        ) // ignore returned promise
+      p {'Do you require more information, or assistance with integrating the projects on this site? We’d love to hear from you.'}
 
-        p {'Thanks! We\'ll be in touch as soon as we can.'}
-      }
-      else {
-        p {'Do you require more information or consulting assistance with integrating the projects on this site? We\'d love to hear from you.'}
-
-        form#contact-form(method="post" action="index.html" role="form") {
-          div.row {
-            div.col-md-6 {
-              div.form-group {
-                label(for="contact_form_first_name") {'First name *'}
-                input.form-control#contact_form_first_name(type="text" name="first_name" placeholder="Please enter your first name" required="required" data-error="First name is required.") {}
-                div.help-block.with-errors {}
-              }
+      div.accordion#accordion.mb-5(role="tablist" aria-multiselectable="true") {
+        div.card#step-1 {
+          div.card-header#step-1-heading(role="tab") {
+            span#step-1-tick(style="display: none;") {
+              span.icon-color.pr-3 {_out.push(icon_tick)}
             }
-            div.col-md-6 {
-              div.form-group {
-                label(for="contact_form_last_name") {'Last name *'}
-                input.form-control#contact_form_last_name(type="text" name="last_name" placeholder="Please enter your last name" required="required" data-error="Last name is required.") {}
-                div.help-block.with-errors {}
-              }
+            span#step-1-cross(style="display: none;") {
+              span.icon-color.pr-3 {_out.push(icon_cross)}
+            }
+            //span#step-1-spinner(style="display: none;") {
+            //  span.icon-color.pr-3 {
+            //    div.spinner-border(role="status") {
+            //      span.sr-only {'Loading...'}
+            //    }
+            //  }
+            //}
+            a.h5(data-toggle="collapse" data-parent="#accordion" href="#step-1-collapse" aria-expanded="true" aria-controls="step-1-collapse") {
+              'Enquiry details'
             }
           }
-          div.row {
-            div.col-md-6 {
-              div.form-group {
-                label(for="contact_form_company") {'Company'}
-                input.form-control#contact_form_company(type="text" name="company" placeholder="Please enter your company") {}
-                div.help-block.with-errors {}
+          div#step-1-collapse.collapse.show(role="tabpanel" aria-labelledby="step-1-heading" data-parent="#accordion") {
+            div.card-body {
+              div.row {
+                div.col-md-6 {
+                  div.form-group {
+                    label.form-label(for="given-names") {'Given names *'}
+                    input.form-control#given-names(type="text" value=draft_details ? draft_details.given_names : '' placeholder="Your given names" required="required" maxlength=256) {}
+                  }
+                }
+                div.col-md-6 {
+                  div.form-group {
+                    label.form-label(for="family-name") {'Family name'}
+                    input.form-control#family-name(type="text" value=draft_details ? draft_details.family_name : '' placeholder="Your family name" maxlength=256) {}
+                  }
+                }
               }
-            }
-            div.col-md-6 {
-              div.form-group {
-                label(for="contact_form_email") {'Email *'}
-                input.form-control#contact_form_email(type="email" name="email" placeholder="Please enter your email" required="required" data-error="Valid email is required.") {}
-                div.help-block.with-errors {}
+              div.row {
+                div.col-md-6 {
+                  div.form-group {
+                   label.form-label(for="company") {'Company'}
+                    input.form-control#company(type="company" value=draft_details ? draft_details.company : '' placeholder="Your company" maxlength=256) {}
+                  }
+                }
+                div.col-md-6 {
+                  div.form-group {
+                   label.form-label(for="email") {'Email *'}
+                    input.form-control#email(type="email" value=draft_details ? draft_details.email : '' placeholder="Your email address" required="required" maxlength=256) {}
+                  }
+                }
+              }
+              div.row {
+                div.col-md-12 {
+                  div.form-group {
+                    label.form-label(for="message") {'Message *'}
+                    textarea.form-control#message(placeholder="Your message" required="required" rows=6 maxlength=65536) {
+                      if (draft_details)
+                        `${draft_details.message}`
+                    }
+                  }
+                }
               }
+
+              button.btn.btn-success#step-1-continue(type="button") {'Continue'}
+              p.'mt-3'.mb-0 {'* These fields are required.'}
             }
           }
-          div.row {
-            div.col-md-12 {
-              div.form-group {
-                label(for="contact_form_message") {'Message *'}
-                textarea.form-control#contact_form_message(name="message" placeholder="Please explain your application" rows="4" required="required" data-error="Please, leave us a message.") {}
-                div.help-block.with-errors {}
+        }
+        div.card#step-2 {
+          div.card-header#step-2-heading(role="tab") {
+            span#step-2-tick(style="display: none;") {
+              span.icon-color.pr-3 {_out.push(icon_tick)}
+            }
+            span#step-2-cross(style="display: none;") {
+              span.icon-color.pr-3 {_out.push(icon_cross)}
+            }
+            span#step-2-spinner(style="display: none;") {
+              span.icon-color.pr-3 {
+                div.spinner-border(role="status") {
+                  span.sr-only {'Loading...'}
+                }
               }
             }
-          }
-          p {} // fix this later
-          div.row {
-            div.col-md-12 {
-              button.btn.btn-success.btn-send(type="submit") {'Send message'}
+            a.h5.collapsed(data-toggle="collapse" data-parent="#accordion" href="#step-2-collapse" aria-expanded="false" aria-controls="step-2-collapse") {
+              'Send enquiry'
             }
           }
-          p {} // fix this later
-          div.row {
-            div.col-md-12 {
-              p.text-muted {
-                strong {'*'}
-                'These fields are required.'
-                //'Contact form template by '
-                //a(href="https://bootstrapious.com/p/how-to-build-a-working-bootstrap-contact-form" target="_blank") {'Bootstrapious'}
-                //'.'
-              }
+          div#step-2-collapse.collapse(role="tabpanel" aria-labelledby="step-2-heading" data-parent="#accordion") {
+            div.card-body {
+              p#step-2-message {'Please enter enquiry details first.'}
+
+              button.btn.btn-outline-secondary#step-2-back(type="button") {'Back'}
+              button.btn.btn-outline-secondary.ml-3#step-2-resend-enquiry(type="button") {'Re-send enquiry'}
             }
           }
         }
       }
     },
-    async _out => {}
+    // scripts
+    async _out => {
+      //script(src="/js/api_call.js") {}
+
+      script {
+        //let api_contact_get_draft = async (...arguments) => api_call(
+        //  '/api/contact/get_draft.json',
+        //  ...arguments
+        //)
+        let api_contact_set_draft = async (...arguments) => api_call(
+          '/api/contact/set_draft.json',
+          ...arguments
+        )
+        let api_contact_send_enquiry = async (...arguments) => api_call(
+          '/api/contact/send_enquiry.json',
+          ...arguments
+        )
+
+        let draft_timeout_running = false
+        let draft_timeout_handler = async () => {
+          draft_timeout_running = false
+          await api_contact_set_draft(
+            {
+              given_names: document.getElementById('given-names').value.slice(0, 256),
+              family_name: document.getElementById('family-name').value.slice(0, 256),
+              company: document.getElementById('company').value.slice(0, 256),
+              email: document.getElementById('email').value.slice(0, 256).toLowerCase(),
+              message: document.getElementById('message').value.slice(0, 65536)
+            }
+          )
+          //console.log('draft', await api_contact_get_draft())
+        }
+        let draft_change_handler = () => {
+          if (!draft_timeout_running) {
+            draft_timeout_running = true
+            setTimeout(draft_timeout_handler, 5000)
+          }
+        }
+
+        let details
+        let step_1 = async () => {
+          if (
+            !document.getElementById('given-names').reportValidity() ||
+              !document.getElementById('family-name').reportValidity() ||
+              !document.getElementById('company').reportValidity() ||
+              !document.getElementById('email').reportValidity() ||
+              !document.getElementById('message').reportValidity()
+          ) {
+            $('#step-1-tick').hide()
+            $('#step-1-cross').show()
+            //$('#step-1-spinner').hide()
+            return false
+          }
+          $('#step-1-tick').show()
+          $('#step-1-cross').hide()
+          //$('#step-1-spinner').hide()
+
+          details = {
+            given_names: document.getElementById('given-names').value.slice(0, 256),
+            family_name: document.getElementById('family-name').value.slice(0, 256),
+            company: document.getElementById('company').value.slice(0, 256),
+            email: document.getElementById('email').value.slice(0, 256).toLowerCase(),
+            message: document.getElementById('message').value.slice(0, 65536)
+          }
+          return true
+        }
+
+        let step_2 = async () => {
+          $('#step-2-tick').hide()
+          $('#step-2-cross').hide()
+          $('#step-2-spinner').show()
+          document.getElementById('step-2').scrollIntoView()
+
+          try {
+            await api_contact_send_enquiry(details)
+          }
+          catch (error) {
+            let problem =
+              error instanceof Problem ?
+                error :
+                new Problem(
+                  // title
+                  'Bad request',
+                  // detail
+                  (error.stack || error.message).toString()
+                  // status
+                  400
+                )
+
+            $('#step-2-tick').hide()
+            $('#step-2-cross').show()
+            $('#step-2-spinner').hide()
+
+            document.getElementById('step-2-message').textContent = problem.detail
+            $('#step-2-collapse').collapse('show')
+            return false
+          }
+          $('#step-2-tick').show()
+          $('#step-2-cross').hide()
+          $('#step-2-spinner').hide()
+
+          document.getElementById('step-2-message').textContent = 'We have received your enquiry. We will be in touch as soon as possible.'
+          return true
+        }
+
+        document.addEventListener(
+          'DOMContentLoaded',
+          () => {
+            document.getElementById('given-names').addEventListener(
+              'change',
+              draft_change_handler
+            )
+            document.getElementById('family-name').addEventListener(
+              'change',
+              draft_change_handler
+            )
+            document.getElementById('company').addEventListener(
+              'change',
+              draft_change_handler
+            )
+            document.getElementById('email').addEventListener(
+              'change',
+              draft_change_handler
+            )
+            document.getElementById('message').addEventListener(
+              'change',
+              draft_change_handler
+            )
+
+            document.getElementById('step-1-continue').addEventListener(
+              'click',
+              async () => {
+                if (await step_1() && await step_2())
+                  $('#step-2-collapse').collapse('show')
+              }
+            )
+
+            document.getElementById('step-2-back').addEventListener(
+              'click',
+              () => {$('#step-1-collapse').collapse('show')}
+            )
+
+            document.getElementById('step-2-resend-enquiry').addEventListener(
+              'click',
+              async () => {
+                if (await step_2())
+                  $('#step-2-collapse').collapse('show')
+              }
+            )
+          }
+        )
+      }
+    }
   )
 }