Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions assets/js/clarity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** Microsoft Claity Script **/

(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "uwj81e7e55");
156 changes: 129 additions & 27 deletions assets/js/component.recaptch-helper.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
// recaptcha-per-form.strict.js
// Only affects forms that CONTAIN a .g-recaptcha element.

(function () {
const RECAPTCHA_SELECTOR = '.g-recaptcha';
const RESPONSE_SELECTOR = 'textarea.g-recaptcha-response';
const RESPONSE_SELECTOR = 'textarea.g-recaptcha-response';
const POLL_MS = 400;

// Map<form, { button: HTMLElement|null, lastSolved: boolean, pollId: number|null }>
const PUBLIC_EMAIL_PROVIDERS = [
'gmail',
'hotmail',
'outlook',
'live',
'yahoo',
'icloud',
'aol',
'gmx',
'gg',
'zoho'
];

function isPublicEmail(email) {
if (!email || !email.includes('@')) return false;

const domain = email.split('@')[1].toLowerCase();

return PUBLIC_EMAIL_PROVIDERS.some(provider =>
domain.startsWith(provider + '.')
);
}

const formState = new Map();

const disableBtn = (btn) => {
Expand All @@ -21,20 +40,94 @@
btn.removeAttribute('disabled');
};

// True if any reCAPTCHA response textarea in the form has a non-empty value
function formCaptchaSolved(form) {
const responses = form.querySelectorAll(RESPONSE_SELECTOR);

for (const ta of responses) {
const val = (ta && typeof ta.value === 'string') ? ta.value.trim() : '';
const val =
ta && typeof ta.value === 'string'
? ta.value.trim()
: '';

if (val.length > 0) return true;
}

return false;
}

function validateCompanyEmail(form) {
const validationDiv = form.querySelector('[data-company-email-validation="true"]');
if (!validationDiv) return true;

const emailInput = validationDiv.querySelector('input[type="email"]');
const errorEl = form.querySelector('#company-email_error');
const inputWrapper = validationDiv.querySelector('.form_input');

if (!emailInput) return true;

const emailValue = emailInput.value.trim();
const invalid = isPublicEmail(emailValue);

if (invalid) {
if (errorEl) errorEl.style.display = 'block';

if (inputWrapper) {
inputWrapper.classList.add('is-wrong-email');
}

return false;
} else {
if (errorEl) errorEl.style.display = 'none';

if (inputWrapper) {
inputWrapper.classList.remove('is-wrong-email');
}

return true;
}
}

function updateSubmitState(form) {
const state = formState.get(form);
if (!state) return;

const captchaSolved = formCaptchaSolved(form);
const emailValid = validateCompanyEmail(form);

if (captchaSolved && emailValid) {
enableBtn(state.button);
} else {
disableBtn(state.button);
}
}

function wireEmailValidation(form) {
const validationDiv = form.querySelector('[data-company-email-validation="true"]');
if (!validationDiv) return;

const emailInput = validationDiv.querySelector('input[type="email"]');
if (!emailInput) return;

if (emailInput.dataset._company_validation === '1') return;

emailInput.dataset._company_validation = '1';

emailInput.addEventListener('blur', () => {
updateSubmitState(form);
});
}

function wireSubmitGuard(form) {
if (form.dataset._rc_guard === '1') return;

form.dataset._rc_guard = '1';

form.addEventListener('submit', (e) => {
if (!validateCompanyEmail(form)) {
e.preventDefault();
return;
}

if (!formCaptchaSolved(form)) {
e.preventDefault();
const state = formState.get(form);
Expand All @@ -46,21 +139,20 @@
function startPoll(form) {
const state = formState.get(form);
if (!state) return;

if (state.pollId) clearInterval(state.pollId);

const evaluate = () => {
const solved = formCaptchaSolved(form);

if (solved !== state.lastSolved) {
state.lastSolved = solved;
if (solved) enableBtn(state.button);
else disableBtn(state.button);
updateSubmitState(form);
}
};

// Initial state & toggle
state.lastSolved = formCaptchaSolved(form);
if (state.lastSolved) enableBtn(state.button);
else disableBtn(state.button);
updateSubmitState(form);

state.pollId = setInterval(evaluate, POLL_MS);
}
Expand All @@ -69,54 +161,65 @@
const form = container.closest('form');
if (!form || formState.has(form)) return;

const button = form.querySelector('button[type="submit"], input[type="submit"]');
formState.set(form, { button, lastSolved: false, pollId: null });
const button = form.querySelector(
'button[type="submit"], input[type="submit"]'
);

formState.set(form, {
button,
lastSolved: false,
pollId: null
});

wireSubmitGuard(form);
wireEmailValidation(form);
startPoll(form);
}

function scanRecaptchaContainers() {
document.querySelectorAll(RECAPTCHA_SELECTOR).forEach(wireFormForRecaptchaContainer);
document
.querySelectorAll(RECAPTCHA_SELECTOR)
.forEach(wireFormForRecaptchaContainer);
}

function init() {
scanRecaptchaContainers();

// Watch only for new .g-recaptcha nodes (not all forms)
const mo = new MutationObserver((muts) => {
for (const m of muts) {
if (m.type === 'childList' && m.addedNodes?.length) {
m.addedNodes.forEach((n) => {
if (n.nodeType !== 1) return;
if (n.matches?.(RECAPTCHA_SELECTOR)) wireFormForRecaptchaContainer(n);
else {

if (n.matches?.(RECAPTCHA_SELECTOR)) {
wireFormForRecaptchaContainer(n);
} else {
const found = n.querySelector?.(RECAPTCHA_SELECTOR);
if (found) wireFormForRecaptchaContainer(found);
}
});
}
}
});
mo.observe(document.documentElement, { childList: true, subtree: true });

mo.observe(document.documentElement, {
childList: true,
subtree: true
});
}

// Global callbacks for data-callback / data-expired-callback in markup
window.onCaptchaSuccess = function () {
formState.forEach((state, form) => {
if (form.querySelector(RECAPTCHA_SELECTOR)) {
if (formCaptchaSolved(form)) enableBtn(state.button);
}
updateSubmitState(form);
});
};

window.onCaptchaExpired = function () {
formState.forEach((state, form) => {
if (!formCaptchaSolved(form)) disableBtn(state.button);
formState.forEach((state) => {
disableBtn(state.button);
});
};

// Optional manual refresh if forms/captchas are injected dynamically
window.refreshRecaptchaPerFormStrict = function () {
scanRecaptchaContainers();
};
Expand All @@ -127,4 +230,3 @@
init();
}
})();