/* ============================================================ client.jsx — Tela do CLIENTE (link provisório ?p=&t=&d=) Termo → assinatura → pagamento PIX → conclusão. Sem login, sem sidebar, sem config da conta. ============================================================ */ function ClientTerm({ deal }) { const c = deal.cliente || {}; const metodo = (window.PAYMENT_METHODS.find(m => m.id === deal.pagamento) || {}).nome || '—'; const hoje = new Date().toLocaleDateString('pt-BR', { day: '2-digit', month: 'long', year: 'numeric' }); return (
Termo de Contratação {hoje}
Contratante
{c.nome || '—'}{c.empresa ? ` · ${c.empresa}` : ''}
{c.doc || '—'}
{c.email || '—'}
{c.whatsapp || '—'}
{c.endereco &&
{c.endereco}
}
Serviços contratados {(deal.items || []).map(it => ( ))} {deal.recorrente > 0 && }
ServiçoPrazoValor
{it.nome}{it.obs ? — {it.obs} : ''} {it.prazo} {fmtBRL(it.preco)}
Recorrente mensal{fmtBRL(deal.recorrente)}
Valor total{fmtBRL(deal.total)}
Forma de pagamento
{metodo}
{deal.obsGeral &&
Observações
{deal.obsGeral}
}

Declaro estar de acordo com os serviços, valores e condições comerciais apresentados pela {window.brandName()}, autorizando a continuidade do processo de contratação.

); } function ClientPayment({ total, onPaid, busy }) { const [copied, setCopied] = React.useState(false); const pixCfg = window.PIX_DEFAULT; const payload = window.buildPixPayload({ ...pixCfg, valor: total }); const hoje = new Date().toLocaleDateString('pt-BR'); const copy = () => { navigator.clipboard?.writeText(payload); setCopied(true); setTimeout(() => setCopied(false), 1800); }; return (
Aceite registrado

Pagamento via PIX

Escaneie o QR Code ou use o PIX copia e cola para concluir.

Valor a pagar {fmtBRL(total)}
Recebedor{pixCfg.nome}
Chave PIX{pixCfg.chave}
Data{hoje}
PIX copia e cola
{payload}
); } function ClientView({ params }) { const { p: id, t: token, d: enc, c } = params; const code = c || token || ''; const [deal, setDeal] = React.useState(null); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(''); const [phase, setPhase] = React.useState('term'); // term | pay | done const [aceito, setAceito] = React.useState(false); const [nome, setNome] = React.useState(''); const [cpf, setCpf] = React.useState(''); const [assinaturaImg, setAssinaturaImg] = React.useState(null); const [busy, setBusy] = React.useState(false); const [warn, setWarn] = React.useState(''); const [diag, setDiag] = React.useState(null); const [source, setSource] = React.useState(null); // 'cloud' | 'link' const [syncErr, setSyncErr] = React.useState(''); // erro do client_set_status, se houver const scrollRef = React.useRef(null); const hasData = (d) => !!(d && d.cliente && (d.cliente.nome || (d.items && d.items.length))); /* carga inicial: tenta nuvem (RPC) e cai no payload codificado; usa a fonte que tiver dados */ React.useEffect(() => { (async () => { const info = { mode: window.crmStore.mode, hasCloud: !!window.crmStore.hasCloud, id: id || '(curto)', token: code ? 'ok' : '(vazio)', encLen: (enc || '').length, cloud: '—', encOk: false }; let cloudDeal = null, encDeal = null; if (window.crmStore.hasCloud && code) { try { cloudDeal = await window.crmStore.clientGet(code, code); info.cloud = hasData(cloudDeal) ? 'ok' : 'vazio'; } catch (e) { info.cloud = 'erro: ' + ((e && e.message) || e); } } else { info.cloud = 'não tentado'; } if (enc) { encDeal = (typeof window.decodeShare === 'function') ? window.decodeShare(enc) : (function () { try { let s = String(enc).replace(/ /g, '+').replace(/-/g, '+').replace(/_/g, '/'); while (s.length % 4) s += '='; return JSON.parse(decodeURIComponent(escape(atob(s)))); } catch (e) { return null; } })(); info.encOk = hasData(encDeal); } console.log('[D Digital CRM] diagnóstico do link:', info, { cloudDeal, encDeal }); // escolhe a melhor fonte: a que efetivamente tem dados (nuvem tem prioridade) const fromCloud = hasData(cloudDeal); const d = fromCloud ? cloudDeal : (hasData(encDeal) ? encDeal : (cloudDeal || encDeal)); if (!hasData(d)) { setDiag(info); setError('Não foi possível carregar os dados desta proposta.'); setLoading(false); return; } setSource(fromCloud ? 'cloud' : 'link'); if (!window.crmStore.hasCloud) setWarn('offline'); setNome((d.cliente && d.cliente.nome) || ''); setCpf((d.cliente && d.cliente.doc) || ''); setDeal(d); setLoading(false); })(); }, [code, id, token, enc]); const goPay = async () => { if (!aceito) { setWarn('Marque o aceite dos termos para continuar.'); return; } if (!nome.trim()) { setWarn('Informe seu nome completo.'); return; } setWarn(''); setBusy(true); const assinatura = { nome: nome.trim(), cpf, signedAt: Date.now() }; try { if (window.crmStore.hasCloud) await window.crmStore.clientSetStatus(code, code, 'aceite', assinatura); } catch (e) { console.error('[D Digital CRM] client_set_status (aceite) falhou:', e); setSyncErr((e && e.message) || String(e)); } setBusy(false); setPhase('pay'); if (scrollRef.current) scrollRef.current.scrollTop = 0; }; const confirmPaid = async () => { setBusy(true); let ok = true; try { if (window.crmStore.hasCloud) await window.crmStore.clientSetStatus(code, code, 'pagamento', null); } catch (e) { console.error('[D Digital CRM] client_set_status (pagamento) falhou:', e); setSyncErr((e && e.message) || String(e)); ok = false; } // verificação: relê da nuvem para confirmar que o status realmente mudou if (ok && window.crmStore.hasCloud) { try { const check = await window.crmStore.clientGet(code, code); if (!check || check.stage !== 'pagamento') setSyncErr('A confirmação não chegou ao sistema (link antigo?). Avise seu contato pelo WhatsApp.'); } catch (e) { setSyncErr('Não foi possível confirmar com o sistema. Avise seu contato pelo WhatsApp.'); } } setBusy(false); setPhase('done'); if (scrollRef.current) scrollRef.current.scrollTop = 0; }; if (loading) return
; return (
Ambiente seguro
{error && (

Link indisponível

{error} Solicite um novo ao seu contato na {window.brandName()}.

{diag && (
Detalhes técnicos
  • Modo: {diag.mode} · nuvem: {String(diag.hasCloud)}
  • ID: {diag.id} · token: {diag.token}
  • Busca na nuvem: {diag.cloud}
  • Dados no link (d=): {diag.encLen} chars · válido: {String(diag.encOk)}

Se "Busca na nuvem" indicar erro, falta rodar o passo 7 no Supabase. Se o "d=" estiver com 0 chars, o link foi cortado ao enviar — gere e copie o link inteiro.

)}
)} {!error && deal && phase === 'term' && (
Proposta comercial

Olá{(deal.cliente.nome ? ', ' + deal.cliente.nome.split(' ')[0] : '')} 👋

Revise sua proposta abaixo, assine e siga para o pagamento.

setNome(e.target.value)} /> setCpf(maskDoc(e.target.value))} inputMode="numeric" />
Assinatura digital {}} onCapture={(img) => setAssinaturaImg(img)} />
{warn &&
{warn}
}
)} {!error && deal && phase === 'pay' && ( )} {!error && deal && phase === 'done' && (

Tudo certo! 🎉

Recebemos seu aceite e a confirmação de pagamento. A equipe da {window.brandName()} dará sequência ao seu projeto e entrará em contato em breve.

{syncErr ? (

{syncErr}

) : (

Confirmação registrada no sistema.

)}
)}
); } Object.assign(window, { ClientView });