CONTRACTOR’S PAGE

Step 1 of 4 — Contractor

1) Contractor

Identity

Tax & GST

Registered for GST?
No Yes
11 digits. We’ll verify against ABN rules.

Contact

Address

Bank details

Used only for EFT payments. Stored encrypted. Privacy policy.

Licences & Certificates

Upload Documentation

\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); }); })();