Step 1 of 4 — Contractor
4) Finish
Review complete. When you click Send we’ll submit your agreement and generate your invoice if selected.
Before sending
1Your contractor details look good.
2Project and terms confirmed.
Email sent
We’ve sent you an email to review and sign. Next steps:
- Open your email and e‑sign the agreement.
- We’ll notify you when it’s countersigned.
- If you opted in, we’ll generate an invoice PDF after completion.
\1
/* Lightweight diagnostic (disabled) */
var PCX_DEBUG = false;
function dlog(){ try{ if(PCX_DEBUG) console.log.apply(console, arguments); }catch(_){ } }
// Endpoints
// put the contractor/event key here (e.g., from your login/param)
window.CONTRACTOR_EVENT_KEY = "
";
// tell the embed it can unlock & load
window.dispatchEvent(new CustomEvent("pcx:unlocked"));
const SEND_URL = (window.CONTRACTOR_FN && window.CONTRACTOR_FN.sendAgreement) || 'https://na4q6jdtzbhltlvi2dmwa2cyay0kxtcs.lambda-url.us-east-1.on.aws/';
const RSA_UPLOAD_URL = (window.CONTRACTOR_FN && window.CONTRACTOR_FN.uploadDocument) || null; // optional; if set, uploads RSA PDF via backend
// Defer RSA upload until after successful submission (use returned project_id)
const DEFER_RSA_UPLOAD = true;
// ABN lookup temporarily disabled until ABR GUID is configured
const ABN_LOOKUP_URL = null;
// State
const state = { step:1, maxStep:4, unlocked:false, identity:null, sigDirty:false, completed:new Set([1]), revealBank:false, rsa:null, abnInfo:null, lockOnFinish:false };
// TESTING: set to true to make all steps directly accessible via stepper
// Production: keep false to enforce gated navigation.
const TEST_MODE = false;
// Helpers
const $ = (s) => document.querySelector(s);
const $$ = (s) => Array.from(document.querySelectorAll(s));
const prog = $('#progFill');
const stepsEls = $$('.progress .steps .s');
const stepTitles = ['Contractor','Project & Compliance','Signature & Review','Finish'];
const TERMS_URL = 'https://docs.google.com/document/d/e/2PACX-1vTxL9hHkG9fLdeKQsySlbcP2AitmWt3Ior7PicZWWtMC7VFXq7PoHCse-IcBv5EMo70NJ4NoNM7XKRZ/pub';
// Prefetch Terms for quicker open
try{
const l1=document.createElement('link'); l1.rel='preconnect'; l1.href='https://docs.google.com'; document.head.appendChild(l1);
const l2=document.createElement('link'); l2.rel='prefetch'; l2.href=TERMS_URL; l2.as='document'; document.head.appendChild(l2);
}catch(_){ }
function setStep(n){
state.step = Math.max(1, Math.min(state.maxStep, n));
const pct = ((state.step-1)/(state.maxStep-1))*100; prog.style.width = pct+'%';
if (TEST_MODE) { state.completed = new Set([1,2,3,4]); }
stepsEls.forEach((el,i)=>{ el.classList.toggle('is-active', i===state.step-1); el.toggleAttribute('disabled', !state.completed.has(i+1)); el.setAttribute('aria-current', i===state.step-1? 'step' : 'false'); el.toggleAttribute('data-completed', state.completed.has(i+1)); });
const meta = document.getElementById('progMeta'); meta.textContent = `Step ${state.step} of ${state.maxStep} — ${stepTitles[state.step-1]}`;
$$('.step').forEach(sec=>sec.classList.toggle('hidden', Number(sec.dataset.step)!==state.step));
// Hide success state when navigating away from the final step
if (state.step !== state.maxStep){ const suc = document.getElementById('sec_success'); if (suc) suc.classList.add('hidden'); }
// Sticky bar: hide Next & Back on final step; show Send on final step
const nextBtn = document.querySelector('.stickybar [data-next]');
if (nextBtn){ nextBtn.style.display = (state.step===state.maxStep? 'none' : ''); nextBtn.disabled = (state.step===state.maxStep); }
const backBtn = document.querySelector('.stickybar [data-back]');
if (backBtn){ backBtn.style.display = ''; backBtn.disabled = false; }
const sendBtn = document.getElementById('btn_send');
if (sendBtn){
sendBtn.classList.toggle('hidden', state.step!==state.maxStep);
if (state.step===state.maxStep){ const c=document.getElementById('confirm_send'); sendBtn.disabled = !(c && c.checked); }
}
// When reaching Finish, lock navigation permanently
if (state.step === state.maxStep){
state.lockOnFinish = true;
stepsEls.forEach((el,i)=>{ if (i!==state.step-1) el.setAttribute('disabled','true'); });
}
updatePreview();
// After DOM updates, normalize host embed height so shorter steps don't inherit larger height
requestAnimationFrame(fitEmbedHeight);
}
function getKey(){ return (window.CONTRACTOR_EVENT_KEY || window.CUSTOMER_EVENT_KEY || '').trim(); }
function authHeaders(){ const h={'Accept':'application/json'}; try{ const t=localStorage.getItem('pcx_token'); if(t) h['Authorization']='Bearer '+t; }catch(e){} return h; }
function withKey(payload){ try{payload=Object(payload)}catch(e){payload={}} const k=getKey(); if(k) payload.eventKey=k; try{ if(state.identity && state.identity.email){ payload.identityEmail = state.identity.email; } }catch(_){ } return payload; }
// Session helpers
function getSavedSession(){
try{
const raw = localStorage.getItem('CONTRACTOR_EVENT_KEY');
if (!raw) return null; const obj = JSON.parse(raw);
if (obj && obj.k){ return obj; }
return {k:String(raw), t:0};
}catch(_){ return null; }
}
function startSessionCountdown(){
// Session countdown disabled — clear any existing UI/timer
const badge = document.getElementById('session_badge');
if (badge){ badge.textContent=''; badge.classList.remove('warn'); }
try { if (window._pcxSessTimer) clearInterval(window._pcxSessTimer); } catch(_){ }
window._pcxSessTimer = null;
}
// Inline validators
const reEmail=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;
function softWarnGST(){ const abn= $('#c_abn').value.replace(/\s+/g,'').trim(); const gst = document.getElementById('gst_toggle').checked ? 'yes' : 'no'; $('#warn_gst').classList.toggle('hidden', !(gst==='yes' && !abn)); }
function toggleGSTStart(){ const wrap = document.getElementById('gst_start_wrap'); const show = document.getElementById('gst_toggle').checked; wrap.classList.toggle('hidden', !show); }
// ABN/BSB/phone helpers
function normalizeTitleCase(s){ return String(s||'').toLowerCase().replace(/\b([a-z])(\w*)/g,(m,a,b)=> a.toUpperCase()+b); }
// ABN helpers
function abnDigits(v){ return String(v||'').replace(/\D/g,'').slice(0,11); }
function formatABN(v){ const d = abnDigits(v); if (!d) return ''; return d.replace(/^(\d{2})(\d{3})(\d{3})(\d{0,3})$/, (_,a,b,c,d4)=> [a,b,c,d4].filter(Boolean).join(' ')); }
function validABNChecksum(v){ const d=abnDigits(v); if (d.length!==11) return false; const w=[10,1,3,5,7,9,11,13,15,17,19]; const a=d.split('').map(Number); a[0]-=1; const s=a.reduce((t,n,i)=> t + n*w[i],0); return (s % 89)===0; }
function formatBSB(v){ const d=(v||'').replace(/\D/g,'').slice(0,6); return d.replace(/(\d{3})(\d{0,3})/,'$1-$2').replace(/-$/,''); }
function validBSB(v){ return /^\d{3}-?\d{3}$/.test(v.replace(/\s+/g,'')); }
function validAcct(v){ const d=(v||'').replace(/\D/g,''); return /^\d{6,10}$/.test(d); }
// Debounce utility
function debounce(fn, wait){ let t=null; return function(...args){ clearTimeout(t); t=setTimeout(()=>fn.apply(this,args), wait); }; }
// ABN lookup UI helpers (disabled)
function setAbnHelp(msg){ const el=$('#abn_help'); if(el) el.textContent = '11 digits. We’ll verify against ABN rules.'; }
function setAbnErr(msg){ const el=$('#abn_err'); if(el) el.textContent = ''; var abnEl = document.getElementById('c_abn'); if (abnEl) abnEl.setAttribute('aria-invalid', 'false'); }
function validateStep1(){
let ok=true;
const name=$('#c_name'), email=$('#c_email'), unit=$('#addr_unit'), street=$('#addr_street');
if(!name.value.trim()){ ok=false; }
if(!unit.value.trim()){ ok=false; }
if(!street.value.trim()){ ok=false; }
if(!reEmail.test(email.value.trim())){ ok=false; $('#email_err').textContent='Enter a valid email address.'; email.setAttribute('aria-invalid','true'); } else { $('#email_err').textContent=''; email.setAttribute('aria-invalid','false'); }
{
// TESTING: auto-fill bank fields when empty to avoid blocking during manual testing
if (typeof TEST_MODE !== 'undefined' && TEST_MODE) {
if (!$('#c_bank').value.trim()) $('#c_bank').value = 'Test Contractor';
if (!$('#c_bsb').value.trim()) $('#c_bsb').value = '123-456';
if (!$('#c_acct').value.trim()) $('#c_acct').value = '123456';
}
const bsb = $('#c_bsb').value; const acct=$('#c_acct').value; const bankName = $('#c_bank').value.trim();
const bankEl=$('#c_bank'), bsbEl=$('#c_bsb'), acctEl=$('#c_acct');
if(!bankName || !validBSB(bsb) || !validAcct(acct)){
ok=false; $('#bank_err').textContent='Enter bank name, valid BSB (123-456), and 6–10 digit account number.';
if (bankEl) bankEl.setAttribute('aria-invalid', String(!bankName));
if (bsbEl) bsbEl.setAttribute('aria-invalid', String(!validBSB(bsb)));
if (acctEl) acctEl.setAttribute('aria-invalid', String(!validAcct(acct)));
} else {
$('#bank_err').textContent='';
if (bankEl) bankEl.setAttribute('aria-invalid','false');
if (bsbEl) bsbEl.setAttribute('aria-invalid','false');
if (acctEl) acctEl.setAttribute('aria-invalid','false');
}
}
// ABN optional; verification disabled until ABR GUID configured
setAbnErr('');
// RSA upload is mandatory (new simple block)
(function(){
const okNow = !!state.rsaUploaded; // set true by the new handler on success
if (!okNow){
ok = false;
// Surface the message in the new block’s status area
const statusDiv = document.getElementById('status');
if (statusDiv) statusDiv.innerHTML = 'RSA certificate PDF is required. Please upload above.';
}
})();
softWarnGST(); toggleGSTStart();
return ok;
}
function validateStep2(){
// Project & Compliance: require agreement checkbox; data is auto-filled later
const agree = document.getElementById('agree_terms');
const err = document.getElementById('agree_err');
const ok = !!(agree && agree.checked);
if (agree) agree.setAttribute('aria-invalid', String(!ok));
if (err) err.textContent = ok ? '' : 'You must agree to the terms to continue.';
return ok;
}
function validateStep4(){ const sigOk = state.sigDirty; const nm=$('#sig_name').value.trim(); return sigOk && !!nm; }
function validateStep3(){ return validateStep4(); }
// Prefill hook (if header/site scripts expose helpers)
async function tryPrefill(){
try{
if (typeof window.pcxPrefillFromSheets === 'function'){
const hint = await window.pcxPrefillFromSheets({ email: $('#c_email').value.trim() });
if (hint && typeof hint === 'object'){
const map = { name:'c_name', email:'c_email', phone:'c_phone', address:'addr_street', abn:'c_abn' };
Object.entries(map).forEach(([k,id])=>{ if (hint[k] && !$('#'+id).value) $('#'+id).value = hint[k]; });
updatePreview();
}
}
}catch(e){ /* no-op */ }
}
// Project data loading
async function loadProjectData(contractorId) {
if (!contractorId) return;
try {
const verifyUrl = (window.CONTRACTOR_FN && window.CONTRACTOR_FN.verifyContractorId) || 'https://h5npiegjoyplaisbkmkvb4cydy0urmqe.lambda-url.us-east-1.on.aws/';
try{ const form2=document.querySelector('[data-step="2"] .form'); if(form2) form2.setAttribute('aria-busy','true'); }catch(_){ }
const res = await fetch(verifyUrl + '?contractor_id=' + encodeURIComponent(contractorId));
if (!res.ok) return;
const data = await res.json();
if (data.project) {
const p = data.project || {};
// Strict canonical keys only
$('#p_id').value = p.project_id || '';
$('#p_venue').value = p.venue || '';
$('#p_addr').value = p.site_address || '';
$('#p_date').value = p.event_date || '';
// Start time and Duration (new)
$('#p_start').value = (p.start_time!=null? String(p.start_time) : (p['Start Time']!=null? String(p['Start Time']) : ''));
$('#p_duration').value = (p.duration!=null? String(p.duration) : (p['Duration']!=null? String(p['Duration']) : ''));
$('#p_fee').value = p.fixed_fee_ex_gst || '';
$('#p_deliv').value = p.deliverables || '';
// Debug: log any missing fields from backend response
try{
const need=['site_address','event_date','start_time','duration','fixed_fee_ex_gst'];
const missing = need.filter(k=> !p || !p[k]);
if (missing.length){ console.log('verifyContractorId missing fields:', missing, 'project keys:', Object.keys(p)); }
}catch(_){ }
updatePreview();
}
} catch (e) {
console.log('Failed to load project data:', e);
} finally {
try{ const form2=document.querySelector('[data-step="2"] .form'); if(form2) form2.removeAttribute('aria-busy'); }catch(_){ }
}
}
// Gate: rely on site header injection and/or unique_login
function setUnlocked(){
state.unlocked = true;
$('#p_id').disabled = false;
const lock = document.getElementById('proj_lock');
if(lock) lock.classList.add('hidden');
// Load project data when unlocked
const contractorId = getKey();
if (contractorId) loadProjectData(contractorId);
}
window.addEventListener('pcx:unlocked', ()=>{ setUnlocked(); if (window.pcxIdentity) state.identity = window.pcxIdentity; try{ startSessionCountdown(); }catch(_){ } });
if (getKey()) { setUnlocked(); try{ startSessionCountdown(); }catch(_){ } }
// If session expires, reflect locked state locally
window.addEventListener('pcx:locked', ()=>{ try{ state.unlocked=false; var el=document.getElementById('p_id'); if(el) el.disabled=true; const b=document.getElementById('session_badge'); if(b) b.textContent=''; }catch(_){ } });
// Step navigation
document.addEventListener('click', (e)=>{
const btn = e.target.closest('[data-next],[data-back]'); if(!btn) return;
// When on Finish, allow Back but block Next
if (state.lockOnFinish && btn.hasAttribute('data-next')) return;
if (btn.hasAttribute('data-next')){
const v = [null, validateStep1, validateStep2, validateStep3][state.step];
if (v && !v()){ shakeCurrent(); return; }
state.completed.add(state.step+1);
setStep(state.step+1);
if(window.innerWidth <= 700) window.scrollTo(0,0);
} else {
setStep(state.step-1);
}
});
document.querySelector('.steps').addEventListener('click', (e)=>{
const b = e.target.closest('[data-stepnav]'); if(!b) return; const n = Number(b.dataset.stepnav);
if (state.lockOnFinish) return; // disable stepper after reaching Finish
if (TEST_MODE || state.completed.has(n)) setStep(n);
});
function shakeCurrent(){ const sec = document.querySelector('.step:not(.hidden)'); if(!sec) return; sec.classList.add('shake'); setTimeout(()=>sec.classList.remove('shake'), 380); }
// Signature pad (only when step 3 first shown)
let sigInit=false; function initSig(){ if(sigInit) return; sigInit=true; const c=$('#sig'); const ctx=c.getContext('2d'); let drawing=false, prev=null; c.addEventListener('mousedown',start); c.addEventListener('touchstart',start,{passive:false}); c.addEventListener('mousemove',move); c.addEventListener('touchmove',move,{passive:false}); window.addEventListener('mouseup',end); window.addEventListener('touchend',end); c.addEventListener('dblclick',clear); $('#sig_clear').addEventListener('click',clear); function pos(e){ const r=c.getBoundingClientRect(); const t=(e.touches?e.touches[0]:e); return {x:t.clientX-r.left, y:t.clientY-r.top}; } function start(e){ drawing=true; prev=pos(e); e.preventDefault(); } function move(e){ if(!drawing) return; const p=pos(e); ctx.strokeStyle='#111'; ctx.lineWidth=2; ctx.lineCap='round'; ctx.beginPath(); ctx.moveTo(prev.x,prev.y); ctx.lineTo(p.x,p.y); ctx.stroke(); prev=p; state.sigDirty=true; e.preventDefault(); } function end(){ drawing=false; } function clear(){ ctx.clearRect(0,0,c.width,c.height); state.sigDirty=false; } }
const obs = new MutationObserver(()=>{ var cur = document.querySelector('.step:not(.hidden)'); if(cur && Number(cur.dataset.step)===3) initSig(); }); obs.observe($('#pcx-emp'), {subtree:true, attributes:true, attributeFilter:['class']});
// Live preview
function maskAcct(v){ const d=(v||'').replace(/\D/g,''); if(!d) return ''; const last4 = d.slice(-4); const len = Math.max(0, d.length-4); const bullets = '•'.repeat(Math.min(len, 6)); return `${bullets}${len>4?' ':''}${last4}`; }
function updatePreview(){
// If in Finish step, populate the summary instead of preview
const outEl = document.getElementById('prev_body') || document.getElementById('finish_summary');
if (!outEl) return;
const name = normalizeTitleCase($('#c_name').value||'');
const email = String($('#c_email').value||'').toLowerCase();
const phone = $('#c_phone').value || '';
const addr = [$('#addr_unit').value, $('#addr_street').value, $('#addr_suburb').value, $('#addr_state').value, $('#addr_post').value].map(normalizeTitleCase).filter(Boolean).join(', ');
const abn = $('#c_abn').value||'';
const gstYes = document.getElementById('gst_toggle').checked;
const gstLabel = gstYes? 'Registered for GST' : 'Not registered for GST.';
const bsb = formatBSB($('#c_bsb').value||'');
const acct = state.revealBank ? ($('#c_acct').value||'') : maskAcct($('#c_acct').value||'');
const bankLine = [$('#c_bank').value, bsb && ('BSB '+bsb), acct && ('Account '+acct)].filter(Boolean).join(' • ');
const fee = $('#p_fee').value ? Number($('#p_fee').value) : null;
const start = $('#p_start') ? $('#p_start').value : '';
const duration = $('#p_duration') ? $('#p_duration').value : '';
const site = [$('#p_addr').value, (start? ('Start '+start):''), (duration? ('Duration '+duration+'h') : ''), $('#p_date').value].filter(Boolean).join(' • ');
const items = [
['Contractor', [name, email].filter(Boolean).join(' • ')],
['Mobile', phone],
['Address', addr],
['ABN', abn],
['GST', gstLabel],
['Bank', bankLine],
['Project', [$('#p_id').value, $('#p_venue').value].filter(Boolean).map(normalizeTitleCase).join(' • ')],
['Site', site],
['Deliverables', $('#p_deliv').value||''],
['Fee', fee!=null? ('$'+fee.toFixed(2)+' ex GST') : '']
].filter(([,v])=> Boolean(v));
outEl.innerHTML = items.map(([k,v])=>`${k}: ${escape(String(v))}
`).join('');
}
const updatePreviewDeb = debounce(updatePreview, 120);
document.addEventListener('input', (e)=>{
// ABN input listeners disabled
if (e.target.id==='c_bsb'){ e.target.value = formatBSB(e.target.value); }
// No phone auto-formatting
if (e.target.closest('.form')) updatePreviewDeb();
});
// ABN blur/lookup disabled
// === Simple upload block (ported from upload.html) ===
const LAMBDA_URL = 'https://p2jjobpy34iybteunqekma6ijm0qmwnz.lambda-url.us-east-1.on.aws/'; // from upload.html
// Track upload success so Step 1 validation works with the new block
if (!('rsaUploaded' in state)) state.rsaUploaded = false;
(function initSimpleUpload(){
const form = document.getElementById('uploadForm');
if (!form) return; // defensive if this step is hidden
form.addEventListener('submit', async (e) => {
e.preventDefault();
const contractorId = (document.getElementById('contractorId')||{}).value || '';
const fileInput = document.getElementById('fileInput');
const file = fileInput && fileInput.files ? fileInput.files[0] : null;
const statusDiv = document.getElementById('status');
if (!file) {
if (statusDiv) statusDiv.textContent = 'Choose a file first.';
return;
}
if (statusDiv) statusDiv.textContent = 'Uploading...';
try {
const reader = new FileReader();
reader.onload = async () => {
const base64Data = String(reader.result||'').split(',')[1];
const response = await fetch(LAMBDA_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contractor_id: contractorId,
file_data: base64Data,
filename: file.name
})
});
let result = {};
try { result = await response.json(); } catch(_) {}
if (response.ok) {
if (statusDiv) statusDiv.innerHTML = 'File uploaded successfully!';
// Satisfy existing flow: mark RSA present
state.rsaUploaded = true;
state.rsa = { name:file.name, size:file.size, type:file.type, id: result.file_id || null };
try { form.reset(); } catch(_) {}
} else {
state.rsaUploaded = false;
const msg = (result && (result.error || result.message)) ? String(result.error || result.message) : 'Unknown error';
if (statusDiv) statusDiv.innerHTML = 'Error: ' + escape(msg) + '';
}
};
reader.readAsDataURL(file);
} catch (error) {
state.rsaUploaded = false;
if (statusDiv) statusDiv.innerHTML = 'Upload failed: ' + escape(String(error && error.message || error)) + '';
}
});
})();
const termsIframe = () => document.getElementById('terms_iframe');
const termsCloseBtn = () => document.getElementById('terms_close');
// Squarespace code block wrappers sometimes preserve an initial height.
// Detect the nearest block-like container and sync its height to the current content.
function getEmbedContainer(){
let el = document.getElementById('pcx-emp');
let p = el ? el.parentElement : null;
let last = null;
while (p && p !== document.body){
if (
p.classList.contains('sqs-block') ||
p.classList.contains('sqs-block-code') ||
p.classList.contains('sqs-block-html') ||
p.dataset && (p.dataset.sqsBlockType === 'code' || p.dataset.blockType === 'code')
){
return p;
}
last = p; p = p.parentElement;
}
return last;
}
function fitEmbedHeight(){
const host = getEmbedContainer();
const root = document.getElementById('pcx-emp');
if (!host || !root) return;
// Clear any fixed height first, then set to current content height
host.style.minHeight = '0px';
host.style.height = 'auto';
// Measure after clearing to avoid stale values
const h = Math.ceil(root.getBoundingClientRect().height);
host.style.height = h + 'px';
}
function sizeTerms(){
// Iframe fills the panel below header; account for header height
const f = termsIframe(); if(!f) return;
const panel = f.closest('.panel');
const header = panel ? panel.querySelector('header') : null;
f.style.width = '100%';
if (panel){
const ph = panel.clientHeight;
const hh = header ? header.offsetHeight : 0;
const h = Math.max(0, ph - hh);
f.style.height = h + 'px';
} else {
f.style.height = '100%';
}
}
function openTerms(){
termsModal.dataset.show = 'true';
setTimeout(()=>{ try{ termsCloseBtn().focus(); }catch(_){ } }, 0);
sizeTerms();
}
function closeTerms(){ delete termsModal.dataset.show; }
if (termsLink){ termsLink.addEventListener('click', (e)=>{ e.preventDefault(); openTerms(); }); }
document.addEventListener('keydown', (e)=>{ if(e.key==='Escape' && termsModal.dataset.show==='true') closeTerms(); });
termsModal.addEventListener('click', (e)=>{ if (e.target === termsModal) closeTerms(); });
document.addEventListener('click', (e)=>{ if (e.target && e.target.id==='terms_close') closeTerms(); });
window.addEventListener('resize', ()=>{ if (termsModal.dataset.show==='true') sizeTerms(); });
// Prefill + toggles
$('#c_email').addEventListener('blur', tryPrefill);
document.getElementById('gst_toggle').addEventListener('change', ()=>{ softWarnGST(); toggleGSTStart(); updatePreview(); });
// Bank details are mandatory; no toggle
// Submit
try{
$('#btn_send').addEventListener('click', async ()=>{
const btn=$('#btn_send'), status=$('#send_status');
// Prevent submission if session expired
if (!getKey()){ status.innerHTML = 'Session expired — please re‑enter your contractor ID.'; window.dispatchEvent(new CustomEvent('pcx:locked',{})); return; }
btn.disabled=true; status.textContent='Sending…';
// Show sending overlay
try{ const ov = document.getElementById('pcx-sending'); if (ov) ov.setAttribute('data-show','true'); }catch(_){ }
try{
// Require RSA at send time as well
if (!state.rsa){
const errEl = document.getElementById('rsa_err');
if (errEl) errEl.textContent = 'RSA certificate PDF is required.';
status.innerHTML = 'Please upload your RSA certificate PDF.';
setStep(1);
throw new Error('Missing required RSA certificate');
}
const payload = withKey({
contractor:{
name: $('#c_name').value.trim(),
email: $('#c_email').value.trim(),
phone: $('#c_phone').value.trim(),
tradingName: $('#c_tname').value.trim() || null,
address: { unit: $('#addr_unit').value.trim() || null, street: $('#addr_street').value.trim() || null, suburb: $('#addr_suburb').value.trim(), state: $('#addr_state').value.trim(), postcode: $('#addr_post').value.trim() },
abn: $('#c_abn').value.replace(/\D/g,'') || null,
gst: document.getElementById('gst_toggle').checked ? 'yes' : 'no',
gstStart: document.getElementById('gst_start').value || null,
bank: { name: $('#c_bank').value.trim(), bsb: formatBSB($('#c_bsb').value), account: $('#c_acct').value.replace(/\D/g,'') },
// Only send RSA metadata to keep payload small; actual file uploads after submit
rsa: (function(){ const r=state.rsa; return r? { name:r.name, size:r.size, type:r.type } : null; })()
},
project:{ id: $('#p_id').value.trim(), venue: $('#p_venue').value.trim(), address: $('#p_addr').value.trim(), date: $('#p_date').value, start_time: ($('#p_start')? $('#p_start').value.trim():''), duration: ($('#p_duration')? $('#p_duration').value.trim():''), deliverables: $('#p_deliv').value.trim(), fee_ex_gst: $('#p_fee').value.trim() },
compliance: Object.fromEntries($$('.ack').map(a=>[a.dataset.k, a.checked])),
signature:{ name: $('#sig_name').value.trim(), image: (function(){ try{ return $('#sig').toDataURL('image/png'); }catch(e){ return null; } })(), ts: new Date().toISOString(), ua: navigator.userAgent },
preferences:{ generateInvoice: true }
});
const res = await fetch(SEND_URL, { method:'POST', headers: Object.assign({'Content-Type':'application/json'}, authHeaders()), body: JSON.stringify(payload) });
if (!res.ok){ const t=await res.text().catch(()=> ''); throw new Error(`HTTP ${res.status}: ${t}`); }
const j = await res.json().catch(()=>({}));
if (j && (j.ok || j.status==='STORED')){
const last = document.querySelector('[data-step="3"]'); if (last) last.classList.add('hidden');
$('#sec_success').classList.remove('hidden');
const meta = document.getElementById('progMeta'); if (meta) meta.textContent = 'Submission received — Processing';
status.textContent='Submission received. Processing will begin shortly.';
// Post-submission: remove navigation/actions that are no longer relevant
try{
const backBtn = document.querySelector('.stickybar [data-back]');
if (backBtn) backBtn.remove();
const sendBtn = document.getElementById('btn_send');
if (sendBtn) sendBtn.remove();
}catch(_){ /* no-op */ }
// RSA already uploaded via resumable session earlier
} else {
throw new Error(j && (j.error||j.message) || 'Unexpected response');
}
}catch(err){ status.innerHTML = ''+escape(String(err.message||err))+''; }
finally{ btn.disabled=false; try{ const ov = document.getElementById('pcx-sending'); if (ov) ov.removeAttribute('data-show'); }catch(_){ } }
});
// Escape util
function escape(s){ return String(s).replace(/[&<>"'`=\/]/g,(c)=>({"&":"&","<":"<",">":">","\"":""","'":"'","`":"`","=":"=","/":"/"}[c])); }
// Observe size changes (e.g., validation errors, async prefill) and keep host height in sync
try{
const ro = new ResizeObserver(()=> fitEmbedHeight());
ro.observe(document.getElementById('pcx-emp'));
}catch(_){ /* ResizeObserver not supported */ }
window.addEventListener('load', fitEmbedHeight);
window.addEventListener('resize', ()=>{ requestAnimationFrame(fitEmbedHeight); });
})();
Please wait… sending your submission. Do not close this tab.