Skip to content

Commit

Permalink
add validation
Browse files Browse the repository at this point in the history
  • Loading branch information
rrusher committed Feb 3, 2024
1 parent 3b4fdfe commit 0f17040
Show file tree
Hide file tree
Showing 9 changed files with 438 additions and 14 deletions.
18 changes: 12 additions & 6 deletions blocks/contact-form/contact-form.css
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
}
Expand All @@ -58,34 +63,33 @@
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);
}

.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;
Expand Down Expand Up @@ -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 {
Expand Down
221 changes: 221 additions & 0 deletions blocks/contact-form/contact-form.js
Original file line number Diff line number Diff line change
@@ -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 protected].'));
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');

Check warning on line 63 in blocks/contact-form/contact-form.js

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement
return isValid(form);
}

// eslint-disable no-console
const addForm = async (block) => {
const displayValue = block.style.display;
Expand All @@ -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;
};

Expand Down
78 changes: 78 additions & 0 deletions blocks/contact-form/forms/contact-property.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<div class="contact-form">
<h1 id="contact-us">Contact Us</h1>
<div class="company-name"></div>
<div class="company-email"></div>
<div class="company-phone">Direct: </div>
<form id="contactForm" class="form-elements" method="post" action="https://www.commonmoves.com/bin/bhhs/websiteTopicServlet" onsubmit="return submitContactForm(this)">
<div class="message">
<svg class="icon error" role="presentation">
<use xlink:href="/icons/icons.svg#error"></use>
</svg>
<svg class="icon success" role="presentation">
<use xlink:href="/icons/icons.svg#success"></use>
</svg>
<div class="details">
<span></span>
</div>
</div>
<div class="inputs">
<div class="name">
<input name="first_name" type="text" placeholder="First Name*" aria-label="first name"
aria-required="true" autocomplete="given-name">
<input name="last_name" type="text" placeholder="Last Name*" aria-label="last name"
aria-required="true" autocomplete="family-name">
</div>
<div class="contact-info">
<input name="email" type="email" placeholder="Email Address*" aria-label="email address"
aria-required="true" autocomplete="email">
<input name="phone" type="text" placeholder="Phone Number*" aria-label="phone number"
aria-required="true" autocomplete="tel-national">
</div>
<textarea name="comments"
placeholder="Leave a comment or question and we'll get back to you shortly."
autocomplete="off"></textarea>
</div>
<div class="agent">
<div class="question">Are you currently working with an agent?</div>
<div class="agent-check">
<div>
<input name="agentY" type="checkbox" aria-label="checkbox" tabindex="-1" value="" autocomplete="off" checked>
<div class="checkbox">
<svg class="empty">
<use xlink:href="/icons/icons.svg#checkmark"></use>
</svg>
</div>
<span class="label">yes</span>
</div>
<div>
<input name="agentN" type="checkbox" aria-label="checkbox" tabindex="-1" value="" autocomplete="off">
<div class="checkbox">
<svg class="empty">
<use xlink:href="/icons/icons.svg#checkmark"></use>
</svg>
</div>
<span class="label">no</span>
</div>
</div>
<div class="disclaimer"></div>
</div>
<div id="captcha-20285" class="g-recaptcha contact-form-captcha" captcha-render="1">
<div style="width: 304px; height: 78px;">
<iframe title="reCAPTCHA" width="304" height="78" role="presentation" name="a-duze5gwmci0x"
frameborder="0" scrolling="no"
sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation allow-modals allow-popups-to-escape-sandbox allow-storage-access-by-user-activation"
src="https://www.google.com/recaptcha/api2/anchor?ar=2&amp;k=6LebYaYUAAAAAC9SqASljwaF57MpKSvEkwDOzk6l&amp;co=aHR0cHM6Ly93d3cuY29tbW9ubW92ZXMuY29tOjQ0Mw..&amp;hl=en&amp;v=Ya-Cd6PbRI5ktAHEhm9JuKEu&amp;size=normal&amp;cb=sa2502jzdt91"></iframe>
<textarea id="g-recaptcha-response-3" name="g-recaptcha-response"
class="g-recaptcha-response"
style="width: 250px; height: 40px; border: 1px solid rgb(193, 193, 193); margin: 10px 25px; padding: 0px; resize: none; display: none;"></textarea>
</div>
<iframe style="display: none;"></iframe>
</div>
<div class="cta">
<div class="button-container">
<a href="" class="button primary submit">Send</a>
<a href="" class="button cancel">Cancel</a>
</div>
</div>
</form>
</div>
Loading

0 comments on commit 0f17040

Please sign in to comment.