From 79d5947494a247f5d5115d486c051cf956191997 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Thu, 25 Jan 2024 09:57:49 -0700 Subject: [PATCH 01/62] base form load --- blocks/contact-form/contact-form.css | 0 blocks/contact-form/contact-form.js | 16 ++++++ blocks/contact-form/forms/contact-us.html | 65 +++++++++++++++++++++++ icons/error.svg | 1 + icons/success.svg | 1 + 5 files changed, 83 insertions(+) create mode 100644 blocks/contact-form/contact-form.css create mode 100644 blocks/contact-form/contact-form.js create mode 100644 blocks/contact-form/forms/contact-us.html create mode 100644 icons/error.svg create mode 100644 icons/success.svg diff --git a/blocks/contact-form/contact-form.css b/blocks/contact-form/contact-form.css new file mode 100644 index 00000000..e69de29b diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js new file mode 100644 index 00000000..e140a6c0 --- /dev/null +++ b/blocks/contact-form/contact-form.js @@ -0,0 +1,16 @@ +export default async function decorate(block) { + const displayValue = block.style.display; + block.style.display = 'none'; + + const formName = block.firstElementChild.innerText.trim(); + const thankYou = block.firstElementChild.nextElementSibling; + const data = await fetch(`${window.hlx.codeBasePath}/blocks/contact-form/forms/${formName}.html`); + if (!data.ok) { + /* eslint-disable-next-line no-console */ + console.error(`failed to load form: ${formName}`); + block.innerHTML = ''; + return; + } + + block.innerHTML = await data.text(); +} \ No newline at end of file diff --git a/blocks/contact-form/forms/contact-us.html b/blocks/contact-form/forms/contact-us.html new file mode 100644 index 00000000..3857b836 --- /dev/null +++ b/blocks/contact-form/forms/contact-us.html @@ -0,0 +1,65 @@ +
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+
are you currently working with an agent?
+
+
+
+
+
+
+
+
+
+
+
+ +
\ No newline at end of file diff --git a/icons/error.svg b/icons/error.svg new file mode 100644 index 00000000..1918d7eb --- /dev/null +++ b/icons/error.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/success.svg b/icons/success.svg new file mode 100644 index 00000000..12c3cfe8 --- /dev/null +++ b/icons/success.svg @@ -0,0 +1 @@ + \ No newline at end of file From fd3035c66208f1f05db27901771b0f3041cc8a51 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Fri, 26 Jan 2024 17:56:18 -0700 Subject: [PATCH 02/62] form styling --- blocks/contact-form/contact-form.css | 167 ++++++++++++++++++++++ blocks/contact-form/contact-form.js | 18 ++- blocks/contact-form/forms/contact-us.html | 131 +++++++++-------- icons/error.svg | 1 - icons/success.svg | 1 - 5 files changed, 252 insertions(+), 66 deletions(-) delete mode 100644 icons/error.svg delete mode 100644 icons/success.svg diff --git a/blocks/contact-form/contact-form.css b/blocks/contact-form/contact-form.css index e69de29b..bdd0d0e1 100644 --- a/blocks/contact-form/contact-form.css +++ b/blocks/contact-form/contact-form.css @@ -0,0 +1,167 @@ +.contact-form.block .contact-form form .message { + display: none; + padding: 10px 4px; + margin-bottom: 1em; + flex-direction: row; + align-items: center; + border: 1px solid; + column-gap: 4px; +} + +.contact-form.block .contact-form form .message.error { + display: flex; + color: var(--error); + border-color: var(--error); +} + +.contact-form.block .contact-form form .message.success { + display: flex; + color: var(--success); + border-color: var(--success); +} + +.contact-form.block .contact-form form .message .icon { + display: none; + align-self: flex-start; + width: 20px; + height: 20px; +} + +.contact-form.block .contact-form form .message.error .icon.error { + display: block; +} + +.contact-form.block .contact-form form .message.success .icon.success { + display: block; +} + +.contact-form.block .contact-form form .message .details { + display: flex; + flex-direction: column; +} + +.contact-form.block .contact-form form .message span { + font-family: var(--font-family-proxima); + font-size: var(--body-font-size-xs); + line-height: var(--line-height-s); +} + +.contact-form.block .contact-form form .inputs .name, +.contact-form.block .contact-form form .inputs .contact-info { + display: block; +} + +.contact-form.block .contact-form form .inputs input[type="text"], +.contact-form.block .contact-form form .inputs input[type="email"], +.contact-form.block .contact-form form .inputs textarea { + height: 50px; + width: 100%; + padding-left: 15px; + margin-bottom: 1em; + font-family: var(--font-family-proxima); + font-size: var(--body-font-size-s); + line-height: 50px; + color: var(--body-color); + border: 1px solid var(--dark-grey); +} + +.contact-form.block .contact-form form .inputs textarea { + width: 100%; + height: 110px; +} + +.contact-form.block .contact-form form .agent > div:first-child { + margin-bottom: .5rem; +} + +.contact-form.block .contact-form form .agent .label-check { + display: inline-flex; + gap: 10px; +} +.contact-form.block .contact-form form .agent label { + font-weight: 400; + font-size: 14px; + color: var(--body-color); + letter-spacing: .5px; + line-height: 1; + display: flex; + align-items: center; + justify-content: flex-start; + margin-bottom: 15px; + cursor: pointer; +} + +.contact-form.block .contact-form form .agent input[type="checkbox"] { + height: 24px; + min-width: 24px; + width: 24px; + border-radius: 50%; + border: 1px solid var(--body-color); + opacity: 0; + position: absolute; + z-index: 1; + cursor: pointer; +} + +.contact-form.block .contact-form form .agent .checkbox { + cursor: pointer; + height: 24px; + width: 24px; + border: 1px solid #aaa; + margin-right: 8px; + position: relative; +} + +.contact-form.block .contact-form form .agent .checkbox svg { + display: none; + height: 10px; + width: 12px; + top: -webkit-calc(50% - 5px); + top: calc(50% - 5px); + position: absolute; + left: 5px; +} + +.contact-form.block .contact-form form .agent input[type="checkbox"]:checked+.checkbox svg { + display: block; +} + +.contact-form.block .contact-form form .inputs input[type="text"].error, +.contact-form.block .contact-form form .inputs input[type="email"].error { + color: var(--error); + background-color: var(--error-highlight); + border: 2px solid var(--error); +} + +.contact-form.block .contact-form .g-recaptcha { + padding: 3rem 0; +} + +.contact-form.block .contact-form form .cta { + padding-bottom: 2rem; +} + +.contact-form.block .contact-form form .cta .button-container a.button.primary { + background-color: var(--primary-color); + color: var(--white); +} + +.contact-form.block .contact-form form .cta .button-container a.button.primary:hover { + background-color: var(--white); + color: var(--body-color); + border-color: var(--grey); +} + +@media (min-width: 600px) { + .contact-form.block .contact-form form .inputs .name, + .contact-form.block .contact-form form .inputs .contact-info { + display: flex; + gap: 10px; + + } + + .contact-form.block .contact-form form .inputs input[type="text"], + .contact-form.block .contact-form form .inputs input[type="email"] { + width: 50%; + } +} diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index e140a6c0..b394d83d 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -1,4 +1,5 @@ -export default async function decorate(block) { +// eslint-disable no-console +const addForm = async (block) => { const displayValue = block.style.display; block.style.display = 'none'; @@ -13,4 +14,17 @@ export default async function decorate(block) { } block.innerHTML = await data.text(); -} \ No newline at end of file + block.style.display = displayValue; +}; + +export default async function decorate(block) { + const observer = new IntersectionObserver((entries) => { + if (entries.some((e) => e.isIntersecting)) { + observer.disconnect(); + addForm(block); + } + }, { + rootMargin: '300px', + }); + observer.observe(block); +} diff --git a/blocks/contact-form/forms/contact-us.html b/blocks/contact-form/forms/contact-us.html index 3857b836..5418d5ff 100644 --- a/blocks/contact-form/forms/contact-us.html +++ b/blocks/contact-form/forms/contact-us.html @@ -1,65 +1,72 @@ -
-
- - -
-
- - -
-
-
-
-
-
-
-
-
are you currently working with an agent?
-
-
-
+
+ +
+ + + + + + +
+ +
-
-
-
-
-
-
-
- - \ No newline at end of file + +
+ +
+
+
+ Submit +
+
+ + \ No newline at end of file diff --git a/icons/error.svg b/icons/error.svg deleted file mode 100644 index 1918d7eb..00000000 --- a/icons/error.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/icons/success.svg b/icons/success.svg deleted file mode 100644 index 12c3cfe8..00000000 --- a/icons/success.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 096b81acdf6fbc79d58cc257a664693db6b5902e Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Tue, 30 Jan 2024 11:26:11 -0700 Subject: [PATCH 03/62] update form-header style --- styles/styles.css | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/styles/styles.css b/styles/styles.css index 55b923c1..d24033be 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -294,6 +294,17 @@ main .section.two-columns > div { flex: 0 1 40%; } +main .section.form-header h1 { + font-size: 60px; + font-family: var(--font-family-georgia); +} + +main .section.form-header p { + font-size: var(--heading-font-size-s); + margin: 0 auto 60px; + max-width: 420px; +} + /* Center rules for a section, but don't apply it to blocks! */ /* stylelint-disable-next-line no-descending-specificity */ main div[data-align="center"] h1, From 0f170402e33c304134e2ebe017b80941850ca7c5 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Fri, 2 Feb 2024 18:03:04 -0700 Subject: [PATCH 04/62] add validation --- blocks/contact-form/contact-form.css | 18 +- blocks/contact-form/contact-form.js | 221 ++++++++++++++++++ .../contact-form/forms/contact-property.html | 78 +++++++ blocks/contact-form/forms/contact-us.html | 14 +- blocks/side-modal/side-modal.css | 33 +++ blocks/side-modal/side-modal.js | 31 +++ scripts/scripts.js | 16 ++ scripts/util.js | 38 ++- styles/lazy-styles.css | 3 + 9 files changed, 438 insertions(+), 14 deletions(-) create mode 100644 blocks/contact-form/forms/contact-property.html create mode 100644 blocks/side-modal/side-modal.css create mode 100644 blocks/side-modal/side-modal.js diff --git a/blocks/contact-form/contact-form.css b/blocks/contact-form/contact-form.css index bdd0d0e1..df32f787 100644 --- a/blocks/contact-form/contact-form.css +++ b/blocks/contact-form/contact-form.css @@ -1,3 +1,9 @@ +.contact-form.block form, +.contact-form.block .company-email, +.contact-form.block .company-phone { + font-family: var(--font-family-proxima); +} + .contact-form.block .contact-form form .message { display: none; padding: 10px 4px; @@ -41,7 +47,6 @@ } .contact-form.block .contact-form form .message span { - font-family: var(--font-family-proxima); font-size: var(--body-font-size-xs); line-height: var(--line-height-s); } @@ -58,9 +63,8 @@ width: 100%; padding-left: 15px; margin-bottom: 1em; - font-family: var(--font-family-proxima); font-size: var(--body-font-size-s); - line-height: 50px; + line-height: var(--line-height-xs); color: var(--body-color); border: 1px solid var(--dark-grey); } @@ -68,24 +72,24 @@ .contact-form.block .contact-form form .inputs textarea { width: 100%; height: 110px; + padding: 15px; } .contact-form.block .contact-form form .agent > div:first-child { margin-bottom: .5rem; } -.contact-form.block .contact-form form .agent .label-check { +.contact-form.block .contact-form form .agent .agent-check { display: inline-flex; gap: 10px; } -.contact-form.block .contact-form form .agent label { +.contact-form.block .contact-form form .agent div { font-weight: 400; font-size: 14px; color: var(--body-color); letter-spacing: .5px; line-height: 1; display: flex; - align-items: center; justify-content: flex-start; margin-bottom: 15px; cursor: pointer; @@ -144,6 +148,8 @@ .contact-form.block .contact-form form .cta .button-container a.button.primary { background-color: var(--primary-color); color: var(--white); + text-transform: uppercase; + margin-right: 12px; } .contact-form.block .contact-form form .cta .button-container a.button.primary:hover { diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index b394d83d..4857bb68 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -1,3 +1,69 @@ +import { hideSideModal, i18nLookup } from '../../scripts/util.js'; + +const LOGIN_ERROR = 'There was a problem processing your request.'; +const i18n = await i18nLookup(); +const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; +const phoneRegex = /^\d{10}$/; + +function displayError(errors) { + const message = document.body.querySelector('.contact-form.block').querySelector('.message'); + const details = message.querySelector('.details'); + const spans = []; + [LOGIN_ERROR, ...errors].forEach((m) => { + const span = document.createElement('span'); + span.textContent = i18n(m); + spans.push(span); + }); + details.replaceChildren(...spans); + message.classList.add('error'); +} + +function isValid(form) { + const errors = []; + const firstName = form.querySelector('input[name="first_name"]'); + if (!firstName.value || firstName.value.trim().length === 0) { + errors.push(i18n('First name is required.')); + firstName.classList.add('error'); + } + + const lastName = form.querySelector('input[name="last_name"]'); + if (!lastName.value || lastName.value.trim().length === 0) { + errors.push(i18n('Last name is required.')); + lastName.classList.add('error'); + } + + const email = form.querySelector('input[name="email"]'); + if (!email.value || email.value.trim().length === 0) { + errors.push(i18n('Email address is required.')); + email.classList.add('error'); + } + if (!emailRegex.test(email)) { + errors.push(i18n('Please enter an email address in the format: email@domain.com.')); + email.classList.add('error'); + } + + const phone = form.querySelector('input[name="phone"]'); + if (!phone.value || phone.value.trim().length === 0) { + errors.push(i18n('Email address is required.')); + phone.classList.add('error'); + } + if (!phoneRegex.test(phone)) { + errors.push(i18n('Please enter a 10 digit phone number.')); + phone.classList.add('error'); + } + + if (errors.length > 0) { + displayError(errors); + return false; + } + return true; +} + +function submitContactForm(form) { + console.log('submitted'); + return isValid(form); +} + // eslint-disable no-console const addForm = async (block) => { const displayValue = block.style.display; @@ -14,6 +80,161 @@ const addForm = async (block) => { } block.innerHTML = await data.text(); + + const submitBtn = block.querySelector('.cta a.submit'); + if (submitBtn) { + submitBtn.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + submitContactForm(block.querySelector('form')); + }); + } + + const cancelBtn = block.querySelector('.cta a.cancel'); + if (cancelBtn) { + cancelBtn.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + hideSideModal(); + }); + } + + [...block.querySelectorAll('input[name="first_name"], input[name="last_name"]')] + .forEach((el) => { + el.addEventListener('blur', (e) => { + const { value } = e.currentTarget; + if (!value || value.trim().length === 0) { + e.currentTarget.classList.add('error'); + } else { + e.currentTarget.classList.remove('error'); + } + }); + }); + + [...block.querySelectorAll('input[name="phone"]')] + .forEach((el) => { + el.addEventListener('blur', (e) => { + const { value } = e.currentTarget; + if (!value || value.trim().length === 0 || !phoneRegex.test(value)) { + e.currentTarget.classList.add('error'); + } else { + e.currentTarget.classList.remove('error'); + } + }); + }); + + [...block.querySelectorAll('input[name="email"]')] + .forEach((el) => { + el.addEventListener('blur', (e) => { + const { value } = e.currentTarget; + if (!value || value.trim().length === 0 || !emailRegex.test(value)) { + e.currentTarget.classList.add('error'); + } else { + e.currentTarget.classList.remove('error'); + } + }); + }); + + if (thankYou) { + const form = block.querySelector('#contactForm'); + const oldSubmit = form.onsubmit; + thankYou.classList.add('form-thank-you'); + form.onsubmit = function handleSubmit() { + if (oldSubmit.call(this)) { + const body = new FormData(this); + const { action, method } = this; + fetch(action, { method, body, redirect: 'manual' }).then((resp) => { + /* eslint-disable-next-line no-console */ + if (!resp.ok) console.error(`Form submission failed: ${resp.status} / ${resp.statusText}`); + const firstContent = thankYou.firstElementChild; + if (firstContent.tagName === 'A') { + // redirect to thank you page + window.location.href = firstContent.href; + } else { + // show thank you content + const btn = thankYou.querySelector('a'); + const sideModal = document.querySelector('.side-modal-form'); + if (btn && sideModal) { + btn.setAttribute('href', '#'); + btn.addEventListener('click', (e) => { + e.preventDefault(); + hideSideModal(); + }); + sideModal?.replaceChildren(thankYou); + } else { + block.replaceChildren(thankYou); + } + } + }); + } + return false; + }; + } + + // If the form has it's own styles, add them. + const styles = block.querySelectorAll('style'); + styles.forEach((styleSheet) => { + document.head.appendChild(styleSheet); + }); + + // If the form has it's own scripts, load them one by one to maintain execution order. + // eslint-disable-next-line no-restricted-syntax + for (const script of [...block.querySelectorAll('script')]) { + let waitForLoad = Promise.resolve(); + // The script element added by innerHTML is NOT executed. + // The workaround is to create the new script tag, copy attibutes and content. + const newScript = document.createElement('script'); + newScript.setAttribute('type', 'text/javascript'); + // Copy script attributes to the new element. + script.getAttributeNames().forEach((attrName) => { + const attrValue = script.getAttribute(attrName); + newScript.setAttribute(attrName, attrValue); + + if (attrName === 'src') { + waitForLoad = new Promise((resolve) => { + newScript.addEventListener('load', resolve); + }); + } + }); + newScript.innerHTML = script.innerHTML; + script.remove(); + document.body.append(newScript); + + // eslint-disable-next-line no-await-in-loop + await waitForLoad; + } + + const inputs = block.querySelectorAll('input'); + inputs.forEach((formEl) => { + formEl.placeholder = i18n(formEl.placeholder); + formEl.ariaLabel = i18n(formEl.ariaLabel); + }); + + const taEl = block.querySelector('textarea'); + if (taEl && taEl.placeholder) taEl.placeholder = i18n(taEl.placeholder); + + // Get all checkboxes with class 'checkbox' + const checkboxes = document.querySelectorAll('input[type="checkbox"]'); + + // Define a function declaration to handle the change event + function handleChange() { + // Store the clicked checkbox in a variable + const clickedCheckbox = this; + + // Uncheck all checkboxes that are not the clicked checkbox + checkboxes.forEach((cb) => { + if (cb !== clickedCheckbox) { + cb.checked = false; + } + }); + } + + // Add the change event listener to each checkbox using the function declaration + checkboxes.forEach((checkbox) => { + checkbox.addEventListener('change', handleChange); + checkbox.nextElementSibling.addEventListener('change', handleChange); + }); + block.style.display = displayValue; }; diff --git a/blocks/contact-form/forms/contact-property.html b/blocks/contact-form/forms/contact-property.html new file mode 100644 index 00000000..aed6c0e5 --- /dev/null +++ b/blocks/contact-form/forms/contact-property.html @@ -0,0 +1,78 @@ +
+

Contact Us

+
+
+
Direct:
+
+
+ + + + + + +
+ +
+
+
+
+ + +
+
+ + +
+ +
+
+
Are you currently working with an agent?
+
+
+ +
+ + + +
+ yes +
+
+ +
+ + + +
+ no +
+
+
+
+
+
+ + +
+ +
+
+
+ Send + Cancel +
+
+
+
\ No newline at end of file diff --git a/blocks/contact-form/forms/contact-us.html b/blocks/contact-form/forms/contact-us.html index 5418d5ff..941dbf99 100644 --- a/blocks/contact-form/forms/contact-us.html +++ b/blocks/contact-form/forms/contact-us.html @@ -1,5 +1,5 @@
-
+
@@ -30,8 +30,8 @@
Are you currently working with an agent?
-
-
@@ -65,7 +65,7 @@
diff --git a/blocks/side-modal/side-modal.css b/blocks/side-modal/side-modal.css new file mode 100644 index 00000000..557d3d8e --- /dev/null +++ b/blocks/side-modal/side-modal.css @@ -0,0 +1,33 @@ +aside.side-modal { + overflow: hidden scroll; + position: fixed; + top: 200px; + bottom: 0; + width: 100vw; + right: -100vw; + transition: right .2s cubic-bezier(.4,0,.2,1) .1s; + background-color: white; + z-index: 1090; +} + +aside.side-modal[aria-expanded=true] { + right: 0; +} + +aside.side-modal > div { + width: 100%; + padding: 53px 15px 15px; +} + +@media (min-width: 768px) { + aside.side-modal { + width: 50vw; + right: -50vw; + } +} + +@media (min-width: 992px) { + aside.side-modal > div { + padding: 53px 16% 16%; + } +} diff --git a/blocks/side-modal/side-modal.js b/blocks/side-modal/side-modal.js new file mode 100644 index 00000000..850807a5 --- /dev/null +++ b/blocks/side-modal/side-modal.js @@ -0,0 +1,31 @@ +/* eslint-disable import/prefer-default-export */ +import { + decorateSections, decorateBlocks, loadBlocks, decorateButtons, decorateIcons, loadCSS, +} from '../../scripts/aem.js'; + +export async function showSideModal(a) { + const { href } = a; + const module$ = import(`${window.hlx.codeBasePath}/scripts/util.js`); + await loadCSS(`${window.hlx.codeBasePath}/blocks/side-modal/side-modal.css`); + const content = await fetch(`${href}.plain.html`); + + async function decorateSideModal(container) { + decorateButtons(container); + decorateIcons(container); + decorateSections(container); + decorateBlocks(container); + + container.classList.add('side-modal-form'); + const [theForm] = container.children; + theForm.classList.add('form'); + + await loadBlocks(container); + } + + if (content.ok) { + const html = await content.text(); + const fragment = document.createRange().createContextualFragment(html); + const [module] = await Promise.all([module$]); + module.showSideModal(fragment.children, decorateSideModal); + } +} diff --git a/scripts/scripts.js b/scripts/scripts.js index 3f1027ca..d45af915 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -152,6 +152,21 @@ function decorateVideoLinks(main) { }); } +function decorateFormLinks(main) { + async function openSideModal(event) { + event.preventDefault(); + const module = await import(`${window.hlx.codeBasePath}/blocks/side-modal/side-modal.js`); + if (module.showSideModal) { + await module.showSideModal(event.target); + } + } + main.querySelectorAll('a[href*="form"]').forEach((a) => { + if (a.href.endsWith('-form')) { + a.addEventListener('click', openSideModal); + } + }); +} + function decorateImages(main) { main.querySelectorAll('.section .default-content-wrapper picture').forEach((picture) => { const img = picture.querySelector('img'); @@ -262,6 +277,7 @@ export function decorateMain(main) { decorateSections(main); decorateBlocks(main); decorateVideoLinks(main); + decorateFormLinks(main); decorateImages(main); } diff --git a/scripts/util.js b/scripts/util.js index f89c8c4b..a0196e1c 100644 --- a/scripts/util.js +++ b/scripts/util.js @@ -1,4 +1,4 @@ -import { fetchPlaceholders } from './aem.js'; +import { fetchPlaceholders, loadCSS } from './aem.js'; /** * Creates the standard Spinner Div. @@ -49,6 +49,42 @@ export function showModal(content) { document.body.append(modal); } +let sideModal; +let focusElement; + +export function hideSideModal() { + if (!sideModal) return; + sideModal.ariaExpanded = false; + document.body.classList.remove('disable-scroll'); + if (focusElement) focusElement.focus(); +} + +export async function showSideModal(content, decorateContent) { + if (!sideModal) { + const fragment = document.createRange().createContextualFragment(` +
+ +
+ `); + sideModal = fragment.querySelector('.side-modal'); + document.body.append(...fragment.children); + } + const container = sideModal.querySelector('div'); + container.replaceChildren(...content); + + if (decorateContent) await decorateContent(container); + + // required delay for animation to work + setTimeout(() => { + document.body.classList.add('disable-scroll'); + sideModal.ariaExpanded = true; + }); + + focusElement = document.activeElement; +} + function createTextKey(text) { // create a key that can be used to look up the text in the placeholders const words = text.toLowerCase().replace(/[^a-z0-9\s]/g, '').split(/\s+/); diff --git a/styles/lazy-styles.css b/styles/lazy-styles.css index 84e7d6c9..c8ced821 100644 --- a/styles/lazy-styles.css +++ b/styles/lazy-styles.css @@ -1 +1,4 @@ /* add global styles that can be loaded post LCP here */ +body.disable-scroll, body.disable-scroll main { + overflow: hidden; +} \ No newline at end of file From c63b0eb4e52b5db01166f72a1ec4e488723a9c1a Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Mon, 5 Feb 2024 13:31:07 -0700 Subject: [PATCH 05/62] test submission --- blocks/contact-form/contact-form.js | 10 +++++----- scripts/util.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 4857bb68..318bdc7b 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -37,7 +37,7 @@ function isValid(form) { errors.push(i18n('Email address is required.')); email.classList.add('error'); } - if (!emailRegex.test(email)) { + if (!emailRegex.test(email.value)) { errors.push(i18n('Please enter an email address in the format: email@domain.com.')); email.classList.add('error'); } @@ -47,7 +47,7 @@ function isValid(form) { errors.push(i18n('Email address is required.')); phone.classList.add('error'); } - if (!phoneRegex.test(phone)) { + if (!phoneRegex.test(phone.value)) { errors.push(i18n('Please enter a 10 digit phone number.')); phone.classList.add('error'); } @@ -140,9 +140,9 @@ const addForm = async (block) => { const oldSubmit = form.onsubmit; thankYou.classList.add('form-thank-you'); form.onsubmit = function handleSubmit() { - if (oldSubmit.call(this)) { - const body = new FormData(this); - const { action, method } = this; + if (oldSubmit.call(form)) { + const body = new FormData(form); + const { action, method } = form; fetch(action, { method, body, redirect: 'manual' }).then((resp) => { /* eslint-disable-next-line no-console */ if (!resp.ok) console.error(`Form submission failed: ${resp.status} / ${resp.statusText}`); diff --git a/scripts/util.js b/scripts/util.js index a0196e1c..b73cfc38 100644 --- a/scripts/util.js +++ b/scripts/util.js @@ -1,4 +1,4 @@ -import { fetchPlaceholders, loadCSS } from './aem.js'; +import { fetchPlaceholders } from './aem.js'; /** * Creates the standard Spinner Div. From 938993e38b0b3d8f3b79011bea15ea90c481e1c1 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Mon, 5 Feb 2024 13:34:18 -0700 Subject: [PATCH 06/62] linting --- blocks/contact-form/contact-form.css | 18 +++++++++--------- blocks/side-modal/side-modal.css | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/blocks/contact-form/contact-form.css b/blocks/contact-form/contact-form.css index df32f787..e965f626 100644 --- a/blocks/contact-form/contact-form.css +++ b/blocks/contact-form/contact-form.css @@ -75,14 +75,6 @@ padding: 15px; } -.contact-form.block .contact-form form .agent > div:first-child { - margin-bottom: .5rem; -} - -.contact-form.block .contact-form form .agent .agent-check { - display: inline-flex; - gap: 10px; -} .contact-form.block .contact-form form .agent div { font-weight: 400; font-size: 14px; @@ -95,6 +87,15 @@ cursor: pointer; } +.contact-form.block .contact-form form .agent > div:first-child { + margin-bottom: .5rem; +} + +.contact-form.block .contact-form form .agent .agent-check { + display: inline-flex; + gap: 10px; +} + .contact-form.block .contact-form form .agent input[type="checkbox"] { height: 24px; min-width: 24px; @@ -120,7 +121,6 @@ display: none; height: 10px; width: 12px; - top: -webkit-calc(50% - 5px); top: calc(50% - 5px); position: absolute; left: 5px; diff --git a/blocks/side-modal/side-modal.css b/blocks/side-modal/side-modal.css index 557d3d8e..9e2e282d 100644 --- a/blocks/side-modal/side-modal.css +++ b/blocks/side-modal/side-modal.css @@ -10,7 +10,7 @@ aside.side-modal { z-index: 1090; } -aside.side-modal[aria-expanded=true] { +aside.side-modal[aria-expanded="true"] { right: 0; } From eae9440b84558c1cd1c985fddaed0fdc50b23625 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Wed, 14 Feb 2024 10:18:52 -0700 Subject: [PATCH 07/62] added validation, form data, and listeners --- blocks/contact-form/contact-form.css | 4 +- blocks/contact-form/contact-form.js | 206 ++++++++++------------ blocks/contact-form/forms/contact-us.html | 14 +- scripts/util.js | 28 ++- 4 files changed, 127 insertions(+), 125 deletions(-) diff --git a/blocks/contact-form/contact-form.css b/blocks/contact-form/contact-form.css index e965f626..75726806 100644 --- a/blocks/contact-form/contact-form.css +++ b/blocks/contact-form/contact-form.css @@ -96,7 +96,7 @@ gap: 10px; } -.contact-form.block .contact-form form .agent input[type="checkbox"] { +.contact-form.block .contact-form form .agent input[type="radio"] { height: 24px; min-width: 24px; width: 24px; @@ -126,7 +126,7 @@ left: 5px; } -.contact-form.block .contact-form form .agent input[type="checkbox"]:checked+.checkbox svg { +.contact-form.block .contact-form form .agent input[type="radio"]:checked+.checkbox svg { display: block; } diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 318bdc7b..1afbdf9f 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -1,9 +1,34 @@ -import { hideSideModal, i18nLookup } from '../../scripts/util.js'; +import { hideSideModal, i18nLookup, getCookieValue } from '../../scripts/util.js'; const LOGIN_ERROR = 'There was a problem processing your request.'; const i18n = await i18nLookup(); const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; -const phoneRegex = /^\d{10}$/; +const phoneRegex = /^[+]?[ (]?\d{3}[)]?[-.\s]?\d{3}[-.\s]?\d{4}$/; + +/** + * Adds customID and recipientID cookie values to the request body based on the form name. + * + * @param {FormData} form - The FormData object representing the form data. + */ +function addFranchiseData(form) { + console.log('add data to form'); + const formName = form.body.id; + const customID = getCookieValue('customerID'); + + form.append('customID', customID); + if (formName === 'contactForm') { + form.append('recipientId', 'https://ma312.bhhs.hsfaffiliates.com/profile/card#me'); + form.append('recipientName', 'Commonwealth Real Estate'); + form.append('recipientType', 'organization'); + form.append('text', `Name: ${form.get('first_name')} ${form.get('last_name')}\n + Email: ${form.get('email')}\n + Phone: ${form.get('phone')}\n\n + ${form.get('comments')}`); + } + if (formName === 'makeOffer') { + form.append('recipientID', 'recipientID'); + } +} function displayError(errors) { const message = document.body.querySelector('.contact-form.block').querySelector('.message'); @@ -18,7 +43,7 @@ function displayError(errors) { message.classList.add('error'); } -function isValid(form) { +function validateFormInputs(form) { const errors = []; const firstName = form.querySelector('input[name="first_name"]'); if (!firstName.value || firstName.value.trim().length === 0) { @@ -44,7 +69,7 @@ function isValid(form) { const phone = form.querySelector('input[name="phone"]'); if (!phone.value || phone.value.trim().length === 0) { - errors.push(i18n('Email address is required.')); + errors.push(i18n('Phone number is required.')); phone.classList.add('error'); } if (!phoneRegex.test(phone.value)) { @@ -56,14 +81,10 @@ function isValid(form) { displayError(errors); return false; } + console.log('validation passed'); return true; } -function submitContactForm(form) { - console.log('submitted'); - return isValid(form); -} - // eslint-disable no-console const addForm = async (block) => { const displayValue = block.style.display; @@ -81,16 +102,70 @@ const addForm = async (block) => { block.innerHTML = await data.text(); - const submitBtn = block.querySelector('.cta a.submit'); + const form = block.querySelector('form#contactForm'); + + if (thankYou) { + const oldSubmit = form.onsubmit; + thankYou.classList.add('form-thank-you'); + form.onsubmit = function handleSubmit() { + console.log('Handle submit'); // Check if this log appears + if (oldSubmit.call(this)) { + console.log('Form submitted'); // Check if this log appears + const body = new FormData(this); + addFranchiseData(body); + const { action, method } = this; + fetch(action, { method, body, redirect: 'manual' }).then((resp) => { + /* eslint-disable-next-line no-console */ + if (!resp.ok) console.error(`Form submission failed: ${resp.status} / ${resp.statusText}`); + const firstContent = thankYou.firstElementChild; + if (firstContent.tagName === 'A') { + // redirect to thank you page + window.location.href = firstContent.href; + } else { + // show thank you content + const btn = thankYou.querySelector('a'); + const sideModal = document.querySelector('.side-modal-form'); + if (btn && sideModal) { + btn.setAttribute('href', '#'); + btn.addEventListener('click', (e) => { + e.preventDefault(); + hideSideModal(); + }); + sideModal?.replaceChildren(thankYou); + } else { + block.replaceChildren(thankYou); + } + } + }); + } + return false; + }; + } + + const inputs = block.querySelectorAll('input'); + inputs.forEach((formEl) => { + formEl.placeholder = i18n(formEl.placeholder); + formEl.ariaLabel = i18n(formEl.ariaLabel); + }); + + const taEl = block.querySelector('textarea'); + if (taEl && taEl.placeholder) taEl.placeholder = i18n(taEl.placeholder); + + block.style.display = displayValue; + + const submitBtn = block.querySelector('.contact-form.block .cta a.submit'); if (submitBtn) { submitBtn.addEventListener('click', (e) => { + console.log('button clicked'); e.preventDefault(); e.stopPropagation(); - submitContactForm(block.querySelector('form')); + if (validateFormInputs(form)) { + form.submit(); + } }); } - const cancelBtn = block.querySelector('.cta a.cancel'); + const cancelBtn = block.querySelector('.contact-form.block .cta a.cancel'); if (cancelBtn) { cancelBtn.addEventListener('click', (e) => { e.preventDefault(); @@ -121,6 +196,11 @@ const addForm = async (block) => { e.currentTarget.classList.remove('error'); } }); + // create input mask + el.addEventListener('input', (e) => { + const x = e.target.value.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/); + e.target.value = !x[2] ? x[1] : `(${x[1]}) ${x[2]}${x[3] ? `-${x[3]}` : ''}`; + }); }); [...block.querySelectorAll('input[name="email"]')] @@ -134,108 +214,6 @@ const addForm = async (block) => { } }); }); - - if (thankYou) { - const form = block.querySelector('#contactForm'); - const oldSubmit = form.onsubmit; - thankYou.classList.add('form-thank-you'); - form.onsubmit = function handleSubmit() { - if (oldSubmit.call(form)) { - const body = new FormData(form); - const { action, method } = form; - fetch(action, { method, body, redirect: 'manual' }).then((resp) => { - /* eslint-disable-next-line no-console */ - if (!resp.ok) console.error(`Form submission failed: ${resp.status} / ${resp.statusText}`); - const firstContent = thankYou.firstElementChild; - if (firstContent.tagName === 'A') { - // redirect to thank you page - window.location.href = firstContent.href; - } else { - // show thank you content - const btn = thankYou.querySelector('a'); - const sideModal = document.querySelector('.side-modal-form'); - if (btn && sideModal) { - btn.setAttribute('href', '#'); - btn.addEventListener('click', (e) => { - e.preventDefault(); - hideSideModal(); - }); - sideModal?.replaceChildren(thankYou); - } else { - block.replaceChildren(thankYou); - } - } - }); - } - return false; - }; - } - - // If the form has it's own styles, add them. - const styles = block.querySelectorAll('style'); - styles.forEach((styleSheet) => { - document.head.appendChild(styleSheet); - }); - - // If the form has it's own scripts, load them one by one to maintain execution order. - // eslint-disable-next-line no-restricted-syntax - for (const script of [...block.querySelectorAll('script')]) { - let waitForLoad = Promise.resolve(); - // The script element added by innerHTML is NOT executed. - // The workaround is to create the new script tag, copy attibutes and content. - const newScript = document.createElement('script'); - newScript.setAttribute('type', 'text/javascript'); - // Copy script attributes to the new element. - script.getAttributeNames().forEach((attrName) => { - const attrValue = script.getAttribute(attrName); - newScript.setAttribute(attrName, attrValue); - - if (attrName === 'src') { - waitForLoad = new Promise((resolve) => { - newScript.addEventListener('load', resolve); - }); - } - }); - newScript.innerHTML = script.innerHTML; - script.remove(); - document.body.append(newScript); - - // eslint-disable-next-line no-await-in-loop - await waitForLoad; - } - - const inputs = block.querySelectorAll('input'); - inputs.forEach((formEl) => { - formEl.placeholder = i18n(formEl.placeholder); - formEl.ariaLabel = i18n(formEl.ariaLabel); - }); - - const taEl = block.querySelector('textarea'); - if (taEl && taEl.placeholder) taEl.placeholder = i18n(taEl.placeholder); - - // Get all checkboxes with class 'checkbox' - const checkboxes = document.querySelectorAll('input[type="checkbox"]'); - - // Define a function declaration to handle the change event - function handleChange() { - // Store the clicked checkbox in a variable - const clickedCheckbox = this; - - // Uncheck all checkboxes that are not the clicked checkbox - checkboxes.forEach((cb) => { - if (cb !== clickedCheckbox) { - cb.checked = false; - } - }); - } - - // Add the change event listener to each checkbox using the function declaration - checkboxes.forEach((checkbox) => { - checkbox.addEventListener('change', handleChange); - checkbox.nextElementSibling.addEventListener('change', handleChange); - }); - - block.style.display = displayValue; }; export default async function decorate(block) { diff --git a/blocks/contact-form/forms/contact-us.html b/blocks/contact-form/forms/contact-us.html index 941dbf99..6b8aa78b 100644 --- a/blocks/contact-form/forms/contact-us.html +++ b/blocks/contact-form/forms/contact-us.html @@ -1,5 +1,7 @@
-
+
@@ -21,7 +23,7 @@
-
-
+
-->
Submit diff --git a/scripts/util.js b/scripts/util.js index 9fea1784..9eac4d1b 100644 --- a/scripts/util.js +++ b/scripts/util.js @@ -116,9 +116,12 @@ export async function i18nLookup(prefix) { }; } -/* - * Returns the environment type based on the hostname. -*/ +/** + * Retrieves the environment type based on the provided hostname. + * + * @param {string} [hostname=window.location.hostname] - The hostname to determine the environment. + * @returns {string} The environment type ('live', 'preview', or 'dev'). + */ export function getEnvType(hostname = window.location.hostname) { const fqdnToEnvType = { 'commonmoves.com': 'live', @@ -131,11 +134,30 @@ export function getEnvType(hostname = window.location.hostname) { return fqdnToEnvType[hostname] || 'dev'; } +/** + * Retrieves the value of a cookie by its name. + * + * @param {string} cookieName - The name of the cookie to retrieve. + * @returns {string|null} The value of the cookie, or null if not found. + */ +export function getCookieValue(cookieName) { + const cookies = document.cookie.split(';'); + const foundCookie = cookies.find((cookie) => { + const trimmedCookie = cookie.trim(); + return trimmedCookie.includes(cookieName); + }); + if (foundCookie) { + return foundCookie.split('=', 2)[1]; + } + return null; +} + const Util = { getSpinner, showModal, i18nLookup, getEnvType, + getCookieValue, }; export default Util; From d5dcb8bafd852309c078033eff7586030fb46b82 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Thu, 15 Feb 2024 14:26:01 -0700 Subject: [PATCH 08/62] adjust data payload --- blocks/contact-form/contact-form.css | 60 +++++++------- blocks/contact-form/contact-form.js | 96 ++++++++++++++--------- blocks/contact-form/forms/contact-us.html | 9 +-- styles/styles.css | 7 +- 4 files changed, 99 insertions(+), 73 deletions(-) diff --git a/blocks/contact-form/contact-form.css b/blocks/contact-form/contact-form.css index 75726806..cc53b69a 100644 --- a/blocks/contact-form/contact-form.css +++ b/blocks/contact-form/contact-form.css @@ -4,7 +4,7 @@ font-family: var(--font-family-proxima); } -.contact-form.block .contact-form form .message { +.contact-form.block form.contact-form .message { display: none; padding: 10px 4px; margin-bottom: 1em; @@ -14,51 +14,51 @@ column-gap: 4px; } -.contact-form.block .contact-form form .message.error { +.contact-form.block form.contact-form .message.error { display: flex; color: var(--error); border-color: var(--error); } -.contact-form.block .contact-form form .message.success { +.contact-form.block form.contact-form .message.success { display: flex; color: var(--success); border-color: var(--success); } -.contact-form.block .contact-form form .message .icon { +.contact-form.block form.contact-form .message .icon { display: none; align-self: flex-start; width: 20px; height: 20px; } -.contact-form.block .contact-form form .message.error .icon.error { +.contact-form.block form.contact-form .message.error .icon.error { display: block; } -.contact-form.block .contact-form form .message.success .icon.success { +.contact-form.block form.contact-form .message.success .icon.success { display: block; } -.contact-form.block .contact-form form .message .details { +.contact-form.block form.contact-form .message .details { display: flex; flex-direction: column; } -.contact-form.block .contact-form form .message span { +.contact-form.block form.contact-form .message span { font-size: var(--body-font-size-xs); line-height: var(--line-height-s); } -.contact-form.block .contact-form form .inputs .name, -.contact-form.block .contact-form form .inputs .contact-info { +.contact-form.block form.contact-form .inputs .name, +.contact-form.block form.contact-form .inputs .contact-info { display: block; } -.contact-form.block .contact-form form .inputs input[type="text"], -.contact-form.block .contact-form form .inputs input[type="email"], -.contact-form.block .contact-form form .inputs textarea { +.contact-form.block form.contact-form .inputs input[type="text"], +.contact-form.block form.contact-form .inputs input[type="email"], +.contact-form.block form.contact-form .inputs textarea { height: 50px; width: 100%; padding-left: 15px; @@ -69,13 +69,13 @@ border: 1px solid var(--dark-grey); } -.contact-form.block .contact-form form .inputs textarea { +.contact-form.block form.contact-form .inputs textarea { width: 100%; height: 110px; padding: 15px; } -.contact-form.block .contact-form form .agent div { +.contact-form.block form.contact-form .agent div { font-weight: 400; font-size: 14px; color: var(--body-color); @@ -87,16 +87,16 @@ cursor: pointer; } -.contact-form.block .contact-form form .agent > div:first-child { +.contact-form.block form.contact-form .agent > div:first-child { margin-bottom: .5rem; } -.contact-form.block .contact-form form .agent .agent-check { +.contact-form.block form.contact-form .agent .agent-check { display: inline-flex; gap: 10px; } -.contact-form.block .contact-form form .agent input[type="radio"] { +.contact-form.block form.contact-form .agent input[type="radio"] { height: 24px; min-width: 24px; width: 24px; @@ -108,7 +108,7 @@ cursor: pointer; } -.contact-form.block .contact-form form .agent .checkbox { +.contact-form.block form.contact-form .agent .checkbox { cursor: pointer; height: 24px; width: 24px; @@ -117,7 +117,7 @@ position: relative; } -.contact-form.block .contact-form form .agent .checkbox svg { +.contact-form.block form.contact-form .agent .checkbox svg { display: none; height: 10px; width: 12px; @@ -126,12 +126,12 @@ left: 5px; } -.contact-form.block .contact-form form .agent input[type="radio"]:checked+.checkbox svg { +.contact-form.block form.contact-form .agent input[type="radio"]:checked+.checkbox svg { display: block; } -.contact-form.block .contact-form form .inputs input[type="text"].error, -.contact-form.block .contact-form form .inputs input[type="email"].error { +.contact-form.block form.contact-form .inputs input[type="text"].error, +.contact-form.block form.contact-form .inputs input[type="email"].error { color: var(--error); background-color: var(--error-highlight); border: 2px solid var(--error); @@ -141,33 +141,33 @@ padding: 3rem 0; } -.contact-form.block .contact-form form .cta { +.contact-form.block form.contact-form .cta { padding-bottom: 2rem; } -.contact-form.block .contact-form form .cta .button-container a.button.primary { +.contact-form.block form.contact-form .cta .button-container button.primary { background-color: var(--primary-color); color: var(--white); text-transform: uppercase; margin-right: 12px; } -.contact-form.block .contact-form form .cta .button-container a.button.primary:hover { +.contact-form.block form.contact-form .cta .button-container button.primary:hover { background-color: var(--white); color: var(--body-color); border-color: var(--grey); } @media (min-width: 600px) { - .contact-form.block .contact-form form .inputs .name, - .contact-form.block .contact-form form .inputs .contact-info { + .contact-form.block form.contact-form .inputs .name, + .contact-form.block form.contact-form .inputs .contact-info { display: flex; gap: 10px; } - .contact-form.block .contact-form form .inputs input[type="text"], - .contact-form.block .contact-form form .inputs input[type="email"] { + .contact-form.block form.contact-form .inputs input[type="text"], + .contact-form.block form.contact-form .inputs input[type="email"] { width: 50%; } } diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 1afbdf9f..41b0d9d4 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -11,23 +11,48 @@ const phoneRegex = /^[+]?[ (]?\d{3}[)]?[-.\s]?\d{3}[-.\s]?\d{4}$/; * @param {FormData} form - The FormData object representing the form data. */ function addFranchiseData(form) { - console.log('add data to form'); - const formName = form.body.id; - const customID = getCookieValue('customerID'); - - form.append('customID', customID); - if (formName === 'contactForm') { - form.append('recipientId', 'https://ma312.bhhs.hsfaffiliates.com/profile/card#me'); - form.append('recipientName', 'Commonwealth Real Estate'); - form.append('recipientType', 'organization'); - form.append('text', `Name: ${form.get('first_name')} ${form.get('last_name')}\n - Email: ${form.get('email')}\n - Phone: ${form.get('phone')}\n\n - ${form.get('comments')}`); + const jsonObj = {}; + jsonObj.form = form.id; + + const firstName = form.elements.first_name.value; + const lastName = form.elements.last_name.value; + const email = form.elements.email.value; + const phone = form.elements.phone.value; + const comments = form.elements.comments.value; + const hasAgentRadio = form.elements.hasAgent; + const hasAgentValue = Array.from(hasAgentRadio).find((radio) => radio.checked)?.value === 'yes'; + const officeIdMeta = document.querySelector('meta[name="office-id"]'); + + try { + const consumerID = getCookieValue('customerID'); + if (consumerID !== null) { + jsonObj.data.consumerID = consumerID; + } else { + /* eslint-disable-next-line no-console */ + console.warn('Cookie not found: customerID'); + } + } catch (error) { + /* eslint-disable-next-line no-console */ + console.error('Error getting cookie value:', error); } - if (formName === 'makeOffer') { - form.append('recipientID', 'recipientID'); + jsonObj.data = {}; + jsonObj.data.email = email; + jsonObj.data.name = `${firstName} ${lastName}`; + jsonObj.data.recipientId = `https://${officeIdMeta}.bhhs.hsfaffiliates.com/profile/card#me`; + jsonObj.data.recipientName = 'Commonwealth Real Estate'; + jsonObj.data.recipientType = 'organization'; + jsonObj.data.source = `https://${officeIdMeta}.bhhs.hsfaffiliates.com/profile/card#me`; + jsonObj.data.telephone = phone; + jsonObj.data.text = `Name: ${firstName} ${lastName}\n + Email: ${email}\n + Phone: ${phone}\n\n + ${comments}`; + jsonObj.data.url = `${window.location.href} | ${document.title}`; + jsonObj.data.workingWithAgent = hasAgentValue; + if (form.id === 'makeOffer') { + jsonObj.data.prixe = form.elements.price; } + return JSON.stringify(jsonObj); } function displayError(errors) { @@ -102,19 +127,29 @@ const addForm = async (block) => { block.innerHTML = await data.text(); - const form = block.querySelector('form#contactForm'); + const form = block.querySelector('form.contact-form'); + // if there is a thank you, highjack the submission + // otherwise submit form normally. if (thankYou) { - const oldSubmit = form.onsubmit; + const oldSubmit = validateFormInputs; thankYou.classList.add('form-thank-you'); - form.onsubmit = function handleSubmit() { + form.onsubmit = function handleSubmit(e) { console.log('Handle submit'); // Check if this log appears - if (oldSubmit.call(this)) { - console.log('Form submitted'); // Check if this log appears - const body = new FormData(this); - addFranchiseData(body); + e.preventDefault(); + if (oldSubmit(this)) { + console.log('OnSubmit called'); // Check if this log appears + const jsonData = addFranchiseData(this); const { action, method } = this; - fetch(action, { method, body, redirect: 'manual' }).then((resp) => { + console.log('Call fetch'); // Check if this log appears + fetch(action, { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: jsonData, + mode: 'no-cors', + }).then((resp) => { /* eslint-disable-next-line no-console */ if (!resp.ok) console.error(`Form submission failed: ${resp.status} / ${resp.statusText}`); const firstContent = thankYou.firstElementChild; @@ -134,6 +169,7 @@ const addForm = async (block) => { sideModal?.replaceChildren(thankYou); } else { block.replaceChildren(thankYou); + const temp = block.parent.nextSibling; } } }); @@ -153,18 +189,6 @@ const addForm = async (block) => { block.style.display = displayValue; - const submitBtn = block.querySelector('.contact-form.block .cta a.submit'); - if (submitBtn) { - submitBtn.addEventListener('click', (e) => { - console.log('button clicked'); - e.preventDefault(); - e.stopPropagation(); - if (validateFormInputs(form)) { - form.submit(); - } - }); - } - const cancelBtn = block.querySelector('.contact-form.block .cta a.cancel'); if (cancelBtn) { cancelBtn.addEventListener('click', (e) => { @@ -199,7 +223,7 @@ const addForm = async (block) => { // create input mask el.addEventListener('input', (e) => { const x = e.target.value.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/); - e.target.value = !x[2] ? x[1] : `(${x[1]}) ${x[2]}${x[3] ? `-${x[3]}` : ''}`; + e.target.value = !x[2] ? x[1] : `${x[1]}-${x[2]}${x[3] ? `-${x[3]}` : ''}`; }); }); diff --git a/blocks/contact-form/forms/contact-us.html b/blocks/contact-form/forms/contact-us.html index 6b8aa78b..604e2e9a 100644 --- a/blocks/contact-form/forms/contact-us.html +++ b/blocks/contact-form/forms/contact-us.html @@ -1,7 +1,6 @@ -
- +
+
@@ -67,7 +66,7 @@
-->
- Submit +
diff --git a/styles/styles.css b/styles/styles.css index d24033be..9d2aae37 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -295,8 +295,11 @@ main .section.two-columns > div { } main .section.form-header h1 { - font-size: 60px; - font-family: var(--font-family-georgia); + font-size: var(--heading-font-size-m); + font-family: var(--font-family-proxima); + font-weight: 700; + line-height: 130%; + margin-bottom: 15px; } main .section.form-header p { From 0b283a73ad4f841d173c1afdf469a9252f4a87e9 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Thu, 15 Feb 2024 15:56:13 -0700 Subject: [PATCH 09/62] remove the disclaimer --- blocks/contact-form/contact-form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 41b0d9d4..e97eb2c2 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -169,7 +169,7 @@ const addForm = async (block) => { sideModal?.replaceChildren(thankYou); } else { block.replaceChildren(thankYou); - const temp = block.parent.nextSibling; + block.parentNode.nextSibling.remove(); } } }); From 918947011d6791d462dcc3e257f1fb701ecf3f91 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Thu, 15 Feb 2024 17:01:32 -0700 Subject: [PATCH 10/62] json update --- blocks/contact-form/contact-form.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index e97eb2c2..5824475a 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -6,14 +6,11 @@ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const phoneRegex = /^[+]?[ (]?\d{3}[)]?[-.\s]?\d{3}[-.\s]?\d{4}$/; /** - * Adds customID and recipientID cookie values to the request body based on the form name. + * Adds form and cookie values to a JSON object. * * @param {FormData} form - The FormData object representing the form data. */ function addFranchiseData(form) { - const jsonObj = {}; - jsonObj.form = form.id; - const firstName = form.elements.first_name.value; const lastName = form.elements.last_name.value; const email = form.elements.email.value; @@ -21,21 +18,24 @@ function addFranchiseData(form) { const comments = form.elements.comments.value; const hasAgentRadio = form.elements.hasAgent; const hasAgentValue = Array.from(hasAgentRadio).find((radio) => radio.checked)?.value === 'yes'; - const officeIdMeta = document.querySelector('meta[name="office-id"]'); + const officeIdMeta = document.querySelector('meta[name="office-id"]').getAttribute('content'); + const jsonObj = {}; + jsonObj.data = {}; + jsonObj.form = form.id; try { - const consumerID = getCookieValue('customerID'); - if (consumerID !== null) { - jsonObj.data.consumerID = consumerID; + const consumerCookie = getCookieValue('consumerID'); + if (consumerCookie !== null) { + jsonObj.data.consumerID = consumerCookie; } else { /* eslint-disable-next-line no-console */ - console.warn('Cookie not found: customerID'); + console.warn('Cookie not found: consumerID'); } } catch (error) { /* eslint-disable-next-line no-console */ console.error('Error getting cookie value:', error); } - jsonObj.data = {}; + jsonObj.data.email = email; jsonObj.data.name = `${firstName} ${lastName}`; jsonObj.data.recipientId = `https://${officeIdMeta}.bhhs.hsfaffiliates.com/profile/card#me`; @@ -148,7 +148,7 @@ const addForm = async (block) => { 'Content-Type': 'application/json', }, body: jsonData, - mode: 'no-cors', + credentials: 'include', }).then((resp) => { /* eslint-disable-next-line no-console */ if (!resp.ok) console.error(`Form submission failed: ${resp.status} / ${resp.statusText}`); From c221ae2fc0c07cd30730699a2caf17b07cc0d10f Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Wed, 21 Feb 2024 14:57:47 -0700 Subject: [PATCH 11/62] update payload --- blocks/contact-form/contact-form.js | 24 +++++++++++++++---- .../contact-form/forms/contact-property.html | 13 +++++----- styles/lazy-styles.css | 2 +- styles/styles.css | 4 ++-- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 5824475a..55c742b2 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -49,8 +49,22 @@ function addFranchiseData(form) { ${comments}`; jsonObj.data.url = `${window.location.href} | ${document.title}`; jsonObj.data.workingWithAgent = hasAgentValue; - if (form.id === 'makeOffer') { - jsonObj.data.prixe = form.elements.price; + if (form.id === 'property-contact') { + jsonObj.data.addressLocality = 'Boston'; + jsonObj.data.addressRegion = 'Boston'; + jsonObj.data.agentType = 'Boston'; + jsonObj.data.coListing = 'Boston'; + jsonObj.data.postalCode = 'Boston'; + jsonObj.data.price = 'Boston'; + jsonObj.data.priceCurrency = 'Boston'; + jsonObj.data.streetAddress = 'Boston'; + } + if (form.id === 'make-offer' || form.id === 'see-property') { + jsonObj.listAor = 'mamlspin'; + jsonObj.mlsId = '234234'; + jsonObj.mlsKey = '234234'; + jsonObj.mlsName = 'MLSPIN - MLS Property Information Network'; + jsonObj.pid = '234234'; } return JSON.stringify(jsonObj); } @@ -162,8 +176,8 @@ const addForm = async (block) => { const sideModal = document.querySelector('.side-modal-form'); if (btn && sideModal) { btn.setAttribute('href', '#'); - btn.addEventListener('click', (e) => { - e.preventDefault(); + btn.addEventListener('click', (event) => { + event.preventDefault(); hideSideModal(); }); sideModal?.replaceChildren(thankYou); @@ -189,7 +203,7 @@ const addForm = async (block) => { block.style.display = displayValue; - const cancelBtn = block.querySelector('.contact-form.block .cta a.cancel'); + const cancelBtn = block.querySelector('.contact-form.block .cta button.cancel'); if (cancelBtn) { cancelBtn.addEventListener('click', (e) => { e.preventDefault(); diff --git a/blocks/contact-form/forms/contact-property.html b/blocks/contact-form/forms/contact-property.html index aed6c0e5..cba6dcb6 100644 --- a/blocks/contact-form/forms/contact-property.html +++ b/blocks/contact-form/forms/contact-property.html @@ -3,7 +3,7 @@

Contact Us

Direct:
-
+
@@ -33,10 +33,10 @@

Contact Us

autocomplete="off">
-
Are you currently working with an agent?
+
Are you currently working with an agent?
- +
@@ -45,7 +45,7 @@

Contact Us

yes
- +
@@ -53,7 +53,6 @@

Contact Us

no
-
@@ -70,8 +69,8 @@

Contact Us

- Send - Cancel + +
diff --git a/styles/lazy-styles.css b/styles/lazy-styles.css index c8ced821..4293513f 100644 --- a/styles/lazy-styles.css +++ b/styles/lazy-styles.css @@ -1,4 +1,4 @@ /* add global styles that can be loaded post LCP here */ -body.disable-scroll, body.disable-scroll main { +body.disable-scroll { overflow: hidden; } \ No newline at end of file diff --git a/styles/styles.css b/styles/styles.css index 9d2aae37..43e4f211 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -381,7 +381,7 @@ main .section .default-content-wrapper picture img { animation: spinner-spin 2s linear infinite; } -.button-container a { +.button-container a, .button-container button.cancel { display: inline-block; padding: .75em 1.5em; font-family: var(--font-family-proxima); @@ -463,7 +463,7 @@ main .section.button-primary-color .button-container a:hover { background-color: var(--primary-color); } -form button[type="submit"] { +form button[type="submit"], .button-container button { display: inline-block; padding: 13px 16px; margin: 16px 0; From 1ebbc3a5ed29be11a3ff3d45448cb2f6a8c3c744 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Mon, 26 Feb 2024 17:41:54 -0700 Subject: [PATCH 12/62] add team inquiry form --- blocks/contact-form/contact-form.css | 30 +- blocks/contact-form/contact-form.js | 134 ++-- blocks/contact-form/forms/contact-us.html | 4 +- blocks/contact-form/forms/join-our-team.html | 797 +++++++++++++++++++ 4 files changed, 908 insertions(+), 57 deletions(-) create mode 100644 blocks/contact-form/forms/join-our-team.html diff --git a/blocks/contact-form/contact-form.css b/blocks/contact-form/contact-form.css index cc53b69a..bf253409 100644 --- a/blocks/contact-form/contact-form.css +++ b/blocks/contact-form/contact-form.css @@ -52,13 +52,16 @@ } .contact-form.block form.contact-form .inputs .name, -.contact-form.block form.contact-form .inputs .contact-info { +.contact-form.block form.contact-form .inputs .contact-info, +.contact-form.block form.contact-form .inputs .size { display: block; } .contact-form.block form.contact-form .inputs input[type="text"], .contact-form.block form.contact-form .inputs input[type="email"], -.contact-form.block form.contact-form .inputs textarea { +.contact-form.block form.contact-form .inputs textarea, +.contact-form.block form.contact-form .inputs select, +.contact-form.block form.contact-form .inputs select option { height: 50px; width: 100%; padding-left: 15px; @@ -159,15 +162,30 @@ } @media (min-width: 600px) { - .contact-form.block form.contact-form .inputs .name, - .contact-form.block form.contact-form .inputs .contact-info { - display: flex; + .contact-form.block form.contact-form .inputs div { + display: flex !important; gap: 10px; - } .contact-form.block form.contact-form .inputs input[type="text"], .contact-form.block form.contact-form .inputs input[type="email"] { width: 50%; } + + .contact-form.block form.contact-form .inputs input[name="title"] { + width: calc(50% - 6px); + } + + .contact-form.block form.contact-form .inputs input[name="city"] { + flex: 0 0 41.667%; + } + + .contact-form.block form.contact-form .inputs input[name="state"] { + flex: 0 0 33.333%; + } + + .contact-form.block form.contact-form .inputs input[name="postalCode"] { + flex: 0 0 25%; + flex-shrink: 1; + } } diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 55c742b2..86e91d75 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -16,57 +16,89 @@ function addFranchiseData(form) { const email = form.elements.email.value; const phone = form.elements.phone.value; const comments = form.elements.comments.value; - const hasAgentRadio = form.elements.hasAgent; - const hasAgentValue = Array.from(hasAgentRadio).find((radio) => radio.checked)?.value === 'yes'; - const officeIdMeta = document.querySelector('meta[name="office-id"]').getAttribute('content'); - const jsonObj = {}; - jsonObj.data = {}; - jsonObj.form = form.id; - try { - const consumerCookie = getCookieValue('consumerID'); - if (consumerCookie !== null) { - jsonObj.data.consumerID = consumerCookie; - } else { + if (form.id !== 'team-inquiry') { + const hasAgentRadio = form.elements.hasAgent; + const hasAgentValue = Array.from(hasAgentRadio).find((radio) => radio.checked)?.value === 'yes'; + const officeIdMeta = document.querySelector('meta[name="office-id"]').getAttribute('content'); + const jsonObj = {}; + jsonObj.data = {}; + jsonObj.form = form.id; + + try { + const consumerCookie = getCookieValue('consumerID'); + if (consumerCookie !== null) { + jsonObj.data.consumerID = consumerCookie; + } else { + /* eslint-disable-next-line no-console */ + console.warn('Cookie not found: consumerID'); + } + } catch (error) { /* eslint-disable-next-line no-console */ - console.warn('Cookie not found: consumerID'); + console.error('Error getting cookie value:', error); } - } catch (error) { - /* eslint-disable-next-line no-console */ - console.error('Error getting cookie value:', error); - } - jsonObj.data.email = email; - jsonObj.data.name = `${firstName} ${lastName}`; - jsonObj.data.recipientId = `https://${officeIdMeta}.bhhs.hsfaffiliates.com/profile/card#me`; - jsonObj.data.recipientName = 'Commonwealth Real Estate'; - jsonObj.data.recipientType = 'organization'; - jsonObj.data.source = `https://${officeIdMeta}.bhhs.hsfaffiliates.com/profile/card#me`; - jsonObj.data.telephone = phone; - jsonObj.data.text = `Name: ${firstName} ${lastName}\n - Email: ${email}\n - Phone: ${phone}\n\n - ${comments}`; - jsonObj.data.url = `${window.location.href} | ${document.title}`; - jsonObj.data.workingWithAgent = hasAgentValue; - if (form.id === 'property-contact') { - jsonObj.data.addressLocality = 'Boston'; - jsonObj.data.addressRegion = 'Boston'; - jsonObj.data.agentType = 'Boston'; - jsonObj.data.coListing = 'Boston'; - jsonObj.data.postalCode = 'Boston'; - jsonObj.data.price = 'Boston'; - jsonObj.data.priceCurrency = 'Boston'; - jsonObj.data.streetAddress = 'Boston'; - } - if (form.id === 'make-offer' || form.id === 'see-property') { - jsonObj.listAor = 'mamlspin'; - jsonObj.mlsId = '234234'; - jsonObj.mlsKey = '234234'; - jsonObj.mlsName = 'MLSPIN - MLS Property Information Network'; - jsonObj.pid = '234234'; + jsonObj.data.email = email; + jsonObj.data.name = `${firstName} ${lastName}`; + jsonObj.data.recipientId = `https://${officeIdMeta}.bhhs.hsfaffiliates.com/profile/card#me`; + jsonObj.data.recipientName = 'Commonwealth Real Estate'; + jsonObj.data.recipientType = 'organization'; + jsonObj.data.source = `https://${officeIdMeta}.bhhs.hsfaffiliates.com/profile/card#me`; + jsonObj.data.telephone = phone; + jsonObj.data.text = `Name: ${firstName} ${lastName}\n + Email: ${email}\n + Phone: ${phone}\n\n + ${comments}`; + jsonObj.data.url = `${window.location.href} | ${document.title}`; + jsonObj.data.workingWithAgent = hasAgentValue; + if (form.id === 'property-contact') { + jsonObj.data.addressLocality = 'Boston'; + jsonObj.data.addressRegion = 'Boston'; + jsonObj.data.agentType = 'Boston'; + jsonObj.data.coListing = 'Boston'; + jsonObj.data.postalCode = 'Boston'; + jsonObj.data.price = 'Boston'; + jsonObj.data.priceCurrency = 'Boston'; + jsonObj.data.streetAddress = 'Boston'; + } + if (form.id === 'make-offer' || form.id === 'see-property') { + jsonObj.listAor = 'mamlspin'; + jsonObj.mlsId = '234234'; + jsonObj.mlsKey = '234234'; + jsonObj.mlsName = 'MLSPIN - MLS Property Information Network'; + jsonObj.pid = '234234'; + } + return JSON.stringify(jsonObj); } - return JSON.stringify(jsonObj); + const title = form.elements.title.value; + const zip = form.elements.title.value; + const country = form.elements.title.value; + const state = form.elements.title.value; + const city = form.elements.title.value; + const address1 = form.elements.title.value; + const address2 = form.elements.title.value; + const numAgents = form.elements.title.value; + const gci = form.elements.title.value; + + const formData = new FormData(); + formData.append('FirstName', firstName); + formData.append('LastName', lastName); + formData.append('Phone', phone); + formData.append('Email', email); + formData.append('Title', title); + formData.append('Message', comments); + formData.append('ZipCode', zip); + formData.append('Country', country); + formData.append('State', state); + formData.append('City', city); + formData.append('AddressOne', address1); + formData.append('AddressTwo', address2); + formData.append('NumOfAgents', numAgents); + formData.append('GCI', gci); + formData.append('Subject', 'Join our Team Website Inquiry'); + formData.append('SendEmail', true); + formData.append('To', 'marketing@commonmoves.com'); + return formData; } function displayError(errors) { @@ -154,13 +186,17 @@ const addForm = async (block) => { if (oldSubmit(this)) { console.log('OnSubmit called'); // Check if this log appears const jsonData = addFranchiseData(this); + const headers = new Headers(); + if (this.id === 'team-inquiry') { + headers.append('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); + } else { + headers.append('Content-Type', 'application/json; charset=UTF-8'); + } const { action, method } = this; console.log('Call fetch'); // Check if this log appears fetch(action, { method, - headers: { - 'Content-Type': 'application/json', - }, + headers, body: jsonData, credentials: 'include', }).then((resp) => { diff --git a/blocks/contact-form/forms/contact-us.html b/blocks/contact-form/forms/contact-us.html index 604e2e9a..4fc8aab7 100644 --- a/blocks/contact-form/forms/contact-us.html +++ b/blocks/contact-form/forms/contact-us.html @@ -52,7 +52,7 @@
- +
diff --git a/blocks/contact-form/forms/join-our-team.html b/blocks/contact-form/forms/join-our-team.html new file mode 100644 index 00000000..806d70d3 --- /dev/null +++ b/blocks/contact-form/forms/join-our-team.html @@ -0,0 +1,797 @@ +
+
+
+ + + + + + +
+ +
+
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ + + +
+ +
+
+
+ + +
+ +
+
+
+ +
+
+
+
\ No newline at end of file From dfc38db206bbd0ff5522d4506f35ab4461cdcfdd Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Wed, 28 Feb 2024 11:46:38 -0700 Subject: [PATCH 13/62] update form payload --- blocks/contact-form/contact-form.js | 29 +++++++++++--------- blocks/contact-form/forms/join-our-team.html | 15 ++-------- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 86e91d75..242ebddc 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -6,17 +6,19 @@ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const phoneRegex = /^[+]?[ (]?\d{3}[)]?[-.\s]?\d{3}[-.\s]?\d{4}$/; /** - * Adds form and cookie values to a JSON object. + * Adds form and cookie values to payload. * * @param {FormData} form - The FormData object representing the form data. */ function addFranchiseData(form) { + // Common form elements const firstName = form.elements.first_name.value; const lastName = form.elements.last_name.value; const email = form.elements.email.value; const phone = form.elements.phone.value; const comments = form.elements.comments.value; + // All forms except team inquiry if (form.id !== 'team-inquiry') { const hasAgentRadio = form.elements.hasAgent; const hasAgentValue = Array.from(hasAgentRadio).find((radio) => radio.checked)?.value === 'yes'; @@ -68,17 +70,20 @@ function addFranchiseData(form) { jsonObj.mlsName = 'MLSPIN - MLS Property Information Network'; jsonObj.pid = '234234'; } + // Data format to JSON return JSON.stringify(jsonObj); } + + // Remaining Team Inquiry form elements const title = form.elements.title.value; - const zip = form.elements.title.value; - const country = form.elements.title.value; - const state = form.elements.title.value; - const city = form.elements.title.value; - const address1 = form.elements.title.value; - const address2 = form.elements.title.value; - const numAgents = form.elements.title.value; - const gci = form.elements.title.value; + const zip = form.elements.postalCode.value; + const country = form.elements.country.value; + const state = form.elements.state.value; + const city = form.elements.city.value; + const address1 = form.elements.addressOne.value; + const address2 = form.elements.addressTwo.value; + const numAgents = form.elements.numOfAgents.value; + const gci = form.elements.gci.value; const formData = new FormData(); formData.append('FirstName', firstName); @@ -98,7 +103,8 @@ function addFranchiseData(form) { formData.append('Subject', 'Join our Team Website Inquiry'); formData.append('SendEmail', true); formData.append('To', 'marketing@commonmoves.com'); - return formData; + // Data format to URL Params + return new URLSearchParams(formData).toString(); } function displayError(errors) { @@ -181,10 +187,8 @@ const addForm = async (block) => { const oldSubmit = validateFormInputs; thankYou.classList.add('form-thank-you'); form.onsubmit = function handleSubmit(e) { - console.log('Handle submit'); // Check if this log appears e.preventDefault(); if (oldSubmit(this)) { - console.log('OnSubmit called'); // Check if this log appears const jsonData = addFranchiseData(this); const headers = new Headers(); if (this.id === 'team-inquiry') { @@ -193,7 +197,6 @@ const addForm = async (block) => { headers.append('Content-Type', 'application/json; charset=UTF-8'); } const { action, method } = this; - console.log('Call fetch'); // Check if this log appears fetch(action, { method, headers, diff --git a/blocks/contact-form/forms/join-our-team.html b/blocks/contact-form/forms/join-our-team.html index 806d70d3..392ac04b 100644 --- a/blocks/contact-form/forms/join-our-team.html +++ b/blocks/contact-form/forms/join-our-team.html @@ -33,7 +33,7 @@
-
-
-
- - -
- -
+
From 6428a4f28b244cb2d78d31eae994c285bfea3739 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Mon, 1 Apr 2024 16:49:57 -0600 Subject: [PATCH 14/62] build recaptcha server calls --- blocks/contact-form/contact-form.js | 59 ++++++++++++++++++++++- blocks/contact-form/forms/contact-us.html | 24 ++++----- scripts/delayed.js | 2 + 3 files changed, 72 insertions(+), 13 deletions(-) diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 242ebddc..05567cd6 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -4,6 +4,7 @@ const LOGIN_ERROR = 'There was a problem processing your request.'; const i18n = await i18nLookup(); const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const phoneRegex = /^[+]?[ (]?\d{3}[)]?[-.\s]?\d{3}[-.\s]?\d{4}$/; +let recaptchaToken = null; /** * Adds form and cookie values to payload. @@ -120,7 +121,7 @@ function displayError(errors) { message.classList.add('error'); } -function validateFormInputs(form) { +async function validateFormInputs(form) { const errors = []; const firstName = form.querySelector('input[name="first_name"]'); if (!firstName.value || firstName.value.trim().length === 0) { @@ -154,6 +155,35 @@ function validateFormInputs(form) { phone.classList.add('error'); } + if (!errors.length) { + const payload = `user_response=${encodeURIComponent(recaptchaToken)}`; + const options = { + method: 'POST', + body: payload, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + }, + }; + const url = 'https://www.commonmoves.com/bin/bhhs/googleRecaptchaServlet'; + + fetch(url, options) + .then((response) => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then((data) => { + // Handle the response based on the success property + if (!data.success) { + errors.push(i18n('Captcha verification is required.')); + } + }) + .catch(() => { + errors.push(i18n('Captcha verification failed.')); + }); + } + if (errors.length > 0) { displayError(errors); return false; @@ -231,6 +261,33 @@ const addForm = async (block) => { }; } + // eslint-disable-next-line no-restricted-syntax + for (const script of [...block.querySelectorAll('script')]) { + let waitForLoad = Promise.resolve(); + // the script element added by innerHTML is NOT executed + // the workaround is to create the new script tag, copy attibutes and content + const newScript = document.createElement('script'); + + newScript.setAttribute('type', 'text/javascript'); + // coping all script attribute to the new one + script.getAttributeNames().forEach((attrName) => { + const attrValue = script.getAttribute(attrName); + newScript.setAttribute(attrName, attrValue); + + if (attrName === 'src') { + waitForLoad = new Promise((resolve) => { + newScript.addEventListener('load', resolve); + }); + } + }); + newScript.innerHTML = script.innerHTML; + script.remove(); + document.body.append(newScript); + + // eslint-disable-next-line no-await-in-loop + await waitForLoad; + } + const inputs = block.querySelectorAll('input'); inputs.forEach((formEl) => { formEl.placeholder = i18n(formEl.placeholder); diff --git a/blocks/contact-form/forms/contact-us.html b/blocks/contact-form/forms/contact-us.html index 4fc8aab7..8a301a30 100644 --- a/blocks/contact-form/forms/contact-us.html +++ b/blocks/contact-form/forms/contact-us.html @@ -1,3 +1,14 @@ +
@@ -52,18 +63,7 @@
-
-
- - -
- -
+
diff --git a/scripts/delayed.js b/scripts/delayed.js index a1f80c5f..3c9d6e3d 100644 --- a/scripts/delayed.js +++ b/scripts/delayed.js @@ -18,3 +18,5 @@ async function loadAdobeLaunch() { } if (!window.location.host.includes('localhost')) await loadAdobeLaunch(); + +loadScript('https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit', { async: true, defer: true }); From ec90a7ec116fd8037c8d5428ac317cbc57237be2 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Thu, 18 Apr 2024 15:43:02 -0600 Subject: [PATCH 15/62] contact complete --- blocks/contact-form/contact-form.js | 86 ++++++++++++----------- blocks/contact-form/forms/contact-us.html | 1 + 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 05567cd6..30878a6b 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -155,6 +155,11 @@ async function validateFormInputs(form) { phone.classList.add('error'); } + if (errors.length > 0) { + displayError(errors); + return false; + } + if (!errors.length) { const payload = `user_response=${encodeURIComponent(recaptchaToken)}`; const options = { @@ -183,12 +188,6 @@ async function validateFormInputs(form) { errors.push(i18n('Captcha verification failed.')); }); } - - if (errors.length > 0) { - displayError(errors); - return false; - } - console.log('validation passed'); return true; } @@ -218,45 +217,48 @@ const addForm = async (block) => { thankYou.classList.add('form-thank-you'); form.onsubmit = function handleSubmit(e) { e.preventDefault(); - if (oldSubmit(this)) { - const jsonData = addFranchiseData(this); - const headers = new Headers(); - if (this.id === 'team-inquiry') { - headers.append('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); - } else { - headers.append('Content-Type', 'application/json; charset=UTF-8'); - } - const { action, method } = this; - fetch(action, { - method, - headers, - body: jsonData, - credentials: 'include', - }).then((resp) => { - /* eslint-disable-next-line no-console */ - if (!resp.ok) console.error(`Form submission failed: ${resp.status} / ${resp.statusText}`); - const firstContent = thankYou.firstElementChild; - if (firstContent.tagName === 'A') { - // redirect to thank you page - window.location.href = firstContent.href; - } else { - // show thank you content - const btn = thankYou.querySelector('a'); - const sideModal = document.querySelector('.side-modal-form'); - if (btn && sideModal) { - btn.setAttribute('href', '#'); - btn.addEventListener('click', (event) => { - event.preventDefault(); - hideSideModal(); - }); - sideModal?.replaceChildren(thankYou); + oldSubmit(this) + .then((result) => { + if (result) { + const jsonData = addFranchiseData(this); + const headers = new Headers(); + if (this.id === 'team-inquiry') { + headers.append('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); } else { - block.replaceChildren(thankYou); - block.parentNode.nextSibling.remove(); + headers.append('Content-Type', 'application/json; charset=UTF-8'); } + const { action, method } = this; + fetch(action, { + method, + headers, + body: jsonData, + credentials: 'include', + }).then((resp) => { + /* eslint-disable-next-line no-console */ + if (!resp.ok) console.error(`Form submission failed: ${resp.status} / ${resp.statusText}`); + const firstContent = thankYou.firstElementChild; + if (firstContent.tagName === 'A') { + // redirect to thank you page + window.location.href = firstContent.href; + } else { + // show thank you content + const btn = thankYou.querySelector('a'); + const sideModal = document.querySelector('.side-modal-form'); + if (btn && sideModal) { + btn.setAttribute('href', '#'); + btn.addEventListener('click', (event) => { + event.preventDefault(); + hideSideModal(); + }); + sideModal?.replaceChildren(thankYou); + } else { + block.replaceChildren(thankYou); + block.parentNode.nextSibling.remove(); + } + } + }); } }); - } return false; }; } @@ -359,5 +361,5 @@ export default async function decorate(block) { }, { rootMargin: '300px', }); - observer.observe(block); + await observer.observe(block); } diff --git a/blocks/contact-form/forms/contact-us.html b/blocks/contact-form/forms/contact-us.html index 8a301a30..fb753d25 100644 --- a/blocks/contact-form/forms/contact-us.html +++ b/blocks/contact-form/forms/contact-us.html @@ -2,6 +2,7 @@ var verifyCallback = function(response) { recaptchaToken = response; }; + var onloadCallback = function() { grecaptcha.render('captcha-20285', { 'sitekey' : '6LebYaYUAAAAAC9SqASljwaF57MpKSvEkwDOzk6l', From 74b78e97cfe3ac1bc1e52f25886cf291f85637b4 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Thu, 18 Apr 2024 18:59:15 -0600 Subject: [PATCH 16/62] added consumerID cookie and finished up reCaptcha verification --- blocks/contact-form/contact-form.js | 71 ++++++++++++-------- blocks/contact-form/forms/callback.js | 12 ++++ blocks/contact-form/forms/contact-us.html | 14 +--- blocks/contact-form/forms/join-our-team.html | 2 +- scripts/delayed.js | 23 ++++++- 5 files changed, 78 insertions(+), 44 deletions(-) create mode 100644 blocks/contact-form/forms/callback.js diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 30878a6b..4edc4353 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -1,10 +1,13 @@ +import { loadScript } from '../../scripts/aem.js'; import { hideSideModal, i18nLookup, getCookieValue } from '../../scripts/util.js'; const LOGIN_ERROR = 'There was a problem processing your request.'; const i18n = await i18nLookup(); const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const phoneRegex = /^[+]?[ (]?\d{3}[)]?[-.\s]?\d{3}[-.\s]?\d{4}$/; -let recaptchaToken = null; + +// Load reCaptcha script used on all forms. +loadScript('./blocks/contact-form/forms/callback.js'); /** * Adds form and cookie values to payload. @@ -155,38 +158,48 @@ async function validateFormInputs(form) { phone.classList.add('error'); } - if (errors.length > 0) { - displayError(errors); - return false; + if (form.id === 'team-inquiry') { + const numAgentsEl = form.querySelector('input[name="numOfAgents"]'); + if (!numAgentsEl.value || numAgentsEl.value.trim().length === 0) { + errors.push(i18n('Number of agents is required.')); + numAgentsEl.classList.add('error'); + } + const cgiEl = form.querySelector('input[name="CGI"]'); + if (!cgiEl.value || cgiEl.value.trim().length === 0) { + errors.push(i18n('CGI in USD is required.')); + cgiEl.classList.add('error'); + } } if (!errors.length) { - const payload = `user_response=${encodeURIComponent(recaptchaToken)}`; - const options = { - method: 'POST', - body: payload, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', - }, - }; - const url = 'https://www.commonmoves.com/bin/bhhs/googleRecaptchaServlet'; + if (recaptchaToken) { + const payload = `user_response=${encodeURIComponent(recaptchaToken)}`; + const options = { + method: 'POST', + body: payload, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + }, + }; + const url = '/bin/bhhs/googleRecaptchaServlet'; + await fetch(url, options) + .then((data) => { + // Handle the response based on the success property + if (!data.ok) { + errors.push(i18n('Captcha verification is required.')); + } + }) + .catch(() => { + errors.push(i18n('Captcha verification failed.')); + }); + } else { + errors.push(i18n('Captcha verification is required.')); + } + } - fetch(url, options) - .then((response) => { - if (!response.ok) { - throw new Error('Network response was not ok'); - } - return response.json(); - }) - .then((data) => { - // Handle the response based on the success property - if (!data.success) { - errors.push(i18n('Captcha verification is required.')); - } - }) - .catch(() => { - errors.push(i18n('Captcha verification failed.')); - }); + if (errors.length > 0) { + displayError(errors); + return false; } return true; } diff --git a/blocks/contact-form/forms/callback.js b/blocks/contact-form/forms/callback.js new file mode 100644 index 00000000..5ebb5c1d --- /dev/null +++ b/blocks/contact-form/forms/callback.js @@ -0,0 +1,12 @@ +let recaptchaToken = null; + +function verifyCallback(resp) { + recaptchaToken = resp; +} + +function onloadCallback() { + grecaptcha.render('captcha-20285', { + sitekey: '6LebYaYUAAAAAC9SqASljwaF57MpKSvEkwDOzk6l', + callback: verifyCallback, + }); +} \ No newline at end of file diff --git a/blocks/contact-form/forms/contact-us.html b/blocks/contact-form/forms/contact-us.html index fb753d25..40c4fd3f 100644 --- a/blocks/contact-form/forms/contact-us.html +++ b/blocks/contact-form/forms/contact-us.html @@ -1,18 +1,6 @@ -
+ action="/bin/bhhs/websiteTopicServlet">
diff --git a/blocks/contact-form/forms/join-our-team.html b/blocks/contact-form/forms/join-our-team.html index 392ac04b..fa3de2a4 100644 --- a/blocks/contact-form/forms/join-our-team.html +++ b/blocks/contact-form/forms/join-our-team.html @@ -1,6 +1,6 @@
+ action="/bin/bhhs/InquiryFormServlet">
diff --git a/scripts/delayed.js b/scripts/delayed.js index 3c9d6e3d..56eeb655 100644 --- a/scripts/delayed.js +++ b/scripts/delayed.js @@ -1,6 +1,6 @@ // eslint-disable-next-line import/no-cycle import { sampleRUM, loadScript } from './aem.js'; -import { getEnvType } from './util.js'; +import { getCookieValue, getEnvType } from './util.js'; // Core Web Vitals RUM collection sampleRUM('cwv'); @@ -17,6 +17,27 @@ async function loadAdobeLaunch() { }); } +async function loadIDServlet() { + const sessionID = getCookieValue('XSESSIONID'); + const options = { + method: 'POST', + body: `sameAs=%7B%22cregcontactid%22%3A%22${sessionID}`, + headers: { + 'Content-Type': 'application/json;charset=utf-8', + }, + }; + const url = '/bin/bhhs/graphIdServlet'; + const resp = await fetch(url, options); + if (resp.ok) { + const id = await resp.json(); + const idString = JSON.stringify(id); + document.cookie = `consumerID=${idString}`; + } +} + if (!window.location.host.includes('localhost')) await loadAdobeLaunch(); +if (!getCookieValue('consumerID')) { + loadIDServlet(); +} loadScript('https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit', { async: true, defer: true }); From b0fad8c763a42e0d6ed2a6da709a05b106208356 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Fri, 19 Apr 2024 11:28:15 -0600 Subject: [PATCH 17/62] reset recaptcha token --- blocks/contact-form/contact-form.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 4edc4353..eb309284 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -268,6 +268,9 @@ const addForm = async (block) => { block.replaceChildren(thankYou); block.parentNode.nextSibling.remove(); } + if (window.grecaptcha) { + recaptchaToken = null; + } } }); } From be268a527a8e52a5ab15230dd79e4bcedb64d4b5 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Fri, 19 Apr 2024 11:38:25 -0600 Subject: [PATCH 18/62] disable some lint errors --- blocks/contact-form/contact-form.js | 1 + blocks/contact-form/forms/callback.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index eb309284..dc3baf52 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -171,6 +171,7 @@ async function validateFormInputs(form) { } } + /* eslint-disable no-undef */ if (!errors.length) { if (recaptchaToken) { const payload = `user_response=${encodeURIComponent(recaptchaToken)}`; diff --git a/blocks/contact-form/forms/callback.js b/blocks/contact-form/forms/callback.js index 5ebb5c1d..3e7b9e53 100644 --- a/blocks/contact-form/forms/callback.js +++ b/blocks/contact-form/forms/callback.js @@ -1,3 +1,5 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable no-undef */ let recaptchaToken = null; function verifyCallback(resp) { @@ -9,4 +11,4 @@ function onloadCallback() { sitekey: '6LebYaYUAAAAAC9SqASljwaF57MpKSvEkwDOzk6l', callback: verifyCallback, }); -} \ No newline at end of file +} From 94844097c696a6881becb8bdc1b92a997c70dddd Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Fri, 19 Apr 2024 11:48:52 -0600 Subject: [PATCH 19/62] adjust for CLS --- blocks/contact-form/contact-form.css | 1 + 1 file changed, 1 insertion(+) diff --git a/blocks/contact-form/contact-form.css b/blocks/contact-form/contact-form.css index bf253409..8450563c 100644 --- a/blocks/contact-form/contact-form.css +++ b/blocks/contact-form/contact-form.css @@ -142,6 +142,7 @@ .contact-form.block .contact-form .g-recaptcha { padding: 3rem 0; + height: 174px; } .contact-form.block form.contact-form .cta { From 3d4ac1716e6da640e672896b0ee3f50ee8a90d7b Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Fri, 19 Apr 2024 14:22:23 -0600 Subject: [PATCH 20/62] add fragment block --- blocks/fragment/fragment.css | 13 +++++++++ blocks/fragment/fragment.js | 55 ++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 blocks/fragment/fragment.css create mode 100644 blocks/fragment/fragment.js diff --git a/blocks/fragment/fragment.css b/blocks/fragment/fragment.css new file mode 100644 index 00000000..b38cf896 --- /dev/null +++ b/blocks/fragment/fragment.css @@ -0,0 +1,13 @@ +/* suppress nested section padding */ +.fragment-wrapper > .section { + padding-left: 0; + padding-right: 0; +} + +.fragment-wrapper > .section:first-of-type { + padding-top: 0; +} + +.fragment-wrapper > .section:last-of-type { + padding-bottom: 0; +} diff --git a/blocks/fragment/fragment.js b/blocks/fragment/fragment.js new file mode 100644 index 00000000..648a70e1 --- /dev/null +++ b/blocks/fragment/fragment.js @@ -0,0 +1,55 @@ +/* + * Fragment Block + * Include content on a page as a fragment. + * https://www.aem.live/developer/block-collection/fragment + */ + +import { + decorateMain, +} from '../../scripts/scripts.js'; + +import { + loadBlocks, +} from '../../scripts/aem.js'; + +/** + * Loads a fragment. + * @param {string} path The path to the fragment + * @returns {HTMLElement} The root element of the fragment + */ +export async function loadFragment(path) { + if (path && path.startsWith('/')) { + const resp = await fetch(`${path}.plain.html`); + if (resp.ok) { + const main = document.createElement('main'); + main.innerHTML = await resp.text(); + + // reset base path for media to fragment base + const resetAttributeBase = (tag, attr) => { + main.querySelectorAll(`${tag}[${attr}^="./media_"]`).forEach((elem) => { + elem[attr] = new URL(elem.getAttribute(attr), new URL(path, window.location)).href; + }); + }; + resetAttributeBase('img', 'src'); + resetAttributeBase('source', 'srcset'); + + decorateMain(main); + await loadBlocks(main); + return main; + } + } + return null; +} + +export default async function decorate(block) { + const link = block.querySelector('a'); + const path = link ? link.getAttribute('href') : block.textContent.trim(); + const fragment = await loadFragment(path); + if (fragment) { + const fragmentSection = fragment.querySelector(':scope .section'); + if (fragmentSection) { + block.closest('.section').classList.add(...fragmentSection.classList); + block.closest('.fragment').replaceWith(...fragment.childNodes); + } + } +} From 0dd9b6d68ff0ec9db8a2775976e3d348c47d8072 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Fri, 19 Apr 2024 14:56:07 -0600 Subject: [PATCH 21/62] adjust disclaimer css --- blocks/fragment/fragment.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/blocks/fragment/fragment.css b/blocks/fragment/fragment.css index b38cf896..efdf8a01 100644 --- a/blocks/fragment/fragment.css +++ b/blocks/fragment/fragment.css @@ -11,3 +11,10 @@ .fragment-wrapper > .section:last-of-type { padding-bottom: 0; } + +.fragment-wrapper .disclaimer div p { + font-family: var(--font-family-primary); + font-size: var(--body-font-size-xxs); + line-height: var(--line-height-s); + color: var(--dark-grey); +} From eb5c07da46b366398e52045a43ad04f1f468f39f Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Mon, 13 May 2024 17:25:54 -0600 Subject: [PATCH 22/62] use placeholder and limit script load --- blocks/contact-form/forms/callback.js | 2 +- scripts/delayed.js | 6 ++++-- scripts/util.js | 1 - 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/blocks/contact-form/forms/callback.js b/blocks/contact-form/forms/callback.js index 3e7b9e53..5712dce9 100644 --- a/blocks/contact-form/forms/callback.js +++ b/blocks/contact-form/forms/callback.js @@ -8,7 +8,7 @@ function verifyCallback(resp) { function onloadCallback() { grecaptcha.render('captcha-20285', { - sitekey: '6LebYaYUAAAAAC9SqASljwaF57MpKSvEkwDOzk6l', + sitekey: window.placeholders.default.recaptchaSitekey, callback: verifyCallback, }); } diff --git a/scripts/delayed.js b/scripts/delayed.js index 706fd9d6..ba3feb1c 100644 --- a/scripts/delayed.js +++ b/scripts/delayed.js @@ -35,9 +35,11 @@ async function loadIDServlet() { } } -if (!window.location.host.includes('localhost')) await loadAdobeLaunch(); +if (!window.location.host.includes('localhost')) loadAdobeLaunch(); if (!getCookieValue('consumerID')) { loadIDServlet(); } -loadScript('https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit', { async: true, defer: true }); +if (window.location.pathname.startsWith('/contact-us') || window.location.pathname.startsWith('/search')) { + loadScript('https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit', { async: true, defer: true }); +} diff --git a/scripts/util.js b/scripts/util.js index c79e7774..93130afe 100644 --- a/scripts/util.js +++ b/scripts/util.js @@ -134,7 +134,6 @@ export function getEnvType(hostname = window.location.hostname) { return fqdnToEnvType[hostname] || 'dev'; } - /** * Retrieves the value of a cookie by its name. * From f3e4c0aa341a67cec3c9b61b8fa8bedd5545e58b Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Wed, 15 May 2024 11:09:37 -0600 Subject: [PATCH 23/62] recatpcha update --- blocks/contact-form/forms/contact-property.html | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/blocks/contact-form/forms/contact-property.html b/blocks/contact-form/forms/contact-property.html index cba6dcb6..c05aa0cb 100644 --- a/blocks/contact-form/forms/contact-property.html +++ b/blocks/contact-form/forms/contact-property.html @@ -3,7 +3,7 @@

Contact Us

Direct:
- +
@@ -56,16 +56,6 @@

Contact Us

-
- - -
-
From 3548c33d71972d2956f156ac1ac874020fbf3dc8 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Wed, 15 May 2024 14:17:47 -0600 Subject: [PATCH 24/62] clean up form html, correct url to luxury listing --- blocks/contact-form/contact-form.js | 2 +- blocks/contact-form/forms/contact-property.html | 2 +- blocks/contact-form/forms/join-our-team.html | 2 +- blocks/hero-slides/hero-slides.js | 2 +- scripts/delayed.js | 5 ++++- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index dc3baf52..02c82bfa 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -7,7 +7,7 @@ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const phoneRegex = /^[+]?[ (]?\d{3}[)]?[-.\s]?\d{3}[-.\s]?\d{4}$/; // Load reCaptcha script used on all forms. -loadScript('./blocks/contact-form/forms/callback.js'); +loadScript('/blocks/contact-form/forms/callback.js'); /** * Adds form and cookie values to payload. diff --git a/blocks/contact-form/forms/contact-property.html b/blocks/contact-form/forms/contact-property.html index c05aa0cb..45111aa2 100644 --- a/blocks/contact-form/forms/contact-property.html +++ b/blocks/contact-form/forms/contact-property.html @@ -55,7 +55,7 @@

Contact Us

-
+
diff --git a/blocks/contact-form/forms/join-our-team.html b/blocks/contact-form/forms/join-our-team.html index fa3de2a4..a490033f 100644 --- a/blocks/contact-form/forms/join-our-team.html +++ b/blocks/contact-form/forms/join-our-team.html @@ -776,7 +776,7 @@ placeholder="Leave a comment or question and we'll get back to you shortly." autocomplete="off">
-
+
diff --git a/blocks/hero-slides/hero-slides.js b/blocks/hero-slides/hero-slides.js index ff46460c..317923f4 100644 --- a/blocks/hero-slides/hero-slides.js +++ b/blocks/hero-slides/hero-slides.js @@ -2,7 +2,7 @@ import { createOptimizedPicture, readBlockConfig } from '../../scripts/aem.js'; // eslint-disable-next-line no-unused-vars async function fetchListings(config) { - const resp = await fetch(`${window.hlx.codeBasePath}/drafts/rrusher/listings.json`); + const resp = await fetch(`${window.hlx.codeBasePath}/luxury-listings.json`); // eslint-disable-next-line no-return-await return (await resp.json()).data; } diff --git a/scripts/delayed.js b/scripts/delayed.js index ba3feb1c..c12024d8 100644 --- a/scripts/delayed.js +++ b/scripts/delayed.js @@ -40,6 +40,9 @@ if (!getCookieValue('consumerID')) { loadIDServlet(); } -if (window.location.pathname.startsWith('/contact-us') || window.location.pathname.startsWith('/search')) { +// Load reCAPTCHA script on specific pages that have forms (including sidebar forms) +const paths = ['/contact-us', '/search', '/careers', '/luxury-collection']; + +if (paths.some((path) => window.location.pathname.startsWith(path))) { loadScript('https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit', { async: true, defer: true }); } From 4061119dd2a2141eaf0bc260741e6126a81bfa28 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Fri, 17 May 2024 16:27:36 -0600 Subject: [PATCH 25/62] reduced script loading and added rerenderer for recaptcha --- blocks/contact-form/contact-form.css | 32 ++++++++++++- blocks/contact-form/contact-form.js | 13 ++++-- blocks/contact-form/forms/callback.js | 9 ++++ .../contact-form/forms/contact-property.html | 8 ++-- blocks/property-listing/cards/cards.js | 46 ++++++++++--------- scripts/delayed.js | 7 --- scripts/scripts.js | 21 ++++++++- scripts/util.js | 5 +- 8 files changed, 99 insertions(+), 42 deletions(-) diff --git a/blocks/contact-form/contact-form.css b/blocks/contact-form/contact-form.css index 8450563c..c9b0ba23 100644 --- a/blocks/contact-form/contact-form.css +++ b/blocks/contact-form/contact-form.css @@ -1,7 +1,35 @@ -.contact-form.block form, +.contact-form.block, +.contact-form.block form { + font-family: var(--font-family-primary); +} + +.contact-form.block .form-title { + font-size: var(--heading-font-size-l); + line-height: var(--line-height-xs); + font-weight: 700; + margin-top: 30px; + margin-left: 40px; + margin-bottom: 3em; +} + +.contact-form.block .company-name { + font-size: var(--heading-font-size-m); + line-height: var(--line-height-xs); + font-weight: 800; + margin-bottom: .5em; +} + .contact-form.block .company-email, .contact-form.block .company-phone { - font-family: var(--font-family-proxima); + padding-bottom: .5em; +} + +.contact-form.block a { + color: var(--body-color); +} + +.contact-form.block form#property-contact { + margin-top: 3rem; } .contact-form.block form.contact-form .message { diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 02c82bfa..d38dfff2 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -1,5 +1,5 @@ import { loadScript } from '../../scripts/aem.js'; -import { hideSideModal, i18nLookup, getCookieValue } from '../../scripts/util.js'; +import { removeSideModal, i18nLookup, getCookieValue } from '../../scripts/util.js'; const LOGIN_ERROR = 'There was a problem processing your request.'; const i18n = await i18nLookup(); @@ -262,7 +262,7 @@ const addForm = async (block) => { btn.setAttribute('href', '#'); btn.addEventListener('click', (event) => { event.preventDefault(); - hideSideModal(); + removeSideModal(); }); sideModal?.replaceChildren(thankYou); } else { @@ -323,7 +323,7 @@ const addForm = async (block) => { cancelBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); - hideSideModal(); + removeSideModal(); }); } @@ -367,6 +367,13 @@ const addForm = async (block) => { } }); }); + + if (window.grecaptcha) { + recaptchaToken = null; + renderRecaptcha(); + } else { + loadScript('https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit', { async: true, defer: true }); + } }; export default async function decorate(block) { diff --git a/blocks/contact-form/forms/callback.js b/blocks/contact-form/forms/callback.js index 5712dce9..87e1b35e 100644 --- a/blocks/contact-form/forms/callback.js +++ b/blocks/contact-form/forms/callback.js @@ -12,3 +12,12 @@ function onloadCallback() { callback: verifyCallback, }); } + +function renderRecaptcha() { + if (document.getElementById('captcha-20285')) { + grecaptcha.render('captcha-20285', { + sitekey: window.placeholders.default.recaptchaSitekey, + callback: verifyCallback, + }); + } +} diff --git a/blocks/contact-form/forms/contact-property.html b/blocks/contact-form/forms/contact-property.html index 45111aa2..63039424 100644 --- a/blocks/contact-form/forms/contact-property.html +++ b/blocks/contact-form/forms/contact-property.html @@ -1,8 +1,8 @@
-

Contact Us

-
-
-
Direct:
+
Contact Us
+
Berkshire Hathaway HomeServices
Commonwealth Real Estate
+ +
diff --git a/blocks/property-listing/cards/cards.js b/blocks/property-listing/cards/cards.js index d690fc3e..bd1d8109 100644 --- a/blocks/property-listing/cards/cards.js +++ b/blocks/property-listing/cards/cards.js @@ -1,5 +1,6 @@ import { propertySearch } from '../../../scripts/apis/creg/creg.js'; import { decorateIcons } from '../../../scripts/aem.js'; +import { decorateFormLinks } from '../../../scripts/scripts.js'; function createImage(listing) { if (listing.SmallMedia?.length > 0) { @@ -50,9 +51,9 @@ export function createCard(listing) { item.innerHTML = ` -
-
- ${createImage(listing)} +
+
+ ${createImage(listing)}
@@ -63,7 +64,7 @@ export function createCard(listing) {
-
+
Featured Listing ${applicationType} @@ -71,38 +72,38 @@ export function createCard(listing) {
${listing.ListPriceUS} -
-
+
+
-
-
+
+
Closed: ${listing.ClosedDate}
-
+
${listing.StreetName} -
- ${listing.City}, ${listing.StateOrProvince} ${listing.PostalCode} -
-
${specs.join(' / ')}
-
-
- +
+
-
+
-
+
Listing courtesy of: ${listing.CourtesyOf}
Listing provided by: ${listing.listAor}
@@ -132,6 +133,7 @@ export async function render(searchParams, parent) { list.append(createCard(listing)); }); decorateIcons(parent); + decorateFormLinks(parent); } }); } diff --git a/scripts/delayed.js b/scripts/delayed.js index c12024d8..83afb01c 100644 --- a/scripts/delayed.js +++ b/scripts/delayed.js @@ -39,10 +39,3 @@ if (!window.location.host.includes('localhost')) loadAdobeLaunch(); if (!getCookieValue('consumerID')) { loadIDServlet(); } - -// Load reCAPTCHA script on specific pages that have forms (including sidebar forms) -const paths = ['/contact-us', '/search', '/careers', '/luxury-collection']; - -if (paths.some((path) => window.location.pathname.startsWith(path))) { - loadScript('https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit', { async: true, defer: true }); -} diff --git a/scripts/scripts.js b/scripts/scripts.js index b0f15a05..9397d263 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -152,12 +152,29 @@ function decorateVideoLinks(main) { }); } -function decorateFormLinks(main) { +/** + * Finds the parent anchor tag of the given event target. + * @param {Event} event - The event object. + * @returns {HTMLAnchorElement|null} - The parent anchor tag, or null if not found. + */ +function findParentAnchorTag(event) { + let element = event.target; + while (element && element.nodeName !== 'A') { + element = element.parentNode; + } + return element; +} + +/** + * Decorates form links by attaching a click event listener to open a side modal. + * @param {HTMLElement} main - The main element containing the form links. + */ +export function decorateFormLinks(main) { async function openSideModal(event) { event.preventDefault(); const module = await import(`${window.hlx.codeBasePath}/blocks/side-modal/side-modal.js`); if (module.showSideModal) { - await module.showSideModal(event.target); + await module.showSideModal(findParentAnchorTag(event)); } } main.querySelectorAll('a[href*="form"]').forEach((a) => { diff --git a/scripts/util.js b/scripts/util.js index 93130afe..dc93dc14 100644 --- a/scripts/util.js +++ b/scripts/util.js @@ -52,9 +52,10 @@ export function showModal(content) { let sideModal; let focusElement; -export function hideSideModal() { +export function removeSideModal() { if (!sideModal) return; - sideModal.ariaExpanded = false; + sideModal.parentNode.remove(); + sideModal = null; document.body.classList.remove('disable-scroll'); if (focusElement) focusElement.focus(); } From 9903e6a11483291d6cd98ed336128a5c3e07bd14 Mon Sep 17 00:00:00 2001 From: Bryan Stopp Date: Mon, 20 May 2024 12:47:18 -0400 Subject: [PATCH 26/62] Fix bad merge. --- blocks/shared/property/cards.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/shared/property/cards.js b/blocks/shared/property/cards.js index e6dd4d95..f50bee1f 100644 --- a/blocks/shared/property/cards.js +++ b/blocks/shared/property/cards.js @@ -71,7 +71,7 @@ export function createCard(listing) { ${listing.mlsStatus}
- ${listing.ListPriceUS} +

${listing.ListPriceUS}

${listing.ListPriceUS}

From 686c115ae94f843d1d869b2eb24b1aac822aecf2 Mon Sep 17 00:00:00 2001 From: Bryan Stopp Date: Mon, 20 May 2024 12:48:24 -0400 Subject: [PATCH 27/62] Fix bad merge. --- blocks/shared/property/cards.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/blocks/shared/property/cards.js b/blocks/shared/property/cards.js index f50bee1f..49096382 100644 --- a/blocks/shared/property/cards.js +++ b/blocks/shared/property/cards.js @@ -74,9 +74,6 @@ export function createCard(listing) {

${listing.ListPriceUS}

-

${listing.ListPriceUS}

-
-
From afa294e3a15beae23ff24a96cab26020522a7ffc Mon Sep 17 00:00:00 2001 From: Bryan Stopp Date: Mon, 20 May 2024 13:01:34 -0400 Subject: [PATCH 28/62] Fix lint. --- blocks/shared/property/cards.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/blocks/shared/property/cards.js b/blocks/shared/property/cards.js index 49096382..52afd8fc 100644 --- a/blocks/shared/property/cards.js +++ b/blocks/shared/property/cards.js @@ -1,5 +1,3 @@ -import { propertySearch } from '../../../scripts/apis/creg/creg.js'; -import { decorateIcons } from '../../../scripts/aem.js'; import { decorateFormLinks } from '../../../scripts/scripts.js'; function createImage(listing) { @@ -136,4 +134,5 @@ export function render(parent, properties = []) { cards.push(createCard(listing)); }); parent.replaceChildren(...cards); + decorateFormLinks(parent); } From 09a6237fbdc9b68509e1efb55fa589512d71074f Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Thu, 23 May 2024 12:24:11 -0600 Subject: [PATCH 29/62] update styles --- blocks/contact-form/contact-form.css | 7 +------ blocks/hero/hero.css | 11 +++++++++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/blocks/contact-form/contact-form.css b/blocks/contact-form/contact-form.css index c9b0ba23..11d8e01c 100644 --- a/blocks/contact-form/contact-form.css +++ b/blocks/contact-form/contact-form.css @@ -114,7 +114,7 @@ line-height: 1; display: flex; justify-content: flex-start; - margin-bottom: 15px; + margin-bottom: 10px; cursor: pointer; } @@ -168,11 +168,6 @@ border: 2px solid var(--error); } -.contact-form.block .contact-form .g-recaptcha { - padding: 3rem 0; - height: 174px; -} - .contact-form.block form.contact-form .cta { padding-bottom: 2rem; } diff --git a/blocks/hero/hero.css b/blocks/hero/hero.css index 062f7e72..5f13b0cb 100644 --- a/blocks/hero/hero.css +++ b/blocks/hero/hero.css @@ -103,6 +103,17 @@ main .section.full-width > .hero-wrapper { color: var(--white); } +.hero.block > div .content .button-container a { + border: 1px solid; + color: var(--white); +} + +.hero.block > div .content .button-container a:hover { + background-color: var(--primary-light); + border: 1px solid var(--primary-light); + color: var(--primary-color); +} + .hero.block > .row > .headline { color: var(--white); font-size: var(--body-font-size-m); From cdda86c10d74835a46c911ba199a23f8b600a0b6 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Thu, 23 May 2024 12:39:52 -0600 Subject: [PATCH 30/62] move selector for linting --- blocks/hero/hero.css | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/blocks/hero/hero.css b/blocks/hero/hero.css index a4ac050b..d52e7d8f 100644 --- a/blocks/hero/hero.css +++ b/blocks/hero/hero.css @@ -119,17 +119,6 @@ main .section.full-width > .hero-wrapper { z-index: 1; } -.hero.block > div .content .button-container a { - border: 1px solid; - color: var(--white); -} - -.hero.block > div .content .button-container a:hover { - background-color: var(--primary-light); - border: 1px solid var(--primary-light); - color: var(--primary-color); -} - .hero.block > .row > .headline { color: var(--white); font-size: var(--body-font-size-m); @@ -154,6 +143,17 @@ main .section.full-width > .hero-wrapper { color: var(--white); } +.hero.block > div .content .button-container a { + border: 1px solid; + color: var(--white); +} + +.hero.block > div .content .button-container a:hover { + background-color: var(--primary-light); + border: 1px solid var(--primary-light); + color: var(--primary-color); +} + @media screen and (min-width: 600px) { .hero.block { height: 620px; From 2162427757f1ea7e0980367b8c73cbd66a7d86cf Mon Sep 17 00:00:00 2001 From: Ritwik Srivastava Date: Mon, 27 May 2024 14:49:46 +0530 Subject: [PATCH 31/62] Floating agent bottom --- blocks/floatingagent/floatingagent.css | 81 ++++++++++++++++++++++++++ blocks/floatingagent/floatingagent.js | 66 +++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 blocks/floatingagent/floatingagent.css create mode 100644 blocks/floatingagent/floatingagent.js diff --git a/blocks/floatingagent/floatingagent.css b/blocks/floatingagent/floatingagent.css new file mode 100644 index 00000000..83c2e820 --- /dev/null +++ b/blocks/floatingagent/floatingagent.css @@ -0,0 +1,81 @@ +.floatingagent.block { + display: none; + align-items: center; + justify-content: space-between; + position: fixed; + bottom: 0; + left: 0; + width: 100%; + height: 100px; + background-color: white; + padding: 10px; + border-top: 1px solid #ccc; + z-index: 999; +} + +.floatingagent.block > .floating-agent-col { + margin-left: 100px; + display: flex; + margin-right: 10px; + flex: none; +} + +.floatingagent.block > .agentinfo > p { + margin-block-end: 0; + font-family: Manrope, serif; + font-size: 12px; + font-style: normal; + font-weight: 400; + letter-spacing: normal; + color: #2a2223; +} + +.floatingagent.block > .agentinfo > h2 { + font-size: 18px; + line-height: 130%; + letter-spacing: .18px; + vertical-align: top; + color: #2a2223; + width: max-content; +} + +.floatingagent.block > .floating-agent-col > picture { + height:40px; + width:30px; + margin-right: 40px; +} + +.floatingagent > .floating-agent-col > picture > img{ + display: inline; +} + +.floatingagent.block > .agentinfo { + flex-grow: 1; + margin-top: 10px; +} + +.floatingagent.block > .contactagent { + align-self: center; + background: #670038; + color: #f5f1f2; + border: 0; + flex: none; + padding: 10px; + margin-right: 5%; +} + +@media (max-width: 600px) { + .floatingagent.block { + justify-content: flex-start; + } + + .floatingagent > .floating-agent-col > picture > img, + .floatingagent > .agentinfo { + display: none; + } + + .floatingagent > .contactagent { + display: block; + margin-left: 10px; + } +} \ No newline at end of file diff --git a/blocks/floatingagent/floatingagent.js b/blocks/floatingagent/floatingagent.js new file mode 100644 index 00000000..17b4cd73 --- /dev/null +++ b/blocks/floatingagent/floatingagent.js @@ -0,0 +1,66 @@ +import { + getMetadata, +} from '../../scripts/aem.js'; +import { + button, + div, + h2, + img, + p, + strong, +} from '../../scripts/dom-helpers.js'; + +export default function decorate(block) { + const agentPicture = document.createElement('picture'); + agentPicture.appendChild(img({ + loading: 'lazy', + alt: 'Agent Image', + src: '', + width: '48', + height: '64', + style: 'width: 48px; height: 64px;', + })); + + const agentInfo = div({ class: 'agentinfo' }, + h2(strong('')), // Placeholder, will be updated with metadata + p(''), // Placeholder, will be updated with metadata + p(''), // Placeholder, will be updated with metadata + ); + + const contactButton = button({ class: 'contactagent' }, 'CONTACT AGENT'); + + // Append elements directly to the block + block.append( + div({ class: 'floating-agent-col' }, agentPicture), + agentInfo, + contactButton, + ); + + // Fetch metadata + const agentName = getMetadata('name'); + const agentDesc = getMetadata('desc'); + const pic = getMetadata('pic'); + const lic = getMetadata('lic'); + + // Populate the elements with metadata values + const agentImageElement = agentPicture.querySelector('img'); + const agentInfoElements = agentInfo.children; + + agentImageElement.src = pic; + agentInfoElements[0].innerHTML = `${agentName}`; + agentInfoElements[1].textContent = agentDesc; + agentInfoElements[2].textContent = lic; +} + +const displayedElement = document.querySelector('.floatingagent'); + +window.addEventListener('scroll', () => { + const heroElement = document.querySelector('.hero-wrapper'); + const heroRect = heroElement.getBoundingClientRect(); + + if (heroRect.bottom < 0) { + displayedElement.style.display = 'flex'; + } else { + displayedElement.style.display = 'none'; + } +}); From d50dfb4739c1f586af51f6fe856455acf9fbd664 Mon Sep 17 00:00:00 2001 From: Ritwik Srivastava Date: Mon, 27 May 2024 14:53:25 +0530 Subject: [PATCH 32/62] Floating agent bottom --- blocks/floatingagent/floatingagent.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blocks/floatingagent/floatingagent.css b/blocks/floatingagent/floatingagent.css index 83c2e820..c267848f 100644 --- a/blocks/floatingagent/floatingagent.css +++ b/blocks/floatingagent/floatingagent.css @@ -45,7 +45,7 @@ margin-right: 40px; } -.floatingagent > .floating-agent-col > picture > img{ +.floatingagent.block > .floating-agent-col > picture > img{ display: inline; } @@ -69,12 +69,12 @@ justify-content: flex-start; } - .floatingagent > .floating-agent-col > picture > img, - .floatingagent > .agentinfo { + .floatingagent.block > .floating-agent-col > picture > img, + .floatingagent.block > .agentinfo { display: none; } - .floatingagent > .contactagent { + .floatingagent.block > .contactagent { display: block; margin-left: 10px; } From 0288ab3ab5b7805ed6a1c702bb1f99b8d06e427f Mon Sep 17 00:00:00 2001 From: Ritwik Srivastava Date: Mon, 27 May 2024 15:11:18 +0530 Subject: [PATCH 33/62] Floating agent bottom --- blocks/floatingagent/floatingagent.css | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/blocks/floatingagent/floatingagent.css b/blocks/floatingagent/floatingagent.css index c267848f..b55eeb85 100644 --- a/blocks/floatingagent/floatingagent.css +++ b/blocks/floatingagent/floatingagent.css @@ -7,7 +7,7 @@ left: 0; width: 100%; height: 100px; - background-color: white; + background-color: var(--white); padding: 10px; border-top: 1px solid #ccc; z-index: 999; @@ -22,16 +22,16 @@ .floatingagent.block > .agentinfo > p { margin-block-end: 0; - font-family: Manrope, serif; - font-size: 12px; + font-family: var(--font-family-primary); + font-size: var(--body-font-size-xxs); font-style: normal; - font-weight: 400; + font-weight: var(--font-weight-normal); letter-spacing: normal; color: #2a2223; } .floatingagent.block > .agentinfo > h2 { - font-size: 18px; + font-size: var(--heading-font-size-m); line-height: 130%; letter-spacing: .18px; vertical-align: top; @@ -56,8 +56,8 @@ .floatingagent.block > .contactagent { align-self: center; - background: #670038; - color: #f5f1f2; + background: var(--primary-color); + color: var(--tertiary-color); border: 0; flex: none; padding: 10px; From a35e45450d4c42dbddfb32d915c184b8235fe2c8 Mon Sep 17 00:00:00 2001 From: Ritwik Srivastava Date: Wed, 29 May 2024 11:55:51 +0530 Subject: [PATCH 34/62] use intersectionObserver --- blocks/floatingagent/floatingagent.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/blocks/floatingagent/floatingagent.js b/blocks/floatingagent/floatingagent.js index 17b4cd73..66380e5a 100644 --- a/blocks/floatingagent/floatingagent.js +++ b/blocks/floatingagent/floatingagent.js @@ -54,13 +54,18 @@ export default function decorate(block) { const displayedElement = document.querySelector('.floatingagent'); -window.addEventListener('scroll', () => { - const heroElement = document.querySelector('.hero-wrapper'); - const heroRect = heroElement.getBoundingClientRect(); +const heroElement = document.querySelector('.hero-wrapper'); - if (heroRect.bottom < 0) { - displayedElement.style.display = 'flex'; - } else { - displayedElement.style.display = 'none'; - } +const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + displayedElement.style.display = 'none'; + } else { + displayedElement.style.display = 'flex'; + } + }); +}, { + threshold: [0], }); + +observer.observe(heroElement); From 8d7d84edc7aa80a3eb005c84aac99f169bd519bd Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Wed, 29 May 2024 13:56:16 -0600 Subject: [PATCH 35/62] Update fragment.js removed padding lint --- blocks/fragment/fragment.js | 1 - 1 file changed, 1 deletion(-) diff --git a/blocks/fragment/fragment.js b/blocks/fragment/fragment.js index 3cb40df8..da92d8bb 100644 --- a/blocks/fragment/fragment.js +++ b/blocks/fragment/fragment.js @@ -18,7 +18,6 @@ import { * @returns {HTMLElement} The root element of the fragment */ export async function loadFragment(path) { - if (path?.startsWith('/')) { const resp = await fetch(`${path}.plain.html`); if (resp.ok) { From 99cf2ee2af9563b266488107d30c4458a736307a Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Thu, 30 May 2024 16:12:04 -0600 Subject: [PATCH 36/62] add css --- blocks/cards/cards.css | 72 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/blocks/cards/cards.css b/blocks/cards/cards.css index 07888440..5bf399ed 100644 --- a/blocks/cards/cards.css +++ b/blocks/cards/cards.css @@ -16,6 +16,18 @@ width: 100%; } +.cards.block.mobile-slide .cards-list { + flex-wrap: nowrap; + overflow-x: auto; + flex-direction: row; + scroll-snap-type: x mandatory; + scrollbar-width: none; /* Firefox */ +} + +.cards-list::-webkit-scrollbar { + display: none; /* Chrome, Safari, and Opera */ +} + .cards.block .title { padding: 2em 0; } @@ -86,6 +98,16 @@ text-transform: uppercase; } +.cards.block.mobile-slide .cards-item .card-body { + padding: 0 30px; + height: 90px; +} + +.cards.block.mobile-slide .cards-item .card-body h4, +.cards.block.mobile-slide .cards-item .card-body p { + text-align: center; +} + .cards.block .cards-list .cards-item .card-body h3 { padding-top: 16px; font-size: var(--body-font-size-l); @@ -117,6 +139,25 @@ border-bottom: 1px solid var(--secondary-medium-grey); } +.cards.block.mobile-slide .cards-list .cards-item { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-width: 250px; + flex: 0 0 auto; + margin-right: 20px; + text-align: center; + border: 1px solid var(--secondary-accent); + height: 100%; + scroll-snap-align: start; +} + +.cards.block.mobile-slide.icons .cards-list .cards-item { + flex-direction: column; + width: 90%; +} + .cards.block.icons .cards-list .cards-item .card-icon { margin-top: .5em; margin-right: 1em; @@ -207,6 +248,28 @@ max-width: 750px; } + .cards.block.mobile-slide .cards-list { + flex-wrap: unset; + overflow-x: unset; + flex-direction: column; + } + + .cards.block.mobile-slide.icons .cards-list .cards-item { + flex-direction: row; + } + + .cards.block.mobile-slide .cards-list .cards-item { + flex-direction: column; + align-items: center; + justify-content: unset; + min-width: unset; + flex: unset; + margin: unset; + text-align: unset; + border: unset; + border-bottom: 1px solid var(--secondary-medium-grey); + } + .cards.block.shade-icon .cards-list { flex-direction: row; column-gap: 20px; @@ -214,6 +277,15 @@ margin-bottom: 50px; } + .cards.block.mobile-slide .cards-item .card-body { + padding: unset; + } + + .cards.block.mobile-slide .cards-item .card-body h4, + .cards.block.mobile-slide .cards-item .card-body p { + text-align: left; + } + .cards.block .cards-list .cards-item .card-body h3 { font-size: var(--heading-font-size-m); } From 371cbceb7379840d748432a166acbe6c3e507123 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Thu, 30 May 2024 16:23:02 -0600 Subject: [PATCH 37/62] linting css --- blocks/cards/cards.css | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/blocks/cards/cards.css b/blocks/cards/cards.css index 5bf399ed..85c99319 100644 --- a/blocks/cards/cards.css +++ b/blocks/cards/cards.css @@ -17,9 +17,8 @@ } .cards.block.mobile-slide .cards-list { - flex-wrap: nowrap; + flex-flow: row nowrap; overflow-x: auto; - flex-direction: row; scroll-snap-type: x mandatory; scrollbar-width: none; /* Firefox */ } @@ -153,6 +152,15 @@ scroll-snap-align: start; } +.cards.block.shade-icon .cards-list .cards-item { + background-color: var(--light-grey); + padding: 0; + border-top: 1px solid #000; + min-height: 274px; + align-items: center; + justify-content: center; +} + .cards.block.mobile-slide.icons .cards-list .cards-item { flex-direction: column; width: 90%; @@ -188,15 +196,6 @@ margin-bottom: 0; } -.cards.block.shade-icon .cards-list .cards-item { - background-color: var(--light-grey); - padding: 0; - border-top: 1px solid #000; - min-height: 274px; - align-items: center; - justify-content: center; -} - .cards.block.tertiary-background.border-top .cards-list .cards-item { background-color: var(--tertiary-color); border-top: 1px solid var(--secondary-light); @@ -249,13 +248,8 @@ } .cards.block.mobile-slide .cards-list { - flex-wrap: unset; + flex-flow: column unset; overflow-x: unset; - flex-direction: column; - } - - .cards.block.mobile-slide.icons .cards-list .cards-item { - flex-direction: row; } .cards.block.mobile-slide .cards-list .cards-item { @@ -270,6 +264,10 @@ border-bottom: 1px solid var(--secondary-medium-grey); } + .cards.block.mobile-slide.icons .cards-list .cards-item { + flex-direction: row; + } + .cards.block.shade-icon .cards-list { flex-direction: row; column-gap: 20px; From 43214060f9904fdcb7534ed13fdb43331f96b657 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Thu, 30 May 2024 17:18:44 -0600 Subject: [PATCH 38/62] Remove references to API and document proxy steps. (#234) * Remove references to API and document proxy steps. * Fix some verbage. * Remove URL from new API library. * Fix blog pages. --- .../agent-testimonials/agent-testimonials.css | 94 +++++++++++++++++++ .../agent-testimonials/agent-testimonials.js | 81 ++++++++++++++++ blocks/quote-carousel/quote-carousel.css | 9 +- blocks/quote-carousel/quote-carousel.js | 50 +++++----- 4 files changed, 209 insertions(+), 25 deletions(-) create mode 100644 blocks/agent-testimonials/agent-testimonials.css create mode 100644 blocks/agent-testimonials/agent-testimonials.js diff --git a/blocks/agent-testimonials/agent-testimonials.css b/blocks/agent-testimonials/agent-testimonials.css new file mode 100644 index 00000000..7da5fa4f --- /dev/null +++ b/blocks/agent-testimonials/agent-testimonials.css @@ -0,0 +1,94 @@ +.agent-testimonials.block { + position: relative; + width: 100%; + overflow: hidden; + border-radius: 10px; +} + +.agent-testimonials.block .testimonials { + display: flex; + transition: transform 0.5s ease; + width: 100%; +} + +.agent-testimonials.block .testimonials-inner { + display: flex; + width: 100%; +} + +.agent-testimonials.block .testimonials-item { + min-width: 100%; + box-sizing: border-box; + padding: 20px; + text-align: center; + display: flex; + flex-direction: column; + justify-content: space-between; + position: relative; +} + +.agent-testimonials.block .rating-stars { + color: var(--primary-color); + margin-bottom: 10px; + font-size: 25px; + margin-left: 50px; +} + +.agent-testimonials.block .review-text.full { + max-height: none; + font-size: 26px; +} + +.agent-testimonials.block .review-text { + font-size: 26px; + margin-bottom: 20px; + padding-left: 50px; + padding-right: 50px; +} + +.agent-testimonials.block .read-more { + font-size: 14px; + color: #607C8C; + cursor: pointer; + display: inline-block; + +} + +.agent-testimonials.block .reviewer-name { + font-weight: bold; + margin-bottom: 10px; + align-self: center; +} + +.agent-testimonials.block .testimonials-arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: var(--black); + font-size: 90px; + padding: 0; + cursor: pointer; +} + +.agent-testimonials.block .left-arrow { + left: 10px; +} + +.agent-testimonials.block .right-arrow { + right: 10px; +} + +.agent-testimonials.block .testimonials-counter { + position: absolute; + bottom: 10px; + right: 10px; + font-size: 16px; + color: #333; +} + +.agent-testimonials.block .remaining-text { + font: inherit; + font-size: 26px; +} \ No newline at end of file diff --git a/blocks/agent-testimonials/agent-testimonials.js b/blocks/agent-testimonials/agent-testimonials.js new file mode 100644 index 00000000..54419661 --- /dev/null +++ b/blocks/agent-testimonials/agent-testimonials.js @@ -0,0 +1,81 @@ +import { getMetadata } from '../../scripts/aem.js'; +import { button, div } from '../../scripts/dom-helpers.js'; + +export default function decorate(block) { + const leftArrow = button({ class: 'testimonials-arrow left-arrow' }, '<'); + const testimonialsInner = div({ class: 'testimonials-inner' }); + const testimonialsWrapper = div({ class: 'testimonials' }, testimonialsInner); + const rightArrow = button({ class: 'testimonials-arrow right-arrow' }, '>'); + const testimonialsCounter = div({ class: 'testimonials-counter' }); + block.append(leftArrow, testimonialsWrapper, rightArrow, testimonialsCounter); + + let currentIndex = 0; + let totalReviews = 0; + const updateCounter = () => { + testimonialsCounter.textContent = `${currentIndex + 1} of ${totalReviews}`; + }; + + const addReadMoreFunctionality = () => { + const reviewTexts = document.querySelectorAll('.review-text'); + reviewTexts.forEach((reviewText) => { + const words = reviewText.textContent.split(' '); + if (words.length > 75) { + const initialText = words.slice(0, 50).join(' '); + const remainingText = words.slice(50).join(' '); + const readMore = document.createElement('span'); + readMore.classList.add('read-more'); + readMore.textContent = '... Read more'; + + reviewText.innerHTML = `${initialText}${remainingText}`; + reviewText.appendChild(readMore); + reviewText.querySelector('.remaining-text').style.display = 'none'; + + readMore.addEventListener('click', () => { + const remainingTextSpan = reviewText.querySelector('.remaining-text'); + if (remainingTextSpan.style.display === 'none') { + remainingTextSpan.style.display = 'inline'; + readMore.textContent = ' Show less'; + } else { + remainingTextSpan.style.display = 'none'; + readMore.textContent = '... Read more'; + } + }); + } + }); + }; + + const externalID = getMetadata('externalid'); + fetch(`https://testimonialtree.com/Widgets/jsonFeed.aspx?widgetid=45133&externalID=${externalID}`) + .then((response) => response.json()) + .then((data) => { + const reviews = data.testimonialtreewidget.testimonials.testimonial.slice(0, 4); + totalReviews = reviews.length; + reviews.forEach((review) => { + const reviewElement = div({ class: 'testimonials-item' }, + div({ class: 'rating-stars' }, '★'.repeat(review.rating)), + div({ class: 'review-text-container' }, + div({ class: 'review-text' }, decodeURIComponent(review.testimonial.replace(/\+/g, ' '))), + ), + div({ class: 'reviewer-name' }, review.signature.replace(/\+/g, ' ') || 'Anonymous'), + ); + testimonialsInner.appendChild(reviewElement); + }); + addReadMoreFunctionality(); + updateCounter(); + }); + + const updatetestimonials = () => { + testimonialsInner.style.transform = `translateX(-${currentIndex * 100}%)`; + updateCounter(); + }; + + leftArrow.addEventListener('click', () => { + currentIndex = (currentIndex > 0) ? currentIndex - 1 : totalReviews - 1; + updatetestimonials(); + }); + + rightArrow.addEventListener('click', () => { + currentIndex = (currentIndex < totalReviews - 1) ? currentIndex + 1 : 0; + updatetestimonials(); + }); +} diff --git a/blocks/quote-carousel/quote-carousel.css b/blocks/quote-carousel/quote-carousel.css index 62606fac..7fec8984 100644 --- a/blocks/quote-carousel/quote-carousel.css +++ b/blocks/quote-carousel/quote-carousel.css @@ -13,6 +13,11 @@ position: relative; } +.quote-carousel.block p, +.quote-carousel.block .pagination span { + color: var(--white); +} + .quote-carousel.block .title { text-align: center; text-transform: capitalize; @@ -98,13 +103,13 @@ transform: translateY(2px); } -.quote-carousel.block .controls-container svg { +.quote-carousel.block .controls-container img { color: var(--white); height: var(--body-font-size-m); width: var(--body-font-size-m); } -.quote-carousel.block .controls-container [name="prev"] svg { +.quote-carousel.block .controls-container [name="prev"] img { transform: rotate(-180deg); } diff --git a/blocks/quote-carousel/quote-carousel.js b/blocks/quote-carousel/quote-carousel.js index bc1dc97a..49f55a64 100644 --- a/blocks/quote-carousel/quote-carousel.js +++ b/blocks/quote-carousel/quote-carousel.js @@ -1,3 +1,8 @@ +import { + button, div, p, span, +} from '../../scripts/dom-helpers.js'; +import { decorateIcons } from '../../scripts/aem.js'; + /** * Returns block content from the spreadsheet * @@ -40,6 +45,7 @@ export default async function decorate(block) { const blockId = crypto && crypto.randomUUID ? crypto.randomUUID() : 'UUID-CRYPTO-NEEDS-HTTPS'; const dataUrl = block.querySelector('div > div > div:nth-child(2) > a').href; const title = getTitle(block); + const content = await getContent(dataUrl); // generate carousel content from loaded data block.setAttribute('id', blockId); block.innerHTML = ''; @@ -48,46 +54,44 @@ export default async function decorate(block) { titleElement.innerText = title.trim(); titleElement.classList.add('title'); - const controlsContainer = document.createElement('div'); - controlsContainer.classList.add('controls-container'); + const controlsContainer = div({ class: 'controls-container' }, + div({ class: 'pagination' }, + span({ class: 'index' }, '1'), + span({ class: 'of' }, 'of'), + span({ class: 'total' }, content.total), + ), + button({ + name: 'prev', class: 'control-button', 'aria-label': 'Previous', disabled: true, + }, + span({ class: 'icon icon-chevron-right-white' }), + ), + button({ name: 'next', class: 'control-button', 'aria-label': 'Next' }, + span({ class: 'icon icon-chevron-right-white' }), + ), + ); + decorateIcons(controlsContainer); const slidesContainer = document.createElement('div'); slidesContainer.classList.add('carousel-content'); block.replaceChildren(titleElement, slidesContainer, controlsContainer); - const content = await getContent(dataUrl); - if (content.data.length > 0) { [...content.data].forEach((row) => { - const rowContent = document.createElement('div'); if (!row.quote.startsWith('"')) { row.quote = `"${row.quote}`; } if (!row.quote.endsWith('"')) { row.quote = `${row.quote}"`; } - rowContent.classList.add('item'); - rowContent.innerHTML = ` -

${row.quote}

-

${row.author}

-

${row.position}

- `; - rowContent.classList.add('item'); + const rowContent = div({ class: 'item' }, + p({ class: 'quote' }, row.quote), + p({ class: 'author' }, row.author), + p({ class: 'position' }, row.position), + ); slidesContainer.appendChild(rowContent); }); slidesContainer.children[0].setAttribute('active', true); - - // generate container for carousel controls - controlsContainer.innerHTML = ` - - - - `; window.setTimeout(observeCarousel, 3000); } } From bdfabb71b7057473edb5444f0b6d071622e06bc7 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Fri, 31 May 2024 10:30:15 -0600 Subject: [PATCH 39/62] remove fragment css --- blocks/fragment/fragment.css | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/blocks/fragment/fragment.css b/blocks/fragment/fragment.css index efdf8a01..b3c58707 100644 --- a/blocks/fragment/fragment.css +++ b/blocks/fragment/fragment.css @@ -1,20 +1 @@ -/* suppress nested section padding */ -.fragment-wrapper > .section { - padding-left: 0; - padding-right: 0; -} - -.fragment-wrapper > .section:first-of-type { - padding-top: 0; -} - -.fragment-wrapper > .section:last-of-type { - padding-bottom: 0; -} - -.fragment-wrapper .disclaimer div p { - font-family: var(--font-family-primary); - font-size: var(--body-font-size-xxs); - line-height: var(--line-height-s); - color: var(--dark-grey); -} +/* stylelint-disable-next-line no-empty-source */ From 0aa9ffa2480accaac93927695eb0b1787222ea47 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Fri, 31 May 2024 13:30:57 -0600 Subject: [PATCH 40/62] move disclaimer css to column --- blocks/columns/columns.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/blocks/columns/columns.css b/blocks/columns/columns.css index 4a63594e..b8aa5596 100644 --- a/blocks/columns/columns.css +++ b/blocks/columns/columns.css @@ -48,6 +48,12 @@ order: 1; } +.columns.block.disclaimer div { + font-size: var(--body-font-size-xxs); + line-height: var(--line-height-s); + color: var(--dark-grey); +} + .columns.columns-2-cols > div > div:first-of-type { margin-bottom: 24px; } @@ -66,7 +72,6 @@ .columns.block h3 { margin-bottom: 24px; - } @media (min-width: 600px) { From 39647a2484265d56c1cf6e70a3e4c66df63cae36 Mon Sep 17 00:00:00 2001 From: Ritwik Srivastava Date: Mon, 3 Jun 2024 13:42:13 +0530 Subject: [PATCH 41/62] Floating agent --- blocks/floatingagent/floatingagent.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/blocks/floatingagent/floatingagent.js b/blocks/floatingagent/floatingagent.js index 66380e5a..c593a81a 100644 --- a/blocks/floatingagent/floatingagent.js +++ b/blocks/floatingagent/floatingagent.js @@ -11,6 +11,11 @@ import { } from '../../scripts/dom-helpers.js'; export default function decorate(block) { + const agentName = getMetadata('name'); + const agentDesc = getMetadata('desc'); + const pic = getMetadata('pic'); + const lic = getMetadata('lic'); + const agentPicture = document.createElement('picture'); agentPicture.appendChild(img({ loading: 'lazy', @@ -22,27 +27,19 @@ export default function decorate(block) { })); const agentInfo = div({ class: 'agentinfo' }, - h2(strong('')), // Placeholder, will be updated with metadata - p(''), // Placeholder, will be updated with metadata - p(''), // Placeholder, will be updated with metadata + h2(strong('')), + p(''), + p(''), ); const contactButton = button({ class: 'contactagent' }, 'CONTACT AGENT'); - // Append elements directly to the block block.append( div({ class: 'floating-agent-col' }, agentPicture), agentInfo, contactButton, ); - // Fetch metadata - const agentName = getMetadata('name'); - const agentDesc = getMetadata('desc'); - const pic = getMetadata('pic'); - const lic = getMetadata('lic'); - - // Populate the elements with metadata values const agentImageElement = agentPicture.querySelector('img'); const agentInfoElements = agentInfo.children; From 2eee430d9cd9da7accb4af23b85701025db12863 Mon Sep 17 00:00:00 2001 From: Bryan Stopp Date: Mon, 3 Jun 2024 09:43:40 -0400 Subject: [PATCH 42/62] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 90c552b7..453a7b48 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ In this setup, the proxy is configured to route non-API traffic on the Staging d This setup uses a locally resolved domain, which will Proxyman will use to route the traffic. All non-API traffic will route to localhost, all API traffic will go to the Stage domain. -1. Add an entry to `/etc/maps`: +1. Add an entry to `/etc/hosts`: Proxyman won't proxy localhost, so a custom domain is required. Add the following (if you already have a host entry for 127.0.0.1, simply add the new domain) > 127.0.0.1 proxyman.debug From 695d51a325873739392040fabeb9373b6cb6d630 Mon Sep 17 00:00:00 2001 From: Piyush Jindal Date: Mon, 3 Jun 2024 22:53:38 +0530 Subject: [PATCH 43/62] Transactions block added (#236) * Transactions block added * Review comment changes * Review Comments added * Relative path added --------- Co-authored-by: piyushjindal --- .../agent-transactions/agent-transactions.css | 157 ++++++++++++++++++ .../agent-transactions/agent-transactions.js | 105 ++++++++++++ icons/arrow-down.svg | 3 + icons/arrow-up.svg | 5 + scripts/dom-helpers.js | 6 + 5 files changed, 276 insertions(+) create mode 100644 blocks/agent-transactions/agent-transactions.css create mode 100644 blocks/agent-transactions/agent-transactions.js create mode 100644 icons/arrow-down.svg create mode 100644 icons/arrow-up.svg diff --git a/blocks/agent-transactions/agent-transactions.css b/blocks/agent-transactions/agent-transactions.css new file mode 100644 index 00000000..061fc67c --- /dev/null +++ b/blocks/agent-transactions/agent-transactions.css @@ -0,0 +1,157 @@ +.agent-transactions.block table { + width: 100%; + font-size: var(--body-font-size-s); + line-height: var(--line-height-m); + margin-bottom: 0.25rem; +} + +.agent-transactions.block h1 { + font-size: var(--heading-font-size-l); + line-height: var(--line-height-s); + color: var(--primary-color); + margin: 0 1.875rem 1.5rem 0; + font-weight: 600; +} + +.agent-transactions.block .hide { + display: none; +} + +.agent-transactions.block .show { + display: table-row; +} + +.agent-transactions.block thead { + background: var(--platinum); + font-size: var(--body-font-size-xs); + line-height: var(--line-height-m); + font-weight: var(--font-weight-bold); + text-transform: capitalize; +} + +.agent-transactions.block tbody td { + padding-bottom: 0.5rem; + padding-top: 0.25rem; + font-size: var(--body-font-size-xs); +} + +.agent-transactions.block a { + line-height: var(--line-height-m); + letter-spacing: var(--letter-spacing-m); + text-transform: uppercase; + padding-left: 1rem; + cursor: pointer; +} + +.agent-transactions.block a.show-more::after { + content: "View More"; + display: inline-block; + background-image: url("../../icons/arrow-down.svg"); + background-repeat: no-repeat; + background-position: right; + padding-right: 1rem; + font-size: var(--body-font-size-xs); + font-weight: var(--font-weight-bold); +} + +.agent-transactions.block a.show-less::after { + content: "View Less"; + display: inline-block; + background-image: url("../../icons/arrow-up.svg"); + background-repeat: no-repeat; + background-position: right; + padding-right: 1rem; + font-size: var(--body-font-size-xs); + font-weight: var(--font-weight-bold); +} + +.agent-transactions.block thead th { + padding-top: 0.125rem; + padding-bottom: 0.125rem; +} + +.agent-transactions.block thead th:first-of-type { + padding-left: 1rem; +} + +.agent-transactions.block tbody td:first-of-type { + padding-left: 1rem; +} + +.agent-transactions.block .default { + display: table-cell; +} + +.agent-transactions.block .medium, +.agent-transactions.block .large, +.agent-transactions.block .xl { + display: none; +} + +.agent-transactions.block .sold-price { + width: 40%; +} + +@media (min-width: 600px) { + .agent-transactions.block .medium { + display: table-cell; + } + + .agent-transactions.block .address { + width: 48.7%; + } + + .agent-transactions.block .city { + width: 24.5%; + } + + .agent-transactions.block .state { + width: 7.4%; + } + + .agent-transactions.block .sold-price { + width: 40%; + } +} + +@media (min-width: 992px) { + .agent-transactions.block h1 { + font-size: 1.875rem; + } + + .agent-transactions.block thead { + font-size: var(--body-font-size-s); + } + + .agent-transactions.block tbody td { + font-size: var(--body-font-size-s); + padding-top: 0.5rem; + } + + .agent-transactions.block .large { + display: table-cell; + } + + .agent-transactions.block .address { + width: auto; + } + + .agent-transactions.block .city { + width: 15%; + } + + .agent-transactions.block .sold-price { + width: 13%; + } +} + +@media (min-width: 1200px) { + .agent-transactions.block .xl { + display: table-cell; + } + + .agent-transactions.block .beds, + .agent-transactions.block .baths { + width: 5%; + } +} diff --git a/blocks/agent-transactions/agent-transactions.js b/blocks/agent-transactions/agent-transactions.js new file mode 100644 index 00000000..e2e354a8 --- /dev/null +++ b/blocks/agent-transactions/agent-transactions.js @@ -0,0 +1,105 @@ +import { + table, tbody, th, thead, tr, td, h1, a, +} from '../../scripts/dom-helpers.js'; +import { getMetadata } from '../../scripts/aem.js'; + +const getClosedTransactions = async () => { + const agentId = getMetadata('agent-id'); + const formattedData = []; + + try { + const response = await fetch(`/bin/bhhs/agentPropertyListingsServlet.${agentId}.json`); + const data = await response.json(); + + if (data && data?.closedTransactions?.properties?.length) { + data.closedTransactions.properties.forEach((property) => { + formattedData.push({ + address: property.StreetName, + city: property.City, + state: property.StateOrProvince, + 'sold-price': property.closePrice, + beds: property.BedroomsTotal, + baths: property.BathroomsTotal, + 'approx-sq-ft': property.LivingArea, + type: property.PropertyType, + 'closed-date': property.ClosedDate, + }); + }); + } + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error fetching closed transactions', error); + } + + return formattedData; +}; + +export default async function decorate(block) { + const transactionsData = await getClosedTransactions(); + if (transactionsData.length === 0) { + return; + } + + const thList = ['address', 'city', 'state', 'sold price', 'beds', 'baths', 'approx sq. ft.', 'type', 'closed date']; + const thDefault = { class: 'default', list: [0, 3] }; + const thMedium = { class: 'medium', list: [1, 2] }; + const thLarge = { class: 'large', list: [4, 5, 8] }; + const thXL = { class: 'xl', list: [6, 7] }; + + const theadTr = tr(); + const getClass = (index) => { + if (thDefault.list.includes(index)) { + return `${thDefault.class}`; + } + if (thMedium.list.includes(index)) { + return `${thMedium.class}`; + } + if (thLarge.list.includes(index)) { + return `${thLarge.class}`; + } + return `${thXL.class}`; + }; + + thList.forEach((x, index) => { + theadTr.appendChild(th({ class: `${x.split(' ').join('-').replace(/\./g, '')} ${getClass(index)}` }, x)); + }); + + const trBody = tbody(); + const intialTransactionsCount = 6; // show 6 transactions initially + + transactionsData.forEach((data, topIndex) => { + const trElement = tr({ class: `${topIndex < intialTransactionsCount ? 'show' : 'hide'}` }); + + thList.forEach((x, index) => { + const key = x.split(' ').join('-').replace(/\./g, ''); + trElement.appendChild(td({ class: `${x.split(' ').join('-').replace(/\./g, '')} ${getClass(index)}` }, (data[key]) || '')); + }); + + trBody.appendChild(trElement); + }); + + const tableElement = table({}, thead({}, theadTr), trBody); + const heading1 = h1({}, 'Closed Transactions'); + const anchor = a({ class: 'show-more' }); + anchor.addEventListener('click', () => { + if (anchor.classList.contains('show-more')) { + anchor.classList.remove('show-more'); + anchor.classList.add('show-less'); + const tBodyTr = block.querySelectorAll('tbody tr.hide'); + tBodyTr.forEach((trElement) => { + trElement.classList.remove('hide'); + }); + } else { + anchor.classList.remove('show-less'); + anchor.classList.add('show-more'); + const tBodyTr = block.querySelectorAll('tbody tr'); + tBodyTr.forEach((trElement, index) => { + if (index >= intialTransactionsCount) { + trElement.classList.add('hide'); + } + }); + } + }); + + block.replaceChildren(heading1, tableElement, anchor); +} diff --git a/icons/arrow-down.svg b/icons/arrow-down.svg new file mode 100644 index 00000000..4d8c6193 --- /dev/null +++ b/icons/arrow-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/arrow-up.svg b/icons/arrow-up.svg new file mode 100644 index 00000000..8edce9af --- /dev/null +++ b/icons/arrow-up.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/dom-helpers.js b/scripts/dom-helpers.js index 1fce908c..7059545f 100644 --- a/scripts/dom-helpers.js +++ b/scripts/dom-helpers.js @@ -97,3 +97,9 @@ export function article(...items) { return domEl('article', ...items); } export function strong(...items) { return domEl('strong', ...items); } export function select(...items) { return domEl('select', ...items); } export function option(...items) { return domEl('option', ...items); } +export function table(...items) { return domEl('table', ...items); } +export function tbody(...items) { return domEl('tbody', ...items); } +export function th(...items) { return domEl('th', ...items); } +export function thead(...items) { return domEl('thead', ...items); } +export function tr(...items) { return domEl('tr', ...items); } +export function td(...items) { return domEl('td', ...items); } From 8e64dff1cf393c4bd71429552dd2c8b97045b74c Mon Sep 17 00:00:00 2001 From: RitwikSrivastava <45959816+RitwikSrivastava@users.noreply.github.com> Date: Tue, 4 Jun 2024 19:45:44 +0530 Subject: [PATCH 44/62] Update blocks/floatingagent/floatingagent.js Co-authored-by: Bryan Stopp --- blocks/floatingagent/floatingagent.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/blocks/floatingagent/floatingagent.js b/blocks/floatingagent/floatingagent.js index c593a81a..5b6cb279 100644 --- a/blocks/floatingagent/floatingagent.js +++ b/blocks/floatingagent/floatingagent.js @@ -20,16 +20,16 @@ export default function decorate(block) { agentPicture.appendChild(img({ loading: 'lazy', alt: 'Agent Image', - src: '', + src: pic, width: '48', height: '64', style: 'width: 48px; height: 64px;', })); const agentInfo = div({ class: 'agentinfo' }, - h2(strong('')), - p(''), - p(''), + h2(strong(agentName)), + p(agentDesc), + p(lic), ); const contactButton = button({ class: 'contactagent' }, 'CONTACT AGENT'); @@ -39,14 +39,6 @@ export default function decorate(block) { agentInfo, contactButton, ); - - const agentImageElement = agentPicture.querySelector('img'); - const agentInfoElements = agentInfo.children; - - agentImageElement.src = pic; - agentInfoElements[0].innerHTML = `${agentName}`; - agentInfoElements[1].textContent = agentDesc; - agentInfoElements[2].textContent = lic; } const displayedElement = document.querySelector('.floatingagent'); From 8fcc666d86b5d95822cd305bf1a559f39e6a762e Mon Sep 17 00:00:00 2001 From: Ritwik Srivastava Date: Tue, 4 Jun 2024 19:49:10 +0530 Subject: [PATCH 45/62] Floating agent --- blocks/floatingagent/floatingagent.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/blocks/floatingagent/floatingagent.js b/blocks/floatingagent/floatingagent.js index 5b6cb279..766d94ff 100644 --- a/blocks/floatingagent/floatingagent.js +++ b/blocks/floatingagent/floatingagent.js @@ -39,6 +39,23 @@ export default function decorate(block) { agentInfo, contactButton, ); + const displayedElement = document.querySelector('.floatingagent'); + + const heroElement = document.querySelector('.hero-wrapper'); + + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + displayedElement.style.display = 'none'; + } else { + displayedElement.style.display = 'flex'; + } + }); + }, { + threshold: [0], + }); + + observer.observe(heroElement); } const displayedElement = document.querySelector('.floatingagent'); From 94ba880ba84ae008c2544ebb4381f6bf323df330 Mon Sep 17 00:00:00 2001 From: Ritwik Srivastava Date: Tue, 4 Jun 2024 19:51:50 +0530 Subject: [PATCH 46/62] Floating agent --- blocks/floatingagent/floatingagent.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/blocks/floatingagent/floatingagent.js b/blocks/floatingagent/floatingagent.js index 766d94ff..ef0d685c 100644 --- a/blocks/floatingagent/floatingagent.js +++ b/blocks/floatingagent/floatingagent.js @@ -57,21 +57,3 @@ export default function decorate(block) { observer.observe(heroElement); } - -const displayedElement = document.querySelector('.floatingagent'); - -const heroElement = document.querySelector('.hero-wrapper'); - -const observer = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - displayedElement.style.display = 'none'; - } else { - displayedElement.style.display = 'flex'; - } - }); -}, { - threshold: [0], -}); - -observer.observe(heroElement); From 275f4c55ce44bc183685e799c1cc2e14aaed8842 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Tue, 4 Jun 2024 17:24:16 -0600 Subject: [PATCH 47/62] refactor innerhtml, add link, fix styling --- blocks/property-listing/property-listing.js | 1 + blocks/shared/property/cards.js | 174 +++++++++++--------- blocks/side-modal/side-modal.css | 17 +- 3 files changed, 103 insertions(+), 89 deletions(-) diff --git a/blocks/property-listing/property-listing.js b/blocks/property-listing/property-listing.js index 7c714bb4..f382a5e9 100644 --- a/blocks/property-listing/property-listing.js +++ b/blocks/property-listing/property-listing.js @@ -34,6 +34,7 @@ export default async function decorate(block) { list.classList.add('property-list-cards', `rows-${Math.floor(search.pageSize / 8)}`); block.append(list); propertySearch(search).then((results) => { + window.propertyListings = results; renderCards(list, results.properties); }); } diff --git a/blocks/shared/property/cards.js b/blocks/shared/property/cards.js index 52afd8fc..e545776d 100644 --- a/blocks/shared/property/cards.js +++ b/blocks/shared/property/cards.js @@ -1,10 +1,16 @@ import { decorateFormLinks } from '../../../scripts/scripts.js'; +import { + a, div, domEl, img, p, span, +} from '../../../scripts/dom-helpers.js'; function createImage(listing) { if (listing.SmallMedia?.length > 0) { - return `${listing.StreetName}`; + const tempImg = img({ + src: listing.SmallMedia[0].mediaUrl, alt: listing.StreetName, loading: 'lazy', class: 'property-thumbnail', + }); + return tempImg; } - return '
no images available
'; + return div({ class: 'property-no-available-image' }, span('no images available')); } export function createCard(listing) { @@ -27,98 +33,106 @@ export function createCard(listing) { specs.push(`${listing.LivingArea} ${listing.LivingAreaUnits}`); } - const item = document.createElement('div'); - item.classList.add('listing-tile'); + let classes = 'listing-tile'; if (listing.OpenHouses?.length > 0) { - item.classList.add('has-open-houses'); + classes += ' has-open-houses'; } if (listing.FeaturedListing) { - item.classList.add('is-featured'); + classes += ' is-featured'; } if (listing.PdpPath.includes('LuxuryTheme=true')) { - item.classList.add('is-luxury'); + classes += ' is-luxury'; } const applicationType = listing.ListingType && listing.ListingType === 'For Rent' ? `${listing.ListingType}` : ''; if (listing.ClosedDate !== '01/01/0001') { - item.classList.add('is-sold'); + classes += 'is-sold'; listing.mlsStatus = 'Closed'; } - item.innerHTML = ` - -
-
- ${createImage(listing)} -
-
-
-
Luxury Collection
-
- - Open House -
-
-
-
-
- Featured Listing - ${applicationType} - ${listing.mlsStatus} -
-
-

${listing.ListPriceUS}

-
-
-
-
-
-
-
-
Closed: ${listing.ClosedDate}
-
- ${listing.StreetName} -
- ${listing.City}, ${listing.StateOrProvince} ${listing.PostalCode} -
-
${specs.join(' / ')}
-
-
- -
-
-
-
-
Listing courtesy of: ${listing.CourtesyOf}
-
Listing provided by: ${listing.listAor}
-
-
- Disclaimer Logo Image -
-
- `; - return item; + const newEl = div({ class: classes }, + a({ href: detailsPath, rel: 'noopener', 'aria-labelledby': `listing-${listing.ListingId}-address` }, + div({ class: 'listing-image-container' }, + div({ class: 'property-image' }, createImage(listing)), + div({ class: 'image-position-top' }, + div({ class: 'property-labels' }, + div({ class: 'property-label luxury' }, 'Luxury Collection'), + div({ class: 'property-label open-house' }, + span({ class: 'icon icon-openhouse' }, 'Open House'), + ), + ), + ), + div({ class: 'image-position-bottom' }, + div({ class: 'property-labels' }, + span({ class: 'property-label featured' }, 'Featured Listing'), + applicationType, + span({ class: 'property-label' }, listing.mlsStatus), + ), + div({ class: 'property-price' }, + p(listing.ListPriceUS), + ), + ), + ), + ), + div({ class: 'property-details' }, + div({ class: 'property-info-wrapper' }, + div({ class: 'property-info' }, + div({ class: 'sold-date' }, `Closed: ${listing.ClosedDate}`), + div({ id: `listing-${listing.ListingId}-address`, class: 'address' }, + listing.StreetName, + domEl('br'), + `${listing.City}, `, + `${listing.StateOrProvince} `, + listing.PostalCode, + ), + div({ class: 'specs' }, specs.join(' / ')), + ), + ), + div({ class: 'property-buttons' }, + div({ class: 'buttons-row-flex' }, + a({ 'aria-label': `Contact us about ${listing.StreetName}`, href: '/fragments/contact-property-form', class: 'button-property' }, + span({ class: 'icon icon-envelope' }, + img({ + 'data-icon-name': 'envelope', src: '/icons/envelope.svg', loading: 'lazy', alt: 'envelope', + }), + ), + span({ class: 'icon icon-envelopedark' }, + img({ + 'data-icon-name': 'envelopedark', src: '/icons/envelopedark.svg', loading: 'lazy', alt: 'envelope', + }), + ), + ), + a({ 'aria-label': `Save ${listing.StreetName} to saved properties.`, href: '#', class: 'button-property' }, + span({ class: 'icon icon-heartempty' }, + img({ + 'data-icon-name': 'heartempty', src: '/icons/heartempty.svg', loading: 'lazy', alt: 'heart', + }), + ), + span({ class: 'icon icon-heartemptydark' }, + img({ + 'data-icon-name': 'heartempty', src: '/icons/heartemptydark.svg', loading: 'lazy', alt: 'heart', + }), + ), + ), + ), + ), + ), + domEl('hr'), + div({ class: 'extra-info' }, + div( + div({ class: 'courtesy-info' }, `Listing courtesy of: ${listing.CourtesyOf}`), + div({ class: 'courtesy-provided' }, `Listing provided by: ${listing.listAor}`), + ), + div({ class: `listing-aor ${listing.listAor.toLowerCase()}` }, + img({ + class: 'rimls-image', src: '/styles/images/rimls_logo.jpg', alt: 'Disclaimer Logo Image', loading: 'lazy', height: '20', width: '33', + }), + ), + ), + ); + return newEl; } /** diff --git a/blocks/side-modal/side-modal.css b/blocks/side-modal/side-modal.css index 9e2e282d..312f63d3 100644 --- a/blocks/side-modal/side-modal.css +++ b/blocks/side-modal/side-modal.css @@ -1,10 +1,10 @@ aside.side-modal { overflow: hidden scroll; position: fixed; - top: 200px; + top: 65px; bottom: 0; - width: 100vw; - right: -100vw; + width: 100%; + right: 0; transition: right .2s cubic-bezier(.4,0,.2,1) .1s; background-color: white; z-index: 1090; @@ -19,15 +19,14 @@ aside.side-modal > div { padding: 53px 15px 15px; } -@media (min-width: 768px) { +@media (min-width: 600px) { aside.side-modal { - width: 50vw; - right: -50vw; + top: 0; + width: 600px; + right: -600px; } -} -@media (min-width: 992px) { aside.side-modal > div { - padding: 53px 16% 16%; + padding: 53px 45px 15px; } } From dba5aa8ec7af3b8005732c96f6d5dfb72f537ca1 Mon Sep 17 00:00:00 2001 From: Ritwik Srivastava Date: Wed, 5 Jun 2024 10:55:34 +0530 Subject: [PATCH 48/62] Fix Css mobile first approach --- blocks/floatingagent/floatingagent.css | 97 ++++++++++++++------------ 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/blocks/floatingagent/floatingagent.css b/blocks/floatingagent/floatingagent.css index b55eeb85..688323d9 100644 --- a/blocks/floatingagent/floatingagent.css +++ b/blocks/floatingagent/floatingagent.css @@ -1,7 +1,7 @@ .floatingagent.block { - display: none; + display: flex; align-items: center; - justify-content: space-between; + justify-content: flex-start; position: fixed; bottom: 0; left: 0; @@ -14,68 +14,75 @@ } .floatingagent.block > .floating-agent-col { - margin-left: 100px; - display: flex; - margin-right: 10px; - flex: none; -} - -.floatingagent.block > .agentinfo > p { - margin-block-end: 0; - font-family: var(--font-family-primary); - font-size: var(--body-font-size-xxs); - font-style: normal; - font-weight: var(--font-weight-normal); - letter-spacing: normal; - color: #2a2223; -} - -.floatingagent.block > .agentinfo > h2 { - font-size: var(--heading-font-size-m); - line-height: 130%; - letter-spacing: .18px; - vertical-align: top; - color: #2a2223; - width: max-content; -} - -.floatingagent.block > .floating-agent-col > picture { - height:40px; - width:30px; - margin-right: 40px; -} - -.floatingagent.block > .floating-agent-col > picture > img{ - display: inline; + display: none; } .floatingagent.block > .agentinfo { - flex-grow: 1; - margin-top: 10px; + display: none; } .floatingagent.block > .contactagent { + display: block; align-self: center; background: var(--primary-color); color: var(--tertiary-color); border: 0; - flex: none; padding: 10px; - margin-right: 5%; + margin-left: 35%; } -@media (max-width: 600px) { +@media (min-width: 600px) { .floatingagent.block { - justify-content: flex-start; + justify-content: space-between; + } + + .floatingagent.block > .floating-agent-col { + margin-left: 100px; + display: flex; + margin-right: 10px; + flex: none; + margin-bottom: 20px; } - .floatingagent.block > .floating-agent-col > picture > img, .floatingagent.block > .agentinfo { - display: none; + display: block; + flex-grow: 1; + margin-top: 10px; + margin-bottom: 15px; + } + + .floatingagent.block > .agentinfo > p { + margin-block-end: 0; + font-family: var(--font-family-primary); + font-size: var(--body-font-size-xxs); + font-style: normal; + font-weight: var(--font-weight-normal); + letter-spacing: normal; + color: #2a2223; + } + + .floatingagent.block > .agentinfo > h2 { + font-size: var(--heading-font-size-m); + line-height: 130%; + letter-spacing: var(--letter-spacing-xxxs); + vertical-align: top; + color: #2a2223; + width: max-content; + } + + .floatingagent.block > .floating-agent-col > picture { + height: 40px; + width: 30px; + margin-right: 40px; + } + + .floatingagent.block > .floating-agent-col > picture > img { + display: inline; } .floatingagent.block > .contactagent { - display: block; + flex: none; + margin-right: 5%; margin-left: 10px; } -} \ No newline at end of file +} From f4be7af2b80ef590c0510687f7679247ab50c108 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Wed, 5 Jun 2024 11:03:17 -0600 Subject: [PATCH 49/62] link envelope from property-listing --- blocks/contact-form/contact-form.js | 11 +++++- blocks/property-listing/property-listing.js | 40 +++++++++++---------- blocks/side-modal/side-modal.js | 2 ++ 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index d38dfff2..3f872301 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -9,6 +9,11 @@ const phoneRegex = /^[+]?[ (]?\d{3}[)]?[-.\s]?\d{3}[-.\s]?\d{4}$/; // Load reCaptcha script used on all forms. loadScript('/blocks/contact-form/forms/callback.js'); +function findListing() { + const temp = window.propertyListings?.properties || []; + return temp?.find((property) => property.ListingId === window.selectedListingId) || null; +} + /** * Adds form and cookie values to payload. * @@ -314,7 +319,11 @@ const addForm = async (block) => { }); const taEl = block.querySelector('textarea'); - if (taEl && taEl.placeholder) taEl.placeholder = i18n(taEl.placeholder); + if (taEl?.placeholder) taEl.placeholder = i18n(taEl.placeholder); + if (window.selectedListingId) { + const prop = findListing(); + taEl.value = `Hi, I would like more information about ${prop.StreetName}, ${prop.City}, ${prop.StateOrProvince} ${prop.PostalCode}.`; + } block.style.display = displayValue; diff --git a/blocks/property-listing/property-listing.js b/blocks/property-listing/property-listing.js index f382a5e9..1b0ba734 100644 --- a/blocks/property-listing/property-listing.js +++ b/blocks/property-listing/property-listing.js @@ -2,36 +2,38 @@ import { getMetadata, readBlockConfig } from '../../scripts/aem.js'; import { render as renderCards } from '../shared/property/cards.js'; import Search from '../../scripts/apis/creg/search/Search.js'; import { propertySearch } from '../../scripts/apis/creg/creg.js'; +import { a, div, p, span } from '../../scripts/dom-helpers.js'; export default async function decorate(block) { // Find and process list type configurations. const config = readBlockConfig(block); + const search = await Search.fromBlockConfig(config); + search.franchiseeCode = getMetadata('office-id'); + const searchUrl = `search?${search.asCregURLSearchParameters()}`; if (config.title) { - block.innerHTML = ` -
-
- ${config.title} -
-
- `; - if (config['link-text']) { - const div = document.createElement('div'); - const url = config['link-url'] || ''; - div.innerHTML = ` -

- ${config['link-text'] || 'See More'} -

`; - block.querySelector('.header').append(div); + const blockTitle = div({ class: 'header' }, + div( + span(config.title), + ), + ); + block.replaceChildren(blockTitle); + + if (config.link) { + const moreBtn = div( + p({ class: 'button-container' }, + a({ href: config['link-url'] || searchUrl, 'aria-label': config.link || 'See More', class: 'button secondary' }, + config.link || 'See More', + ), + ), + ); + block.querySelector('.header').append(moreBtn); } } else { block.innerHTML = ''; } - const search = await Search.fromBlockConfig(config); - search.franchiseeCode = getMetadata('office-id'); - const list = document.createElement('div'); - list.classList.add('property-list-cards', `rows-${Math.floor(search.pageSize / 8)}`); + const list = div({ class: `property-list-cards rows-${Math.floor(search.pageSize / 8)}` }); block.append(list); propertySearch(search).then((results) => { window.propertyListings = results; diff --git a/blocks/side-modal/side-modal.js b/blocks/side-modal/side-modal.js index 850807a5..ed30d06a 100644 --- a/blocks/side-modal/side-modal.js +++ b/blocks/side-modal/side-modal.js @@ -5,6 +5,8 @@ import { export async function showSideModal(a) { const { href } = a; + const listing = a.parentNode.parentNode.previousElementSibling.querySelector('div.address').id.split('-')[1]; + window.selectedListingId = listing; const module$ = import(`${window.hlx.codeBasePath}/scripts/util.js`); await loadCSS(`${window.hlx.codeBasePath}/blocks/side-modal/side-modal.css`); const content = await fetch(`${href}.plain.html`); From 88b99481734b73f729e92ba6ef1f4cf82c7b6e21 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Wed, 5 Jun 2024 12:12:45 -0600 Subject: [PATCH 50/62] added in disclaimer --- blocks/contact-form/contact-form.css | 8 +++++++- blocks/contact-form/contact-form.js | 3 +++ blocks/contact-form/forms/contact-property.html | 4 ++-- blocks/property-listing/property-listing.js | 4 +++- blocks/property-search-results/property-search-results.js | 1 + 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/blocks/contact-form/contact-form.css b/blocks/contact-form/contact-form.css index 11d8e01c..7bbae3e7 100644 --- a/blocks/contact-form/contact-form.css +++ b/blocks/contact-form/contact-form.css @@ -115,7 +115,13 @@ display: flex; justify-content: flex-start; margin-bottom: 10px; - cursor: pointer; +} + + +.contact-form.block form.contact-form .agent div.disclaimer { + font-size: var(--body-font-size-xxs); + line-height: var(--line-height-s); + color: var(--dark-grey); } .contact-form.block form.contact-form .agent > div:first-child { diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 3f872301..33c855aa 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -323,6 +323,9 @@ const addForm = async (block) => { if (window.selectedListingId) { const prop = findListing(); taEl.value = `Hi, I would like more information about ${prop.StreetName}, ${prop.City}, ${prop.StateOrProvince} ${prop.PostalCode}.`; + if (window.location.pathname.length === 1) { + block.querySelector('.disclaimer').remove(); + } } block.style.display = displayValue; diff --git a/blocks/contact-form/forms/contact-property.html b/blocks/contact-form/forms/contact-property.html index 63039424..02fbfe94 100644 --- a/blocks/contact-form/forms/contact-property.html +++ b/blocks/contact-form/forms/contact-property.html @@ -53,14 +53,14 @@
no
-
+
By providing this information, you are giving permission to Berkshire Hathaway HomeServices, Constellation1, a division of Constellation Web Solutions, Inc., and the members of the Berkshire Hathaway HomeServices real estate network (1) to contact you in response to your specific question or message, and (2) to register you in our system in order to communicate with you about properties for sale or rent in locations of interest to you. For more about how we will use your contact information, review our Privacy Policy.
- +
diff --git a/blocks/property-listing/property-listing.js b/blocks/property-listing/property-listing.js index 1b0ba734..014f6bcc 100644 --- a/blocks/property-listing/property-listing.js +++ b/blocks/property-listing/property-listing.js @@ -2,7 +2,9 @@ import { getMetadata, readBlockConfig } from '../../scripts/aem.js'; import { render as renderCards } from '../shared/property/cards.js'; import Search from '../../scripts/apis/creg/search/Search.js'; import { propertySearch } from '../../scripts/apis/creg/creg.js'; -import { a, div, p, span } from '../../scripts/dom-helpers.js'; +import { + a, div, p, span, +} from '../../scripts/dom-helpers.js'; export default async function decorate(block) { // Find and process list type configurations. diff --git a/blocks/property-search-results/property-search-results.js b/blocks/property-search-results/property-search-results.js index 7a12daad..69f5d520 100644 --- a/blocks/property-search-results/property-search-results.js +++ b/blocks/property-search-results/property-search-results.js @@ -103,6 +103,7 @@ async function doSearch(search, redraw = true) { const controller = searchController; propertySearch(search).then((results) => { if (!controller.signal.aborted) { + window.propertyListings = results; displayList(parent, results); contentWrapper.querySelector('.search-results-disclaimer-wrapper').replaceChildren( domEl('hr', { role: 'presentation', 'aria-hidden': true, tabindex: -1 }), From 2a73d7e8c612dc91a338ee89220b40e9709b014b Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Fri, 7 Jun 2024 18:27:04 -0600 Subject: [PATCH 51/62] added dynamic form information --- blocks/contact-form/contact-form.css | 12 ++++++ blocks/contact-form/contact-form.js | 43 ++++++++++++++++--- .../contact-form/forms/contact-property.html | 10 +++-- blocks/shared/property/cards.js | 2 +- blocks/side-modal/side-modal.css | 11 +++++ blocks/side-modal/side-modal.js | 3 +- scripts/apis/creg/creg.js | 16 +++++++ scripts/apis/creg/workers/envelope.js | 19 ++++++++ scripts/util.js | 17 ++++---- 9 files changed, 112 insertions(+), 21 deletions(-) create mode 100644 scripts/apis/creg/workers/envelope.js diff --git a/blocks/contact-form/contact-form.css b/blocks/contact-form/contact-form.css index 7bbae3e7..b13f27c3 100644 --- a/blocks/contact-form/contact-form.css +++ b/blocks/contact-form/contact-form.css @@ -11,6 +11,18 @@ margin-left: 40px; margin-bottom: 3em; } +.contact-form.block .contact-details { + display: flex; + flex-direction: row; + gap: 1em; + margin-bottom: 2em; +} + +.contact-form.block .profile { + display: flex; + flex-direction: column; + gap: 1em; +} .contact-form.block .company-name { font-size: var(--heading-font-size-m); diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 33c855aa..527b3765 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -1,5 +1,7 @@ -import { loadScript } from '../../scripts/aem.js'; +import { createOptimizedPicture, loadScript } from '../../scripts/aem.js'; +// import { getEnvelope } from '../../scripts/apis/creg/creg.js'; import { removeSideModal, i18nLookup, getCookieValue } from '../../scripts/util.js'; +import { a, div } from '../../scripts/dom-helpers.js'; const LOGIN_ERROR = 'There was a problem processing your request.'; const i18n = await i18nLookup(); @@ -9,9 +11,17 @@ const phoneRegex = /^[+]?[ (]?\d{3}[)]?[-.\s]?\d{3}[-.\s]?\d{4}$/; // Load reCaptcha script used on all forms. loadScript('/blocks/contact-form/forms/callback.js'); -function findListing() { - const temp = window.propertyListings?.properties || []; - return temp?.find((property) => property.ListingId === window.selectedListingId) || null; +function getImageURL(jsonString) { + try { + const data = JSON.parse(jsonString); + if (Array.isArray(data) && data.length > 0) { + const imageUrl = data[0].url; + return imageUrl; + } + } catch (error) { + return '/media/images/no-profile-image.png'; + } + return null; // Add a return statement at the end of the function } /** @@ -320,9 +330,28 @@ const addForm = async (block) => { const taEl = block.querySelector('textarea'); if (taEl?.placeholder) taEl.placeholder = i18n(taEl.placeholder); - if (window.selectedListingId) { - const prop = findListing(); - taEl.value = `Hi, I would like more information about ${prop.StreetName}, ${prop.City}, ${prop.StateOrProvince} ${prop.PostalCode}.`; + if (window.selectedListing) { + // const prop = await findListing(); + const prop = window.selectedListing; + // if the listing agent is supposed to be displayed vs the office + if (prop.propertyDetails.listAgentCd) { + const info = block.querySelector('.contact-info'); + const pic = getImageURL(prop.listAgent.reAgentDetail.image); + const profile = div({ class: 'profile' }, createOptimizedPicture(pic, prop.listAgent.recipientName, 'lazy', [{ width: '82' }])); + info.insertAdjacentElement('beforebegin', profile); + const name = block.querySelector('.company-name'); + const link = a({ href: '#' }, prop.listAgent.recipientName); // TODO: add link to agent profile + name.replaceChildren(link); + const email = block.querySelector('.company-email a'); + email.textContent = prop.listAgent.reAgentDetail.email; + email.href = `mailto:${prop.listAgent.reAgentDetail.email}`; + const phone = block.querySelector('.company-phone a'); + phone.textContent = prop.listAgent.reAgentDetail.officeTelephone; + phone.href = `tel:${prop.listAgent.reAgentDetail.officeTelephone}`; + } + + taEl.value = `Hi, I would like more information about ${prop.propertyDetails.unparsedAddress}`; + if (window.location.pathname.length === 1) { block.querySelector('.disclaimer').remove(); } diff --git a/blocks/contact-form/forms/contact-property.html b/blocks/contact-form/forms/contact-property.html index 02fbfe94..452c2be9 100644 --- a/blocks/contact-form/forms/contact-property.html +++ b/blocks/contact-form/forms/contact-property.html @@ -1,8 +1,12 @@
Contact Us
-
Berkshire Hathaway HomeServices
Commonwealth Real Estate
- - +
+
+
Berkshire Hathaway HomeServices
Commonwealth Real Estate
+ + +
+
diff --git a/blocks/shared/property/cards.js b/blocks/shared/property/cards.js index e545776d..1b4076c9 100644 --- a/blocks/shared/property/cards.js +++ b/blocks/shared/property/cards.js @@ -80,7 +80,7 @@ export function createCard(listing) { div({ class: 'property-info-wrapper' }, div({ class: 'property-info' }, div({ class: 'sold-date' }, `Closed: ${listing.ClosedDate}`), - div({ id: `listing-${listing.ListingId}-address`, class: 'address' }, + div({ id: `listing-${listing.PropId}-address`, class: 'address' }, listing.StreetName, domEl('br'), `${listing.City}, `, diff --git a/blocks/side-modal/side-modal.css b/blocks/side-modal/side-modal.css index 312f63d3..9fb8c48b 100644 --- a/blocks/side-modal/side-modal.css +++ b/blocks/side-modal/side-modal.css @@ -29,4 +29,15 @@ aside.side-modal > div { aside.side-modal > div { padding: 53px 45px 15px; } + + body > div > div.side-modal-overlay { + background-color: #212529; + width: 100%; + height: 100%; + position: fixed; + opacity: .5; + z-index: 1040; + top: 0; + left: 0; + } } diff --git a/blocks/side-modal/side-modal.js b/blocks/side-modal/side-modal.js index ed30d06a..1835ba28 100644 --- a/blocks/side-modal/side-modal.js +++ b/blocks/side-modal/side-modal.js @@ -2,11 +2,12 @@ import { decorateSections, decorateBlocks, loadBlocks, decorateButtons, decorateIcons, loadCSS, } from '../../scripts/aem.js'; +import { getEnvelope } from '../../scripts/apis/creg/creg.js'; export async function showSideModal(a) { const { href } = a; const listing = a.parentNode.parentNode.previousElementSibling.querySelector('div.address').id.split('-')[1]; - window.selectedListingId = listing; + window.selectedListing = await getEnvelope(listing); const module$ = import(`${window.hlx.codeBasePath}/scripts/util.js`); await loadCSS(`${window.hlx.codeBasePath}/blocks/side-modal/side-modal.css`); const content = await fetch(`${href}.plain.html`); diff --git a/scripts/apis/creg/creg.js b/scripts/apis/creg/creg.js index e7c2d9bd..4e42cceb 100644 --- a/scripts/apis/creg/creg.js +++ b/scripts/apis/creg/creg.js @@ -78,3 +78,19 @@ export async function getEconomicDetails(lat, long) { }); }); } + +/** + * Retrieves the envelope for a given listing ID. + * + * @param {string} listingId - The ID of the listing. + * @returns {Promise} A promise that resolves to the envelope data. + */ +export async function getEnvelope(listingId) { + return new Promise((resolve) => { + const worker = new Worker(`${window.hlx.codeBasePath}/scripts/apis/creg/workers/envelope.js`, { type: 'module' }); + worker.onmessage = (e) => resolve(e.data); + worker.postMessage({ + listingId, + }); + }); +} diff --git a/scripts/apis/creg/workers/envelope.js b/scripts/apis/creg/workers/envelope.js new file mode 100644 index 00000000..768ed801 --- /dev/null +++ b/scripts/apis/creg/workers/envelope.js @@ -0,0 +1,19 @@ +/** + * Handle the Worker event. Fetches details for each provided listing id. + * + * @param {Object} event the worker event. + * @param {string} event.data.api the URL to fetch. + * @param {string[]} event.data.ids list of listing ids + */ +onmessage = async (event) => { + const { listingId } = event.data; + + try { + const response = await fetch(`/bin/bhhs/CregPropertySearchServlet?SearchType=Envelope&ListingId=${listingId}`); + const data = response.ok ? await response.json() : undefined; + + postMessage(data); + } catch (error) { + postMessage({}); + } +}; diff --git a/scripts/util.js b/scripts/util.js index 6a78bdd8..2f665867 100644 --- a/scripts/util.js +++ b/scripts/util.js @@ -1,4 +1,5 @@ import { fetchPlaceholders } from './aem.js'; +import { div, domEl } from './dom-helpers.js'; /** * Creates the standard Spinner Div. @@ -54,6 +55,7 @@ let focusElement; export function removeSideModal() { if (!sideModal) return; + sideModal.parentNode.nextSibling.remove(); sideModal.parentNode.remove(); sideModal = null; document.body.classList.remove('disable-scroll'); @@ -62,15 +64,12 @@ export function removeSideModal() { export async function showSideModal(content, decorateContent) { if (!sideModal) { - const fragment = document.createRange().createContextualFragment(` -
- -
- `); - sideModal = fragment.querySelector('.side-modal'); - document.body.append(...fragment.children); + const temp = div( + domEl('aside', { class: 'side-modal' }, div()), + div({ class: 'side-modal-overlay' }), + ); + sideModal = temp.querySelector('.side-modal'); + document.body.append(temp); } const container = sideModal.querySelector('div'); container.replaceChildren(...content); From 5236b317c70691489af99807ba474837a75bb514 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Fri, 7 Jun 2024 18:29:38 -0600 Subject: [PATCH 52/62] fix naming conflict --- scripts/util.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/util.js b/scripts/util.js index 2f665867..db887d1c 100644 --- a/scripts/util.js +++ b/scripts/util.js @@ -7,10 +7,10 @@ import { div, domEl } from './dom-helpers.js'; * @returns {HTMLDivElement} the spinner div. */ export function getSpinner() { - const div = document.createElement('div'); - div.classList.add('loading-spinner'); - div.innerHTML = ''; - return div; + const spinner = document.createElement('div'); + spinner.classList.add('loading-spinner'); + spinner.innerHTML = ''; + return spinner; } /** From c26841970ec849f9db75787f1799685aeaae5f69 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Fri, 7 Jun 2024 18:31:06 -0600 Subject: [PATCH 53/62] linting css --- blocks/contact-form/contact-form.css | 1 + 1 file changed, 1 insertion(+) diff --git a/blocks/contact-form/contact-form.css b/blocks/contact-form/contact-form.css index b13f27c3..03a1cd47 100644 --- a/blocks/contact-form/contact-form.css +++ b/blocks/contact-form/contact-form.css @@ -11,6 +11,7 @@ margin-left: 40px; margin-bottom: 3em; } + .contact-form.block .contact-details { display: flex; flex-direction: row; From b5466ba58f8413e92c3261a0158c00fece6677e7 Mon Sep 17 00:00:00 2001 From: Piyush Jindal Date: Mon, 10 Jun 2024 20:08:45 +0530 Subject: [PATCH 54/62] Agent Address block added (#245) * Agent Address block added * Review Comments incorporated --------- Co-authored-by: piyushjindal --- blocks/agent-address/agent-address.css | 35 ++++++++++++++++++++++++++ blocks/agent-address/agent-address.js | 22 ++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 blocks/agent-address/agent-address.css create mode 100644 blocks/agent-address/agent-address.js diff --git a/blocks/agent-address/agent-address.css b/blocks/agent-address/agent-address.css new file mode 100644 index 00000000..bc074896 --- /dev/null +++ b/blocks/agent-address/agent-address.css @@ -0,0 +1,35 @@ +.agent-address.block { + padding: 2rem; + background-color: var(--tertiary-color); +} + +.agent-address.block .address { + margin-bottom: 2rem; +} + +.agent-address.block .address>p { + margin-bottom: 0; + font-size: var(--body-font-size-xs); +} + +.agent-address.block a { + border: 1px solid var(--primary-color); + color: var(--primary-color); + font-weight: var(--font-weight-bold); + letter-spacing: var(--letter-spacing-m); + text-transform: uppercase; + padding: 0.5rem 1rem; + text-decoration: none; + font-size: var(--body-font-size-s); +} + +.agent-address.block a:hover { + color: var(--primary-light); + background-color: var(--primary-color); +} + +@media (min-width: 600px) { + .agent-address.block { + display: none; + } +} diff --git a/blocks/agent-address/agent-address.js b/blocks/agent-address/agent-address.js new file mode 100644 index 00000000..187419f0 --- /dev/null +++ b/blocks/agent-address/agent-address.js @@ -0,0 +1,22 @@ +import { getMetadata } from '../../scripts/aem.js'; +import { + a, div, p, +} from '../../scripts/dom-helpers.js'; + +export default function decorate(block) { + const streetAddress = getMetadata('streetaddress'); + const addressLocality = getMetadata('addresslocality'); + const addressRegion = getMetadata('addressregion'); + const postalCode = getMetadata('postalcode'); + + const textDiv = div({ class: 'address' }, + p('Berkshire Hathaway HomeServices'), + p('Commonwealth Real Estate'), + p(streetAddress), + p(`${addressLocality}, ${addressRegion} ${postalCode}`), + ); + const text = `${streetAddress}, ${addressLocality}, ${addressRegion} ${postalCode}`; + + const anchor = a({ href: `https://maps.google.com/maps?q=${text}`, target: '_blank' }, 'Directions'); + block.replaceChildren(textDiv, anchor); +} From 317e7abd226d4f4b17eddcfef2e807a2fdecf55c Mon Sep 17 00:00:00 2001 From: Piyush Jindal Date: Mon, 10 Jun 2024 20:10:20 +0530 Subject: [PATCH 55/62] Minor changes wrt to agent about block (#246) * Minor changes wrt to agent about block * Minor changes wrt to SEO * Agent Profile changes --------- Co-authored-by: piyushjindal --- blocks/agent-about/agent-about.css | 8 +++--- blocks/agent-about/agent-about.js | 25 ++++++++++++++++--- blocks/agent-profile/agent-profile.js | 14 +++++------ .../agent-transactions/agent-transactions.js | 4 +-- 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/blocks/agent-about/agent-about.css b/blocks/agent-about/agent-about.css index 49b456d0..29fbbb77 100644 --- a/blocks/agent-about/agent-about.css +++ b/blocks/agent-about/agent-about.css @@ -7,10 +7,6 @@ letter-spacing: normal; } -.agent-about.block a { - cursor: pointer; -} - .agent-about.block .hide { display: none; } @@ -20,6 +16,8 @@ display: inline-block; margin-top: 1rem; text-decoration: underline; + font-size: var(--body-font-size-xs); + color: var(--black); } .agent-about.block a.view-more::after { @@ -33,7 +31,7 @@ .agent-about.block>div.cols-1, .agent-about.block>div.cols-2, .agent-about.block>div.cols-3 { - padding-bottom: 1rem; + padding-bottom: 2rem; } .agent-about.block>div>div:first-of-type { diff --git a/blocks/agent-about/agent-about.js b/blocks/agent-about/agent-about.js index 80949538..fabeecae 100644 --- a/blocks/agent-about/agent-about.js +++ b/blocks/agent-about/agent-about.js @@ -1,3 +1,4 @@ +import { getMetadata } from '../../scripts/aem.js'; import { a, div, ul, li, } from '../../scripts/dom-helpers.js'; @@ -18,7 +19,23 @@ const viewMoreOnClick = (name, anchor, block) => { }); }; +const getCol = (list, colText) => { + const colsUl = ul(); + list.split(',').forEach((x) => { + colsUl.append(li(x.trim())); + }); + return div(div(colText), div(colsUl)); +}; + export default function decorate(block) { + const aboutText = getMetadata('about'); + const accreditations = getMetadata('professional-accreditations'); + const languages = getMetadata('languages'); + + block.replaceChildren(div(div('About'), div(aboutText)), + getCol(accreditations, 'Professional Accreditations'), + getCol(languages, 'Languages')); + const children = [...block.children]; if (children?.length) { children.forEach((child, index) => { @@ -31,7 +48,7 @@ export default function decorate(block) { child.children[1].classList.add('hide'); child.append(div({ class: `${name}-truncate` }, `${child.children[1].textContent.substring(0, threshold)}...`)); - const anchor = a({ class: 'view-more' }); + const anchor = a({ class: 'view-more', href: '#' }); child.append(anchor); viewMoreOnClick(name, anchor, block); } @@ -43,15 +60,15 @@ export default function decorate(block) { if (liItems.length > threshold) { child.children[1].classList.add('hide'); - const tempUl = ul({ }); + const tempUl = ul(); Array.from(child.children[1].querySelectorAll('li')) .slice(0, threshold).forEach((liItem) => { - const tempLi = li({}, liItem.textContent); + const tempLi = li(liItem.textContent); tempUl.append(tempLi); }); child.append(div({ class: `${name}-truncate` }, tempUl)); - const anchor = a({ class: 'view-more' }); + const anchor = a({ class: 'view-more', href: '#' }); child.append(anchor); viewMoreOnClick(name, anchor, block); } diff --git a/blocks/agent-profile/agent-profile.js b/blocks/agent-profile/agent-profile.js index 61f963b7..191b685f 100644 --- a/blocks/agent-profile/agent-profile.js +++ b/blocks/agent-profile/agent-profile.js @@ -8,13 +8,13 @@ const getPhoneDiv = () => { let phoneUl; if (getMetadata('direct-phone')) { - phoneUl = ul({}); - phoneUl.append(li({}, 'Direct: ', getMetadata('direct-phone'))); + phoneUl = ul(); + phoneUl.append(li('Direct: ', getMetadata('direct-phone'))); } if (getMetadata('office-phone')) { - phoneUl = phoneUl || ul({}); - phoneUl.append(li({}, 'Office: ', getMetadata('office-phone'))); + phoneUl = phoneUl || ul(); + phoneUl.append(li('Office: ', getMetadata('office-phone'))); } if (phoneUl) { @@ -62,9 +62,9 @@ const getSocialDiv = () => { ['facebook', 'instagram', 'linkedin'].forEach((x) => { const url = getMetadata(x); - socialUl = socialUl || ul({}); + socialUl = socialUl || ul(); if (url) { - const socialLi = li({}, a({ + const socialLi = li(a({ href: url, class: x, title: x, 'aria-label': x, }, span({ class: `icon icon-${x}` }))); socialUl.append(socialLi); @@ -82,7 +82,7 @@ const getSocialDiv = () => { export default async function decorate(block) { const profileImage = getImageDiv(); const profileContent = div({ class: 'profile-content' }, - div({ class: 'name' }, h1({}, getMetadata('name'))), + div({ class: 'name' }, h1(getMetadata('name'))), div({ class: 'designation' }, getMetadata('designation')), ); diff --git a/blocks/agent-transactions/agent-transactions.js b/blocks/agent-transactions/agent-transactions.js index e2e354a8..d7e13c00 100644 --- a/blocks/agent-transactions/agent-transactions.js +++ b/blocks/agent-transactions/agent-transactions.js @@ -78,8 +78,8 @@ export default async function decorate(block) { trBody.appendChild(trElement); }); - const tableElement = table({}, thead({}, theadTr), trBody); - const heading1 = h1({}, 'Closed Transactions'); + const tableElement = table(thead(theadTr), trBody); + const heading1 = h1('Closed Transactions'); const anchor = a({ class: 'show-more' }); anchor.addEventListener('click', () => { if (anchor.classList.contains('show-more')) { From f7ab357070ee85f3e12c17047af9fd9dd6bdd4ae Mon Sep 17 00:00:00 2001 From: Ritwik Srivastava Date: Tue, 11 Jun 2024 11:56:41 +0530 Subject: [PATCH 56/62] Add Agent Properties --- blocks/agent-property/agent-property.css | 100 +++++++++++++++++++++++ blocks/agent-property/agent-property.js | 97 ++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 blocks/agent-property/agent-property.css create mode 100644 blocks/agent-property/agent-property.js diff --git a/blocks/agent-property/agent-property.css b/blocks/agent-property/agent-property.css new file mode 100644 index 00000000..fbbef556 --- /dev/null +++ b/blocks/agent-property/agent-property.css @@ -0,0 +1,100 @@ +@import url('../shared/property/cards.css'); + +.agent-property.block { + overflow: hidden; + width: 100%; +} + +.agent-property.block a { + text-decoration: none; +} + +.agent-property.block .gmap-canvas { + width: 100%; + height: 0; + transition: height 0.5s ease; + display: none; +} + +.agent-property.block .view-toggle { + margin-bottom: 20px; +} + +.agent-property.block .gmap-canvas.active { + display:block; + height: 500px; +} + +.agent-property.block .view-toggle .card-view { + display: none; + margin-left: 70%; + font-family: Manrope, sans-serif; + font-size: 16px; + font-style: normal; + font-weight: 700; + letter-spacing: 1.6px; + text-transform: uppercase; + border: 1px solid #2a2223; + line-height: 35px; + background: #fff; +} + +.agent-property.block .view-toggle .map-view { + margin-left: 70%; + font-family: Manrope, sans-serif; + font-size: 16px; + font-style: normal; + font-weight: 700; + letter-spacing: 1.6px; + text-transform: uppercase; + border: 1px solid #2a2223; + line-height: 35px; + background: #fff; +} + +.agent-property.block .header { + display: flex; + flex-direction: column; +} + +.agent-property.block .header > div { + margin-bottom: 24px; +} + +.agent-property.block .header > div > span { + color: var(--primary-color); + font-size: var(--heading-font-size-l); + font-weight: var(--font-weight-semibold); + letter-spacing: initial; + line-height: var(--line-height-m); + text-transform: capitalize; +} + +.agent-property.block .header > div > p { + margin: 0; +} + +.agent-property.block .property-list-cards .listing-tile .extra-info { + display:none; +} + +.liveby-community .agent-property.block { + max-width: 1150px; + margin: auto; +} + +@media (min-width: 600px) { + .agent-property.block .header { + flex-direction: row; + justify-content: space-between; + } + + .agent-property.block .header .button-container { + justify-content: flex-end; + } + + .agent-property.block .view-toggle .map-view , .agent-property.block .view-toggle .card-view { + margin-left: 90.5%; + width: 115px; + } +} \ No newline at end of file diff --git a/blocks/agent-property/agent-property.js b/blocks/agent-property/agent-property.js new file mode 100644 index 00000000..447f8557 --- /dev/null +++ b/blocks/agent-property/agent-property.js @@ -0,0 +1,97 @@ +/* global google */ + +import { render as renderCards } from '../shared/property/cards.js'; +import { button, div } from '../../scripts/dom-helpers.js'; +import loadMaps from '../../scripts/google-maps/index.js'; +import { loadScript, getMetadata } from '../../scripts/aem.js'; + +const cardView = button({ class: 'card-view' }, 'Grid View'); +const mapView = button({ class: 'map-view' }, 'Map View'); +const viewToggle = div({ class: 'view-toggle' }); +const map = div({ class: 'gmap-canvas' }); +const agentId = getMetadata('agent-id'); +let centerlat; +let centerlong; +let data; + +function initMap(block, properties) { + const ele = block.querySelector('.gmap-canvas'); + + const gmap = new google.maps.Map(ele, { + zoom: 9, // Set an appropriate zoom level + center: { lat: centerlat, lng: centerlong }, // Set a default center + mapTypeId: google.maps.MapTypeId?.ROADMAP, + clickableIcons: false, + gestureHandling: 'cooperative', + visualRefresh: true, + disableDefaultUI: true, + }); + + const createMarker = (property, amap) => new google.maps.Marker({ + position: { lat: parseFloat(property.Latitude), lng: parseFloat(property.Longitude) }, + map: amap, + title: property.StreetName, + }); + + // Create markers for all properties + properties.forEach((property) => { + createMarker(property, gmap); + }); + + // Add markers for each property + /* properties.forEach((property) => { + new google.maps.Marker({ + position: { lat: parseFloat(property.Latitude), lng: parseFloat(property.Longitude) }, + map: gmap, + title: property.StreetName, // You can customize the title if needed + }); + }); */ +} + +export default async function decorate(block) { + const list = document.createElement('div'); + list.classList.add('property-list-cards', 'rows-1'); + viewToggle.append(cardView, mapView); + block.append(viewToggle, list, map); + /* firstProperty = result.listings.properties[0]; + centerlat = firstProperty.Latitude; + centerlong = firstProperty.Longitude; + renderCards(list, result.listings.properties); */ + + try { + const response = await fetch(`/bin/bhhs/agentPropertyListingsServlet.${agentId}.json`); + data = await response.json(); + if (data) { + const [firstProperty] = data.listings.properties; + const { Latitude: latitude, Longitude: longitude } = firstProperty; + // Parse the latitude and longitude as floats + centerlat = parseFloat(latitude); + centerlong = parseFloat(longitude); + renderCards(list, data.listings.properties); + } + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error fetching agent properties', error); + } + + document.querySelector('.card-view').addEventListener('click', () => { + document.querySelector('.property-list-cards').style.display = 'grid'; + document.querySelector('.card-view').style.display = 'none'; + document.querySelector('.map-view').style.display = 'block'; + document.querySelector('.gmap-canvas').classList.remove('active'); + }); + + document.querySelector('.map-view').addEventListener('click', async () => { + document.querySelector('.gmap-canvas').classList.add('active'); + document.querySelector('.map-view').style.display = 'none'; + document.querySelector('.card-view').style.display = 'block'; + document.querySelector('.property-list-cards').style.display = 'none'; + loadMaps(); + await google.maps.importLibrary('core'); + await google.maps.importLibrary('maps'); + await google.maps.importLibrary('marker'); + await loadScript('https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js', { type: 'application/javascript' }); + await loadScript('https://unpkg.com/jsts/dist/jsts.min.js', { type: 'application/javascript' }); + initMap(block, data.listings.properties); + }); +} From 143b1be5ea8602ef0a1f599eb9270308eed28bb4 Mon Sep 17 00:00:00 2001 From: Ritwik Srivastava Date: Tue, 11 Jun 2024 11:59:41 +0530 Subject: [PATCH 57/62] Add Agent Properties --- blocks/agent-property/agent-property.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/agent-property/agent-property.js b/blocks/agent-property/agent-property.js index 447f8557..ce39c973 100644 --- a/blocks/agent-property/agent-property.js +++ b/blocks/agent-property/agent-property.js @@ -59,7 +59,7 @@ export default async function decorate(block) { renderCards(list, result.listings.properties); */ try { - const response = await fetch(`/bin/bhhs/agentPropertyListingsServlet.${agentId}.json`); + const response = await fetch('https://www.commonmoves.com/bin/bhhs/agentPropertyListingsServlet.${agentId}.json'); data = await response.json(); if (data) { const [firstProperty] = data.listings.properties; From 06f5fa4eb99c08e4fde23b66acdf0d8c86a0d5f4 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Tue, 11 Jun 2024 17:15:25 -0600 Subject: [PATCH 58/62] remove window references, fixed image --- blocks/contact-form/contact-form.js | 14 +++++++++----- blocks/property-listing/property-listing.js | 1 - .../property-search-results.js | 1 - 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 527b3765..48c9bdd8 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -1,7 +1,7 @@ -import { createOptimizedPicture, loadScript } from '../../scripts/aem.js'; +import { loadScript } from '../../scripts/aem.js'; // import { getEnvelope } from '../../scripts/apis/creg/creg.js'; import { removeSideModal, i18nLookup, getCookieValue } from '../../scripts/util.js'; -import { a, div } from '../../scripts/dom-helpers.js'; +import { a, div, img } from '../../scripts/dom-helpers.js'; const LOGIN_ERROR = 'There was a problem processing your request.'; const i18n = await i18nLookup(); @@ -15,8 +15,11 @@ function getImageURL(jsonString) { try { const data = JSON.parse(jsonString); if (Array.isArray(data) && data.length > 0) { - const imageUrl = data[0].url; - return imageUrl; + const imageUrl = new URL(data[0].url); + // Replace the hostname and pathname with the new ones + imageUrl.hostname = 'hsfazpw2storagesf1.blob.core.windows.net'; + imageUrl.pathname = `/hsflibrary${imageUrl.pathname}`; + return imageUrl.toString(); } } catch (error) { return '/media/images/no-profile-image.png'; @@ -24,6 +27,7 @@ function getImageURL(jsonString) { return null; // Add a return statement at the end of the function } + /** * Adds form and cookie values to payload. * @@ -337,7 +341,7 @@ const addForm = async (block) => { if (prop.propertyDetails.listAgentCd) { const info = block.querySelector('.contact-info'); const pic = getImageURL(prop.listAgent.reAgentDetail.image); - const profile = div({ class: 'profile' }, createOptimizedPicture(pic, prop.listAgent.recipientName, 'lazy', [{ width: '82' }])); + const profile = div({ class: 'profile' }, img({ src: pic, alt: prop.listAgent.recipientName, width: '82px' })); info.insertAdjacentElement('beforebegin', profile); const name = block.querySelector('.company-name'); const link = a({ href: '#' }, prop.listAgent.recipientName); // TODO: add link to agent profile diff --git a/blocks/property-listing/property-listing.js b/blocks/property-listing/property-listing.js index 014f6bcc..11549b93 100644 --- a/blocks/property-listing/property-listing.js +++ b/blocks/property-listing/property-listing.js @@ -38,7 +38,6 @@ export default async function decorate(block) { const list = div({ class: `property-list-cards rows-${Math.floor(search.pageSize / 8)}` }); block.append(list); propertySearch(search).then((results) => { - window.propertyListings = results; renderCards(list, results.properties); }); } diff --git a/blocks/property-search-results/property-search-results.js b/blocks/property-search-results/property-search-results.js index 69f5d520..7a12daad 100644 --- a/blocks/property-search-results/property-search-results.js +++ b/blocks/property-search-results/property-search-results.js @@ -103,7 +103,6 @@ async function doSearch(search, redraw = true) { const controller = searchController; propertySearch(search).then((results) => { if (!controller.signal.aborted) { - window.propertyListings = results; displayList(parent, results); contentWrapper.querySelector('.search-results-disclaimer-wrapper').replaceChildren( domEl('hr', { role: 'presentation', 'aria-hidden': true, tabindex: -1 }), From 46697b4b0cfa7b0c00a3deda52cb74b0b8cf3dd3 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Tue, 11 Jun 2024 17:17:42 -0600 Subject: [PATCH 59/62] remove extra line --- blocks/contact-form/contact-form.js | 1 - 1 file changed, 1 deletion(-) diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 48c9bdd8..2c33df20 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -27,7 +27,6 @@ function getImageURL(jsonString) { return null; // Add a return statement at the end of the function } - /** * Adds form and cookie values to payload. * From 86c30ae2f2d27f6e33dfe0be6f43293dab050eaf Mon Sep 17 00:00:00 2001 From: Ritwik Srivastava Date: Mon, 17 Jun 2024 10:57:57 +0530 Subject: [PATCH 60/62] Add Agent Properties --- blocks/agent-property/agent-property.js | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/blocks/agent-property/agent-property.js b/blocks/agent-property/agent-property.js index ce39c973..5d14debc 100644 --- a/blocks/agent-property/agent-property.js +++ b/blocks/agent-property/agent-property.js @@ -16,7 +16,6 @@ let data; function initMap(block, properties) { const ele = block.querySelector('.gmap-canvas'); - const gmap = new google.maps.Map(ele, { zoom: 9, // Set an appropriate zoom level center: { lat: centerlat, lng: centerlong }, // Set a default center @@ -33,19 +32,9 @@ function initMap(block, properties) { title: property.StreetName, }); - // Create markers for all properties properties.forEach((property) => { createMarker(property, gmap); }); - - // Add markers for each property - /* properties.forEach((property) => { - new google.maps.Marker({ - position: { lat: parseFloat(property.Latitude), lng: parseFloat(property.Longitude) }, - map: gmap, - title: property.StreetName, // You can customize the title if needed - }); - }); */ } export default async function decorate(block) { @@ -53,18 +42,13 @@ export default async function decorate(block) { list.classList.add('property-list-cards', 'rows-1'); viewToggle.append(cardView, mapView); block.append(viewToggle, list, map); - /* firstProperty = result.listings.properties[0]; - centerlat = firstProperty.Latitude; - centerlong = firstProperty.Longitude; - renderCards(list, result.listings.properties); */ try { - const response = await fetch('https://www.commonmoves.com/bin/bhhs/agentPropertyListingsServlet.${agentId}.json'); + const response = await fetch(`https://www.commonmoves.com/bin/bhhs/agentPropertyListingsServlet.${agentId}.json`); data = await response.json(); if (data) { const [firstProperty] = data.listings.properties; const { Latitude: latitude, Longitude: longitude } = firstProperty; - // Parse the latitude and longitude as floats centerlat = parseFloat(latitude); centerlong = parseFloat(longitude); renderCards(list, data.listings.properties); From 9b943d2b6489f06c235602d2c7603da5b0eed660 Mon Sep 17 00:00:00 2001 From: RitwikSrivastava <45959816+RitwikSrivastava@users.noreply.github.com> Date: Mon, 17 Jun 2024 19:09:48 +0530 Subject: [PATCH 61/62] Update agent-property.js --- blocks/agent-property/agent-property.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/agent-property/agent-property.js b/blocks/agent-property/agent-property.js index 5d14debc..2b60823b 100644 --- a/blocks/agent-property/agent-property.js +++ b/blocks/agent-property/agent-property.js @@ -44,7 +44,7 @@ export default async function decorate(block) { block.append(viewToggle, list, map); try { - const response = await fetch(`https://www.commonmoves.com/bin/bhhs/agentPropertyListingsServlet.${agentId}.json`); + const response = await fetch(`/bin/bhhs/agentPropertyListingsServlet.${agentId}.json`); data = await response.json(); if (data) { const [firstProperty] = data.listings.properties; From 1ad82c3a0f650a4f9b83f15d1e21568b52e2d03c Mon Sep 17 00:00:00 2001 From: Piyush Jindal Date: Wed, 19 Jun 2024 14:52:43 +0530 Subject: [PATCH 62/62] Minor address change (#251) Co-authored-by: piyushjindal --- blocks/agent-about/agent-about.css | 3 +-- blocks/agent-address/agent-address.js | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/blocks/agent-about/agent-about.css b/blocks/agent-about/agent-about.css index 29fbbb77..96ab9dae 100644 --- a/blocks/agent-about/agent-about.css +++ b/blocks/agent-about/agent-about.css @@ -29,8 +29,7 @@ } .agent-about.block>div.cols-1, -.agent-about.block>div.cols-2, -.agent-about.block>div.cols-3 { +.agent-about.block>div.cols-2 { padding-bottom: 2rem; } diff --git a/blocks/agent-address/agent-address.js b/blocks/agent-address/agent-address.js index 187419f0..4cd12dee 100644 --- a/blocks/agent-address/agent-address.js +++ b/blocks/agent-address/agent-address.js @@ -4,18 +4,18 @@ import { } from '../../scripts/dom-helpers.js'; export default function decorate(block) { - const streetAddress = getMetadata('streetaddress'); - const addressLocality = getMetadata('addresslocality'); - const addressRegion = getMetadata('addressregion'); - const postalCode = getMetadata('postalcode'); + const streetAddress = getMetadata('street-address'); + const city = getMetadata('city'); + const state = getMetadata('state'); + const zip = getMetadata('zip'); const textDiv = div({ class: 'address' }, p('Berkshire Hathaway HomeServices'), p('Commonwealth Real Estate'), p(streetAddress), - p(`${addressLocality}, ${addressRegion} ${postalCode}`), + p(`${city}, ${state} ${zip}`), ); - const text = `${streetAddress}, ${addressLocality}, ${addressRegion} ${postalCode}`; + const text = `${streetAddress}, ${city}, ${state} ${zip}`; const anchor = a({ href: `https://maps.google.com/maps?q=${text}`, target: '_blank' }, 'Directions'); block.replaceChildren(textDiv, anchor);