BandaLarga/teste/FormViabilidade/viabilidade.js

765 lines
22 KiB
JavaScript
Raw Normal View History

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
);
}
2026-04-17 15:59:08 -03:00
// 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();
2026-04-17 15:59:08 -03:00
// scroll para o bloco de resultado visível - bloqueado
setTimeout(() => {
2026-04-17 15:59:08 -03:00
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;
2026-04-17 15:59:08 -03:00
}, 2200);
});
});
2026-04-17 15:59:08 -03:00
let viabilidadeAprovada = false;
// FUNÇÃO BOTÃO CONSULTAR VIABILIDADE
function showResultField() {
2026-04-17 15:59:08 -03:00
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"]'
);
2026-04-17 15:59:08 -03:00
// 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";
2026-04-17 15:59:08 -03:00
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;
}
2026-04-17 15:59:08 -03:00
// 3) erro de API
if (error) {
if (errorBox) errorBox.style.display = "block";
return;
}
2026-04-17 15:59:08 -03:00
// 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 {
2026-04-17 15:59:08 -03:00
if (temosBandaLargaBox) temosBandaLargaBox.style.display = "block";
}
}
2026-04-17 15:59:08 -03:00
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() {
2026-04-17 15:59:08 -03:00
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
);
})();