765 lines
22 KiB
JavaScript
765 lines
22 KiB
JavaScript
function smoothScrollToElement(element, offset = 150, duration = 500) {
|
|
const rect = element.getBoundingClientRect();
|
|
const startY = window.pageYOffset || window.scrollY || 0;
|
|
|
|
// ✅ offset inteligente: se o elemento está abaixo mas o offset é grande demais,
|
|
// reduz o offset pra garantir que o targetY fique realmente abaixo do startY.
|
|
let effectiveOffset = offset;
|
|
const minGap = 20; // distância mínima que você quer manter do topo
|
|
|
|
if (rect.top > 0 && rect.top < offset + minGap) {
|
|
effectiveOffset = Math.max(0, rect.top - minGap);
|
|
}
|
|
|
|
let targetY = rect.top + startY - effectiveOffset;
|
|
if (targetY < 0) targetY = 0;
|
|
|
|
const distance = targetY - startY;
|
|
|
|
// ✅ se ainda ficou 0, mas o elemento está abaixo, força um scroll mínimo
|
|
if (distance === 0 && rect.top > 0) {
|
|
targetY = startY + rect.top - minGap;
|
|
}
|
|
|
|
const finalDistance = targetY - startY;
|
|
if (finalDistance === 0) return;
|
|
|
|
const startTime = performance.now();
|
|
const easeOutQuad = (t) => t * (2 - t);
|
|
|
|
function step(currentTime) {
|
|
const elapsed = currentTime - startTime;
|
|
const t = Math.min(1, elapsed / duration);
|
|
const easedT = easeOutQuad(t);
|
|
|
|
window.scrollTo(0, startY + finalDistance * easedT);
|
|
|
|
if (t < 1) requestAnimationFrame(step);
|
|
}
|
|
|
|
requestAnimationFrame(step);
|
|
}
|
|
|
|
function isVisible(el) {
|
|
if (!el) return false;
|
|
const style = window.getComputedStyle(el);
|
|
const rect = el.getBoundingClientRect();
|
|
return (
|
|
style.display !== "none" &&
|
|
style.visibility !== "hidden" &&
|
|
rect.height > 0 &&
|
|
rect.width > 0
|
|
);
|
|
}
|
|
|
|
// puxa o plano selecionado na pag1 para pag2
|
|
function aplicarPlanoSelecionado() {
|
|
const planoData = JSON.parse(sessionStorage.getItem("planoSelecionado"));
|
|
if (!planoData) return;
|
|
|
|
console.log("[LP] Plano recebido:", planoData);
|
|
|
|
// 1. Seleciona tipo (residencial / empresarial)
|
|
if (planoData.tipo === "residencial") {
|
|
selecionarTipoPlano("plano-residencia");
|
|
} else {
|
|
selecionarTipoPlano("plano-empresa");
|
|
}
|
|
|
|
// 2. Seleciona plano
|
|
selecionarPlano(planoData.plano);
|
|
|
|
// 3. (opcional) mostrar mensagem
|
|
setPlanoSelecionadoTexto(planoValueToLabel(planoData.plano));
|
|
}
|
|
|
|
window.addEventListener("load", () => {
|
|
setTimeout(() => {
|
|
aplicarPlanoSelecionado();
|
|
}, 500);
|
|
});
|
|
|
|
function scrollToAssinar({ offset = 250, duration = 500, tries = 40 } = {}) {
|
|
// ✅ seu wrapper real é .form-element-field8
|
|
// Tentamos pegar o botão dentro dele; se não achar, rola até o wrapper mesmo
|
|
const selector =
|
|
'.form-element-field8 button, .form-element-field8 input[type="button"], .form-element-field8 input[type="submit"], .form-element-field';
|
|
|
|
let attempt = 0;
|
|
|
|
function tick() {
|
|
const el = document.querySelector(selector);
|
|
|
|
if (isVisible(el)) {
|
|
smoothScrollToElement(el, offset, duration);
|
|
return;
|
|
}
|
|
|
|
attempt++;
|
|
if (attempt < tries) requestAnimationFrame(tick);
|
|
else
|
|
console.warn(
|
|
"[scroll] Botão de assinar não encontrado/visível. Seletor:",
|
|
selector,
|
|
);
|
|
}
|
|
|
|
requestAnimationFrame(tick);
|
|
}
|
|
|
|
function scrollToPlanosById(id, offset = 220, duration = 500) {
|
|
const el = document.getElementById(id);
|
|
if (!el) {
|
|
console.warn('[scroll] nao encontrei id:', id);
|
|
return;
|
|
}
|
|
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
smoothScrollToElement(el, offset, duration);
|
|
setTimeout(() => smoothScrollToElement(el, offset, duration), 120);
|
|
setTimeout(() => smoothScrollToElement(el, offset, duration), 260);
|
|
});
|
|
});
|
|
}
|
|
|
|
const cepInput = document.querySelector('[name="field6[]"]');
|
|
const numeroInput = document.querySelector('[name="field7[]"]');
|
|
const nomeInput = document.querySelector('[name="field3[]"]');
|
|
const emailInput = document.querySelector('[name="field4"]');
|
|
const telefone = document.querySelector('[name="field5[]"]');
|
|
const logradouro = document.querySelector('[name="field20[]"]');
|
|
const temosBandaLargaTitle = document.querySelector(".form-element-field23");
|
|
const temosBandaLargaBox = document.querySelector(".form-element-field24");
|
|
const naoTemosBandaLargaTitle = document.querySelector(".form-element-field33");
|
|
const errorBox = document.querySelector(".form-element-field32");
|
|
|
|
let temosBandaLarga = false;
|
|
let error = false;
|
|
const dataPayload = {};
|
|
|
|
const digitsOnly = (s) => (s || "").replace(/\D/g, "");
|
|
let debounceT,
|
|
inflight,
|
|
lastKey,
|
|
reqSeq = 0;
|
|
let isProcessing = false;
|
|
let originalBtnText = "";
|
|
|
|
// limpa campo de endereço
|
|
|
|
function limpar() {
|
|
logradouro.value = "";
|
|
// esconder blocos de resultado caso estejam visíveis
|
|
if (temosBandaLargaTitle) temosBandaLargaTitle.style.display = "none";
|
|
if (temosBandaLargaBox) temosBandaLargaBox.style.display = "none";
|
|
if (naoTemosBandaLargaTitle) naoTemosBandaLargaTitle.style.display = "none";
|
|
if (errorBox) errorBox.style.display = "none";
|
|
}
|
|
|
|
function pronto() {
|
|
// preciso que todos os campos estejam preenchidos (Nome, Email, CEP e Número)
|
|
const cep = digitsOnly(cepInput?.value);
|
|
const n = (numeroInput?.value || "").trim();
|
|
const nome = (nomeInput?.value || "").trim();
|
|
const email = (emailInput?.value || "").trim();
|
|
return cep.length === 8 && n && nome && email;
|
|
}
|
|
|
|
// JSON da consulta da API:
|
|
// {
|
|
// bairro: address.bairro,
|
|
// cidade: address.localidade,
|
|
// estado: address.uf,
|
|
// logradouro: address.logradouro,
|
|
// naoDedicado: naoDedicado,
|
|
// dedicado: dedicado,
|
|
// }
|
|
|
|
async function consultarCep() {
|
|
if (!pronto()) {
|
|
limpar();
|
|
lastKey = null;
|
|
error = true;
|
|
return;
|
|
}
|
|
|
|
const cep = digitsOnly(cepInput.value);
|
|
const numero = numeroInput.value.trim();
|
|
const key = `${cep}|${numero}`;
|
|
if (key === lastKey) return; // dedupe
|
|
lastKey = key;
|
|
|
|
// cancela requisição anterior
|
|
if (inflight) inflight.abort();
|
|
inflight = new AbortController();
|
|
|
|
const mySeq = ++reqSeq; // marca ordem da requisição
|
|
|
|
try {
|
|
const res = await fetch("https://api.sothis.com.br/api/viabilidade", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
nome: nomeInput.value,
|
|
email: emailInput.value,
|
|
telefone: telefone.value,
|
|
cep,
|
|
numero,
|
|
}),
|
|
signal: inflight.signal,
|
|
});
|
|
const data = await res.json().catch(() => ({}));
|
|
if (!res.ok) throw new Error(data?.error || `HTTP ${res.status}`);
|
|
|
|
// evita aplicar resposta atrasada
|
|
if (mySeq !== reqSeq) return;
|
|
|
|
error = false; // sucesso
|
|
|
|
// preenche no formulário dados de endereço
|
|
logradouro.value = data.logradouro || "";
|
|
|
|
// armazena resultados em uma variavel global para uso posterior
|
|
temosBandaLarga = data.naoDedicado;
|
|
|
|
dataPayload.bairro = data.bairro;
|
|
dataPayload.cidade = data.cidade;
|
|
dataPayload.estado = data.estado;
|
|
dataPayload.logradouro = data.logradouro;
|
|
dataPayload.naoDedicado = data.naoDedicado;
|
|
dataPayload.dedicado = data.dedicado;
|
|
} catch (e) {
|
|
if (e.name === "AbortError") return;
|
|
if (mySeq !== reqSeq) return;
|
|
limpar();
|
|
|
|
lastKey = null; // permite tentar de novo
|
|
|
|
// TODO: mostrar bloco de erro pro usuário
|
|
console.error("Erro ao consultar viabilidade:", e);
|
|
error = true;
|
|
} finally {
|
|
if (mySeq === reqSeq) {
|
|
inflight = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// CACHE
|
|
|
|
(function () {
|
|
function initFormCache() {
|
|
const form = document.querySelector(".fc-form-33"); // ajuste se tiver mais de um formulário
|
|
|
|
console.log("FormCraft cache: init chamado. Form encontrado?", !!form);
|
|
if (!form) return;
|
|
|
|
form.addEventListener("submit", function () {
|
|
console.log("FormCraft cache: submit disparou");
|
|
|
|
const data = {};
|
|
const fields = form.querySelectorAll(
|
|
"input[name], select[name], textarea[name]",
|
|
);
|
|
|
|
// Além dos campos do formulário, preciso salvar o payload da consulta de viabilidade
|
|
data["viabilidade"] = dataPayload;
|
|
|
|
fields.forEach(function (field) {
|
|
const name = field.name;
|
|
if (!name) return;
|
|
|
|
if (field.type === "checkbox") {
|
|
if (!data[name]) data[name] = [];
|
|
if (field.checked) data[name].push(field.value);
|
|
} else if (field.type === "radio") {
|
|
if (field.checked) {
|
|
data[name] = field.value;
|
|
}
|
|
} else {
|
|
data[name] = field.value;
|
|
}
|
|
});
|
|
|
|
sessionStorage.setItem("formcraft_step1", JSON.stringify(data));
|
|
});
|
|
}
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", initFormCache);
|
|
} else {
|
|
initFormCache();
|
|
}
|
|
})();
|
|
|
|
// Função para consultar viabilidade ao clicar no botão
|
|
document.addEventListener("click", function (e) {
|
|
const btn = e.target.closest("#consulta-viabilidade");
|
|
if (!btn) return;
|
|
e.preventDefault();
|
|
if (isProcessing) return;
|
|
isProcessing = true;
|
|
originalBtnText = btn.textContent;
|
|
btn.textContent = "Processando...";
|
|
btn.disabled = true;
|
|
consultarCep().finally(() => {
|
|
showResultField();
|
|
// scroll para o bloco de resultado visível - bloqueado
|
|
setTimeout(() => {
|
|
const blocoVisivel = [
|
|
document.querySelector(".form-element-field23"),
|
|
document.querySelector(".form-element-field33"),
|
|
document.querySelector(".form-element-field32"),
|
|
document.querySelector(".form-element-field36"),
|
|
].find(el => el && el.style.display !== "none");
|
|
isProcessing = false;
|
|
btn.textContent = originalBtnText;
|
|
btn.disabled = false;
|
|
}, 2200);
|
|
});
|
|
});
|
|
|
|
let viabilidadeAprovada = false;
|
|
|
|
// FUNÇÃO BOTÃO CONSULTAR VIABILIDADE
|
|
function showResultField() {
|
|
console.log("Mostrando resultado de viabilidade.", { temosBandaLarga, error });
|
|
|
|
const temosBandaLargaTitle = document.querySelector(".form-element-field23");
|
|
const temosBandaLargaBox = document.querySelector(".form-element-field24");
|
|
const naoTemosBandaLargaTitle = document.querySelector(".form-element-field33");
|
|
const errorBox = document.querySelector(".form-element-field32");
|
|
const camposNaoPreenchidos = document.querySelector(".form-element-field36");
|
|
const btnAssinar = document.querySelector(
|
|
'.form-element-field8 button, .form-element-field8 input[type="submit"]'
|
|
);
|
|
|
|
// 1) esconde todos os blocos e bloqueia botão por padrão
|
|
if (temosBandaLargaTitle) temosBandaLargaTitle.style.display = "none";
|
|
if (temosBandaLargaBox) temosBandaLargaBox.style.display = "none";
|
|
if (naoTemosBandaLargaTitle) naoTemosBandaLargaTitle.style.display = "none";
|
|
if (errorBox) errorBox.style.display = "none";
|
|
if (camposNaoPreenchidos) camposNaoPreenchidos.style.display = "none";
|
|
|
|
viabilidadeAprovada = false; // bloqueia por padrão
|
|
if (btnAssinar) {
|
|
btnAssinar.disabled = true;
|
|
btnAssinar.style.opacity = "0.5";
|
|
btnAssinar.style.cursor = "not-allowed";
|
|
btnAssinar.title = "Consulte a viabilidade antes de continuar";
|
|
}
|
|
|
|
// 2) campos incompletos
|
|
if (!pronto()) {
|
|
if (camposNaoPreenchidos) camposNaoPreenchidos.style.display = "block";
|
|
return;
|
|
}
|
|
|
|
// 3) erro de API
|
|
if (error) {
|
|
if (errorBox) errorBox.style.display = "block";
|
|
return;
|
|
}
|
|
|
|
// 4) SEM cobertura
|
|
if (!temosBandaLarga) {
|
|
if (naoTemosBandaLargaTitle) naoTemosBandaLargaTitle.style.display = "block";
|
|
return; // botão continua bloqueado
|
|
}
|
|
|
|
// 5) TEM cobertura — libera o botão
|
|
viabilidadeAprovada = true;
|
|
if (btnAssinar) {
|
|
btnAssinar.disabled = false;
|
|
btnAssinar.style.opacity = "";
|
|
btnAssinar.style.cursor = "";
|
|
btnAssinar.title = "";
|
|
}
|
|
|
|
if (temosBandaLargaTitle) temosBandaLargaTitle.style.display = "block";
|
|
|
|
const planoJaEscolhido = sessionStorage.getItem("planoSelecionado");
|
|
if (planoJaEscolhido) {
|
|
if (temosBandaLargaBox) temosBandaLargaBox.style.display = "none";
|
|
setTimeout(() => scrollToAssinar({ offset: 250, duration: 500 }), 1500);
|
|
} else {
|
|
if (temosBandaLargaBox) temosBandaLargaBox.style.display = "block";
|
|
}
|
|
}
|
|
|
|
function bloquearSubmitSemViabilidade() {
|
|
const MAX_TRIES = 40;
|
|
const INTERVAL = 300;
|
|
let tries = 0;
|
|
|
|
function init() {
|
|
const form = document.querySelector(".fc-form-33");
|
|
if (!form) {
|
|
if (++tries <= MAX_TRIES) setTimeout(init, INTERVAL);
|
|
return;
|
|
}
|
|
|
|
form.addEventListener("submit", function (e) {
|
|
if (!viabilidadeAprovada) {
|
|
e.preventDefault();
|
|
e.stopImmediatePropagation();
|
|
|
|
// mostra mensagem e rola até ela
|
|
const camposNaoPreenchidos = document.querySelector(".form-element-field36");
|
|
const errorBox = document.querySelector(".form-element-field32");
|
|
|
|
// se nenhum bloco de erro está visível, mostra o de campos não preenchidos
|
|
const algumErroVisivel = [
|
|
document.querySelector(".form-element-field33"),
|
|
document.querySelector(".form-element-field32"),
|
|
document.querySelector(".form-element-field36"),
|
|
].some(el => el && el.style.display !== "none");
|
|
|
|
if (!algumErroVisivel && camposNaoPreenchidos) {
|
|
camposNaoPreenchidos.style.display = "block";
|
|
}
|
|
|
|
// rola até o bloco de erro visível
|
|
const blocoErro = [
|
|
document.querySelector(".form-element-field33"),
|
|
document.querySelector(".form-element-field32"),
|
|
document.querySelector(".form-element-field36"),
|
|
].find(el => el && el.style.display !== "none");
|
|
|
|
if (blocoErro) smoothScrollToElement(blocoErro, 150, 500);
|
|
|
|
console.warn("[viabilidade] Submit bloqueado: viabilidade não aprovada.");
|
|
}
|
|
}, true); // capture=true garante que roda antes do FormCraft
|
|
}
|
|
init();
|
|
};
|
|
|
|
function setPlanoSelecionadoTexto(planoLabel) {
|
|
const el = document.getElementById('plano-selecionado-msg');
|
|
if (!el) {
|
|
console.warn('[plano] #plano-selecionado-msg não encontrado');
|
|
return;
|
|
}
|
|
el.textContent = `Você selecionou o plano de: ${planoLabel}`;
|
|
}
|
|
|
|
function planoValueToLabel(value) {
|
|
const map = {
|
|
"100-mega": "100 Mega",
|
|
"200-mega": "200 Mega",
|
|
"500-mega": "500 Mega",
|
|
"700-mega": "700 Mega",
|
|
"1-gb": "1 Giga",
|
|
};
|
|
return map[value] || value;
|
|
}
|
|
|
|
// função genérica para selecionar o tipo de plano no FIELD30
|
|
function selecionarTipoPlano(tipo) {
|
|
// procura o input do FormCraft pelo name e pelo value
|
|
const input = document.querySelector(
|
|
'input[name="field30[]"][value="' + tipo + '"]',
|
|
);
|
|
|
|
if (!input) {
|
|
console.warn('Campo field30 com value="' + tipo + '" não encontrado');
|
|
return;
|
|
}
|
|
|
|
// marca o radio/checkbox
|
|
input.checked = true;
|
|
|
|
// dispara o evento "change" para o FormCraft reagir (lógica condicional, etc.)
|
|
const evt = new Event("change", { bubbles: true });
|
|
input.dispatchEvent(evt);
|
|
}
|
|
|
|
function selecionarPlano(plano) {
|
|
const input = document.querySelector(
|
|
'input[name="field31[]"][value="' + plano + '"]',
|
|
);
|
|
console.log("[plano] procurando field31", plano, "→", input);
|
|
|
|
if (!input) {
|
|
console.warn(
|
|
'[plano] Campo field31 com value="' + plano + '" não encontrado',
|
|
);
|
|
return;
|
|
}
|
|
|
|
input.checked = true;
|
|
const evt = new Event("change", { bubbles: true });
|
|
input.dispatchEvent(evt);
|
|
setPlanoSelecionadoTexto(planoValueToLabel(plano));
|
|
console.log("[plano] marcado e disparado change");
|
|
}
|
|
|
|
// listeners dos botões dos cards
|
|
document.addEventListener("click", function (e) {
|
|
// botão plano residência
|
|
const btnResidencia = e.target.closest("#btn-plano-residencia");
|
|
if (btnResidencia) {
|
|
e.preventDefault();
|
|
selecionarTipoPlano("plano-residencia");
|
|
|
|
document.querySelectorAll(".seller-card__container").forEach((card) => {
|
|
card.classList.remove("is-selected");
|
|
});
|
|
const card = btnResidencia.closest(".seller-card__container");
|
|
if (card) card.classList.add("is-selected");
|
|
|
|
scrollToPlanosById("planos-residencial", 220, 500);
|
|
|
|
return;
|
|
}
|
|
|
|
// botão plano empresa
|
|
const btnEmpresa = e.target.closest("#btn-plano-empresa");
|
|
if (btnEmpresa) {
|
|
e.preventDefault();
|
|
selecionarTipoPlano("plano-empresa");
|
|
|
|
document.querySelectorAll(".seller-card__container").forEach((card) => {
|
|
card.classList.remove("is-selected");
|
|
});
|
|
const card = btnEmpresa.closest(".seller-card__container");
|
|
if (card) card.classList.add("is-selected");
|
|
|
|
scrollToPlanosById("planos-empresa", 220, 500);
|
|
|
|
return;
|
|
}
|
|
|
|
const btnPlano100Mega = e.target.closest("#btn-plano-100-mega");
|
|
if (btnPlano100Mega) {
|
|
e.preventDefault();
|
|
selecionarPlano("100-mega");
|
|
|
|
document
|
|
.querySelectorAll(".seller-card__container, .out-card__container")
|
|
.forEach((el) => {
|
|
el.classList.remove("is-selected");
|
|
});
|
|
|
|
const card = btnPlano100Mega.closest(".seller-card__container");
|
|
if (card) card.classList.add("is-selected");
|
|
scrollToAssinar({ offset: 500, duration: 500 });
|
|
|
|
return;
|
|
}
|
|
|
|
const btnPlano200Mega = e.target.closest("#btn-plano-200-mega");
|
|
if (btnPlano200Mega) {
|
|
e.preventDefault();
|
|
selecionarPlano("200-mega");
|
|
|
|
document
|
|
.querySelectorAll(".seller-card__container, .out-card__container")
|
|
.forEach((el) => {
|
|
el.classList.remove("is-selected");
|
|
});
|
|
|
|
const card = btnPlano200Mega.closest(".seller-card__container");
|
|
if (card) card.classList.add("is-selected");
|
|
scrollToAssinar({ offset: 500, duration: 500 });
|
|
|
|
return;
|
|
}
|
|
|
|
const btnPlano500Mega = e.target.closest("#btn-plano-500-mega");
|
|
if (btnPlano500Mega) {
|
|
e.preventDefault();
|
|
selecionarPlano("500-mega");
|
|
|
|
document
|
|
.querySelectorAll(".seller-card__container, .out-card__container")
|
|
.forEach((el) => {
|
|
el.classList.remove("is-selected");
|
|
});
|
|
|
|
const card = btnPlano500Mega.closest(".out-card__container");
|
|
if (card) card.classList.add("is-selected");
|
|
scrollToAssinar({ offset: 500, duration: 500 });
|
|
|
|
return;
|
|
}
|
|
|
|
const btnPlano700Mega = e.target.closest("#btn-plano-700-mega");
|
|
if (btnPlano700Mega) {
|
|
e.preventDefault();
|
|
selecionarPlano("700-mega");
|
|
|
|
document
|
|
.querySelectorAll(".seller-card__container, .out-card__container")
|
|
.forEach((el) => {
|
|
el.classList.remove("is-selected");
|
|
});
|
|
|
|
const card = btnPlano700Mega.closest(".seller-card__container");
|
|
if (card) card.classList.add("is-selected");
|
|
scrollToAssinar({ offset: 500, duration: 500 });
|
|
|
|
return;
|
|
}
|
|
|
|
const btnPlano1Gb = e.target.closest("#btn-plano-1-gb");
|
|
if (btnPlano1Gb) {
|
|
e.preventDefault();
|
|
selecionarPlano("1-gb");
|
|
|
|
document
|
|
.querySelectorAll(".seller-card__container, .out-card__container")
|
|
.forEach((el) => {
|
|
el.classList.remove("is-selected");
|
|
});
|
|
|
|
const card = btnPlano1Gb.closest(".seller-card__container");
|
|
if (card) card.classList.add("is-selected");
|
|
scrollToAssinar({ offset: 500, duration: 500 });
|
|
|
|
return;
|
|
}
|
|
});
|
|
|
|
class FormCraftCEP {
|
|
constructor() {
|
|
// Inicializa a classe e define os conjuntos de campos
|
|
this.fieldSets = [
|
|
{
|
|
cepField: document.querySelector('[name="field6[]"]'),
|
|
ruaField: document.querySelector('[name="field20[]"]'),
|
|
},
|
|
];
|
|
|
|
this.fieldSets.forEach((set, index) => {
|
|
this.initializeFields(set, index);
|
|
});
|
|
}
|
|
|
|
initializeFields(set, index) {
|
|
const missingFields = [];
|
|
if (!set.cepField) missingFields.push("CEP");
|
|
if (!set.ruaField) missingFields.push("Rua");
|
|
|
|
if (missingFields.length > 0) {
|
|
console.error(
|
|
`Erro: Não foi possível localizar os seguintes campos do conjunto ${index + 1}: ${missingFields.join(", ")}`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Adicionar evento ao campo de CEP
|
|
set.cepField.addEventListener("input", () => {
|
|
const valor = this.getCEPValue(set.cepField);
|
|
if (valor.length === 8) {
|
|
this.verificaCEP(valor, set);
|
|
}
|
|
});
|
|
}
|
|
|
|
getCEPValue(cepField) {
|
|
const rawValue = cepField.value.trim();
|
|
return rawValue.replace(/\D/g, "");
|
|
}
|
|
|
|
limpaFormularioCEP(set) {
|
|
set.ruaField.value = "";
|
|
}
|
|
|
|
meuCallback(conteudo, set) {
|
|
if (!("erro" in conteudo)) {
|
|
set.ruaField.value = conteudo.logradouro || "";
|
|
} else {
|
|
this.limpaFormularioCEP(set);
|
|
alert("CEP não encontrado.");
|
|
}
|
|
}
|
|
|
|
verificaCEP(valor, set) {
|
|
if (/^[0-9]{8}$/.test(valor)) {
|
|
this.pesquisaCEP(valor, set);
|
|
} else {
|
|
console.warn("CEP inválido:", valor);
|
|
}
|
|
}
|
|
|
|
pesquisaCEP(cep, set) {
|
|
set.ruaField.value = "...";
|
|
|
|
let script = document.createElement("script");
|
|
script.src = `https://viacep.com.br/ws/${cep}/json/?callback=formCraftCEPInstance.createCallback("${cep}", "${set.cepField.name}")`;
|
|
document.body.appendChild(script);
|
|
}
|
|
|
|
createCallback(cep, cepFieldName) {
|
|
const set = this.fieldSets.find(
|
|
(set) => set.cepField.name === cepFieldName,
|
|
);
|
|
return function (conteudo) {
|
|
formCraftCEPInstance.meuCallback(conteudo, set);
|
|
};
|
|
}
|
|
}
|
|
|
|
// Criar instância da classe e torná-la global
|
|
window.formCraftCEPInstance = new FormCraftCEP();
|
|
|
|
|
|
// Garantir que tela scrolle em caso de campo obrigatório sem preenchimento
|
|
|
|
(function scrollToFirstInvalidOnSubmit() {
|
|
const form = document.querySelector(".fc-form-33");
|
|
if (!form) return;
|
|
|
|
const OFFSET = 180; // ajuste conforme seu header/spacing
|
|
|
|
function scrollToInvalid() {
|
|
// pega o primeiro inválido nativo do HTML5
|
|
const invalid =
|
|
form.querySelector(":invalid") ||
|
|
form.querySelector("[aria-invalid='true']") ||
|
|
form.querySelector(".fc-error, .error, .has-error input, .has-error textarea, .has-error select");
|
|
|
|
if (!invalid) return;
|
|
|
|
// se for label/div, tenta ir no input dentro
|
|
const target = invalid.matches("input, select, textarea")
|
|
? invalid
|
|
: invalid.querySelector?.("input, select, textarea") || invalid;
|
|
|
|
// garante foco (ajuda o FormCraft e o usuário)
|
|
if (target.focus) target.focus({ preventScroll: true });
|
|
|
|
// dá um “respiro” pro layout e então rola certinho
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
smoothScrollToElement(target, OFFSET, 450);
|
|
});
|
|
});
|
|
}
|
|
|
|
// captura: roda antes dos handlers internos finalizarem
|
|
form.addEventListener(
|
|
"submit",
|
|
() => {
|
|
// espera o FormCraft marcar erros
|
|
setTimeout(scrollToInvalid, 50);
|
|
setTimeout(scrollToInvalid, 200);
|
|
},
|
|
true
|
|
);
|
|
|
|
// fallback: se o submit nem dispara, mas o clique foi no submit
|
|
form.addEventListener(
|
|
"click",
|
|
(e) => {
|
|
const btn = e.target.closest('button[type="submit"], input[type="submit"]');
|
|
if (!btn) return;
|
|
setTimeout(scrollToInvalid, 50);
|
|
setTimeout(scrollToInvalid, 200);
|
|
},
|
|
true
|
|
);
|
|
})(); |