Pular para o conteúdo

Lead e WhatsApp

Só o modelo captura tem formulário de lead. Os outros modelos não recebem dados pelo site: o contato é pelo WhatsApp e, nos modelos que vendem, a compra vai para o checkout. Para conhecer os modelos, veja Modelos.

O formulário fica na primeira dobra, no lugar do Hero com a variante split-form. Ele tem:

  • Nome e Telefone com DDD, obrigatórios. O telefone precisa ter pelo menos 10 dígitos, sem contar o +55 (ui/lead/phone.ts).
  • E-mail, só quando o formulário recebe fields: ["email"]. O modelo captura não recebe, então o campo não aparece.
  • Consentimento, obrigatório, com o link «Política de privacidade» para consent.privacyHref, dentro de hero.form.consent em src/data/content.ts.
  • Botão de envio, com o texto de submitLabel em src/data/content.ts.

O envio com JavaScript é um POST em JSON para lead.endpoint, com 10 segundos de limite. Só conta como enviado uma resposta 2xx. Uma resposta de redirecionamento, uma falha de rede ou o limite estourado mostram o erro.

Quando o envio dá certo, o formulário some e aparece a mensagem de lead.successMessage, com o foco nela. Quando dá errado, aparece «Não foi possível enviar agora.» com o botão «Chamar no WhatsApp». O visitante pode tentar de novo.

O corpo do POST tem os campos name, phone e page (o caminho da página). email só vai quando o campo existe e foi preenchido, e os parâmetros utm_* só quando a URL os traz. O consentimento não vai no JSON.

Sem JavaScript, o navegador envia um POST comum, no formato de formulário, com os campos do formulário: name, phone, email (se existir), consent e company. O endpoint precisa aceitar os dois formatos.

Quando o endpoint fica em outro domínio, o navegador faz uma pré-requisição OPTIONS antes do POST, porque o envio usa content-type: application/json. O endpoint precisa responder a ela e liberar o domínio do site e o cabeçalho content-type. O endpoint também precisa responder 2xx direto: um redirecionamento 3xx conta como erro.

Um campo oculto chamado company serve de isca para robôs. Com JavaScript, se ele vier preenchido, o envio finge sucesso e nada sai do navegador. Sem JavaScript, ele vai no POST, então o destino deve descartar qualquer lead com company preenchido.

A medição envia o evento generate_lead, com method: "form", só quando o envio dá certo. O evento não leva nome, telefone nem e-mail.

O endereço fica em lead.endpoint, em src/data/site.ts. Ele precisa começar com https://: um endereço sem isso faz o build parar com a mensagem de correção. Um pending(...) nesse campo também para o build, porque o plain o transforma em undefined.

Enquanto o endereço tiver o marcador fictitious(...), o slash check pede slash approve. Um endereço sem marcador, que é o que a agência escreve, não entra na lista do approve.

src/data/site.ts
import { fictitious } from "./origin";
import type { FooterBusiness, SeoInput } from "../ui";
import { theme } from "../theme";
/** Site data, read by the CLI and by the pages. Marked values go through the content gate; use `plain(site)` to consume them. */
export const site = {
name: "Aurora Odonto",
baseUrl: "https://exemplo.com.br",
lang: "pt-BR",
whatsapp: fictitious({ number: "11999999999", message: "Olá! Quero marcar minha avaliação gratuita na Aurora Odonto." }),
lead: {
endpoint: "https://leads.exemplo.com.br/aurora",
successMessage: "Recebemos seus dados. Nossa equipe chama você no WhatsApp para marcar a avaliação.",
},
gtmId: fictitious("GTM-TEST000"),
business: fictitious<FooterBusiness>({
name: "Aurora Odontologia Ltda.",
legalId: "12.345.678/0001-90",
email: "contato@exemplo.com.br",
hours: "Seg a Sáb, 8h às 19h",
}),
} as const;
/** SEO and share data of the page (`jsonLd` and `extra` are added by routes.ts). */
export const seo: SeoInput = {
title: "Aurora Odonto: avaliação odontológica gratuita com dentista",
description: "Deixe seu contato e marque uma avaliação gratuita com dentista: diagnóstico, plano de tratamento e orçamento sem compromisso.",
share: { image: "hero.jpg", alt: "Dentista da Aurora Odonto conversando com uma paciente na clínica" },
favicon: "/favicon.svg",
themeColor: theme.tones.light.bg,
};

O exemplo usa fictitious de src/data/origin.ts nos campos que ainda são de exemplo, e os tipos FooterBusiness e SeoInput de src/ui/ (exportados por src/ui/index.ts). O tema vem de src/theme.ts. Este exemplo troca o endereço de exemplo pelo da agência, sem marcador, e tira o marcador do texto de sucesso.

O número e a mensagem ficam em site.whatsapp, em src/data/site.ts. O número precisa ter de 10 a 13 dígitos, com DDD. Com 10 ou 11 dígitos, o código do país 55 é acrescentado; com 12 ou 13, o número é usado como está. Fora dessa faixa, a geração do link lança o erro «WhatsApp inválido», e o build para. A mensagem entra no link já codificada.

Onde o WhatsApp aparece:

  • Botão do cabeçalho, nos modelos que têm cabeçalho (todos, menos vendas). O texto do botão muda por modelo: no captura é o WhatsApp; em produto, «Testar grátis», que leva ao checkout. Abaixo de 768 px, com o menu funcionando (com JS), o botão do cabeçalho sai da barra. A regra é do componente Header, vale para todo modelo e não depende do tipo do botão.
  • Botão flutuante «Falar no WhatsApp», no canto inferior direito. Ele está em vendas, produto, evento e lancamento. O modelo captura não tem botão flutuante.
  • Botões de CTA de tipo WhatsApp, como a faixa final e o cartão de contato.

Os botões de WhatsApp registram o evento generate_lead, usado para medir contatos.