WARMBOX
☎ 020 3161 4195
✓ Gas Safe registered engineers ✓ TrustMark approved ✓ Cancel anytime, no exit fees
'; html += '
Membership type' + esc(currentSet().label) + '
'; if (p) html += '
Plan' + esc(p.name + (p.tag ? ' · ' + p.tag : '')) + '
'; if (state.postcode) html += '
Postcode' + esc(state.postcode.toUpperCase()) + '
'; if (state.fuel) html += '
Fuel type' + esc(state.fuel) + '
'; if (state.fullName) html += '
Name' + esc(state.fullName) + '
'; if (p) { html += '
Monthly membership' + fmt(p.monthly) + '
'; if (p.requestFee > 0) { html += '
Fee per engineer request' + fmt(p.requestFee) + '
'; } else if (p.joinFee > 0) { html += '
One-off joining fee' + fmt(p.joinFee) + '
'; } } html += '
Prices include parts covered under your plan. Major parts, if ever needed, are quoted separately before any work starts.
'; html += '
'; el.innerHTML = html; } function setStep(n, noScroll) { state.step = Math.max(0, Math.min(steps.length - 1, n)); renderGauge(); renderSummary(); renderStep(); if (!noScroll) root.scrollIntoView({ behavior: 'smooth', block: 'start' }); } function stepHeader(title, sub) { var eyebrow = '0' + (state.step + 1) + ' · ' + steps[state.step]; return '
' + esc(eyebrow) + '

' + esc(title) + '

' + esc(sub) + '

'; } function renderPlanTable() { var set = currentSet(); var html = '
'; html += ''; set.plans.forEach(function (p) { var isSelected = state.planId === p.id; html += ''; }); html += ''; html += ''; set.plans.forEach(function (p) { var isSelected = state.planId === p.id; html += ''; }); html += ''; html += ''; set.plans.forEach(function (p) { var isSelected = state.planId === p.id; html += ''; }); html += ''; set.features.forEach(function (feature, fi) { html += ''; set.plans.forEach(function (p) { var isSelected = state.planId === p.id; html += ''; }); html += ''; }); html += '
Included
' + esc(p.name) + '
' + (p.tag ? '
' + esc(p.tag) + '
' : '
 
') + '
' + fmt(p.monthly) + '/mo
' + (p.requestFee > 0 ? '
' + fmt(p.requestFee) + ' per request
' : (p.joinFee > 0 ? '
+ ' + fmt(p.joinFee) + ' joining fee
' : '
No joining fee
')) + '
' + esc(feature) + '' + (p.included[fi] ? '✓' : '—') + '
'; return html; } function canContinueStep2() { var fuelOk = !(state.customerType === 'homeowner' && state.fuel && state.fuel !== 'Mains gas'); return !!(state.boilerMake && state.boilerAge && state.bedrooms && state.fuel && state.existingCustomer && fuelOk); } function canContinueStep3() { return state.fullName.trim().length > 1 && /\S+@\S+\.\S+/.test(state.email) && state.phone.trim().length > 6 && state.addressLine.trim().length > 2; } function canContinueStep4() { return state.accountName.trim().length > 1 && state.sortCode.replace(/\D/g, '').length === 6 && state.accountNumber.replace(/\D/g, '').length >= 6; } function renderStep() { var panel = document.getElementById('wbxStepPanel'); var html = ''; if (state.step === 0) { html += stepHeader('Choose your cover', 'Tell us who the cover is for, then pick the plan that matches how much protection you want.'); html += '
' + '' + '' + '
'; html += '

' + esc(currentSet().note) + '

'; html += renderPlanTable(); html += '
'; } else if (state.step === 1) { html += stepHeader('Check we cover your area', 'Enter your postcode so we can confirm an engineer covers your road and give you an accurate first-visit date.'); html += '
' + '' + '
Enter a valid UK postcode.
'; html += ''; html += '
' + (state.coverageChecked ? (state.coverageOk ? 'Good news — engineers cover ' + esc(state.postcode.toUpperCase()) + '.' : 'We don\'t have an engineer covering ' + esc(state.postcode.toUpperCase()) + ' yet. Leave your details on our contact page and we\'ll let you know when we launch there.') : '') + '
'; html += '
'; } else if (state.step === 2) { html += stepHeader('Tell us about your boiler', 'This helps us send an engineer who is set up for your system and flag anything that needs a quick look first.'); html += '
'; html += '
'; html += '
'; html += '
'; html += '
'; html += '
' + ['Mains gas', 'LPG', 'Oil'].map(function (f) { return '
' + esc(f) + '
'; }).join('') + '
'; html += '
Homeowner plans currently cover mains gas boilers only. For LPG or oil systems, choose a landlord plan or contact WarmBox directly.
'; html += '
' + ['Yes', 'No', 'Not sure'].map(function (f) { return '
' + esc(f) + '
'; }).join('') + '
'; html += '
'; } else if (state.step === 3) { html += stepHeader('Your details', 'We\'ll use this to confirm your membership and get an engineer booked in.'); html += '
'; html += '
'; html += '
'; html += '
'; html += '
'; html += '
Postcode on file: ' + esc(state.postcode ? state.postcode.toUpperCase() : '—') + '
'; html += '
'; } else if (state.step === 4) { html += stepHeader('Set up your Direct Debit', 'Membership payments are collected by Direct Debit through GoCardless, so your plan renews automatically with no card details to update.'); html += '
'; html += '
'; html += '
'; html += '
'; html += '
'; html += '
The Direct Debit Guarantee. This Guarantee is offered by all banks and building societies that accept instructions to pay Direct Debits. If there\'s ever a change to the amount or date of your payment, WarmBox will notify you in advance. If an error is made, you\'re entitled to a full and immediate refund from your bank or building society.
'; html += '
'; } else if (state.step === 5) { var p = getPlan(); html += stepHeader('Confirm your membership', 'Check the details below, then confirm to activate your cover.'); html += '
'; html += '
Membership type' + esc(currentSet().label) + '
'; html += '
Plan' + esc(p.name + (p.tag ? ' · ' + p.tag : '')) + '
'; html += '
Monthly payment' + fmt(p.monthly) + '
'; if (p.requestFee > 0) html += '
Fee per engineer request' + fmt(p.requestFee) + ' (charged only when you use it)
'; else if (p.joinFee > 0) html += '
Joining fee (due today)' + fmt(p.joinFee) + '
'; html += '
Property' + esc(state.postcode.toUpperCase()) + '
'; html += '
Contact' + esc(state.fullName) + '
'; html += '
'; var termsUrl = CONFIG.termsUrl || '#'; html += ''; html += '
'; html += '
'; if (state.submitError) html += '
' + esc(state.submitError) + '
'; html += '
'; } else if (state.step === 6) { var finalPlan = getPlan(); html += '
'; html += ''; html += '

You\'re covered

'; html += '

Your ' + esc(finalPlan ? finalPlan.name + (finalPlan.tag ? ' (' + finalPlan.tag + ')' : '') : 'WarmBox') + ' membership is active from today.

'; html += '
' + esc(state.reference || '') + '
'; html += '
'; html += '
    ' + '
  • A confirmation email is on its way to ' + esc(state.email || 'your inbox') + ' with your membership pack.
  • ' + (finalPlan && finalPlan.requestFee > 0 ? '
  • This plan has no joining fee — instead, a ' + fmt(finalPlan.requestFee) + ' fee applies each time you request an engineer visit.
  • ' : (finalPlan && finalPlan.joinFee > 0 ? '
  • Your one-off joining fee of ' + fmt(finalPlan.joinFee) + ' will be collected with your first payment.
  • ' : '')) + '
  • Your Direct Debit mandate is being set up now — this usually takes 2–3 working days to confirm with your bank.
  • ' + '
  • We\'ll text you a link to pick a date for your first visit once the mandate is confirmed.
  • ' + '
  • You can cancel free of charge within 14 days, or any time after with no exit fees.
  • ' + '
'; html += '
'; } panel.innerHTML = html; bindStepEvents(); } function refreshNextDisabled(fn) { var btn = document.getElementById('wbxNextBtn'); if (btn) btn.disabled = !fn(); } function activatePills(selector, attribute, handler) { root.querySelectorAll(selector).forEach(function (pill) { function activate() { handler(pill.getAttribute(attribute)); } pill.addEventListener('click', activate); pill.addEventListener('keydown', function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); activate(); } }); }); } function bindStepEvents() { var backBtn = document.getElementById('wbxBackBtn'); if (backBtn) backBtn.onclick = function () { if (!state.submitting) setStep(state.step - 1); }; var nextBtn = document.getElementById('wbxNextBtn'); if (nextBtn) nextBtn.onclick = function () { if (!nextBtn.disabled) setStep(state.step + 1); }; var restartBtn = document.getElementById('wbxRestartBtn'); if (restartBtn) restartBtn.onclick = function () { state = { step: 0, customerType: 'homeowner', planId: null, postcode: '', coverageChecked: false, coverageOk: null, boilerAge: '', boilerMake: '', fuel: '', bedrooms: '', existingCustomer: '', fullName: '', email: '', phone: '', addressLine: '', accountName: '', sortCode: '', accountNumber: '', agreeTerms: false, agreeCoolingOff: false, reference: null, submitting: false, submitError: '' }; setStep(0); }; if (state.step === 0) { root.querySelectorAll('#wbxAudienceToggle button').forEach(function (b) { b.onclick = function () { state.customerType = b.getAttribute('data-a'); state.planId = null; renderSummary(); renderStep(); }; }); root.querySelectorAll('[data-choose]').forEach(function (btn) { btn.onclick = function () { state.planId = btn.getAttribute('data-choose'); renderSummary(); renderStep(); }; }); } if (state.step === 1) { var postcodeInput = document.getElementById('wbxPostcodeInput'); postcodeInput.oninput = function () { state.postcode = postcodeInput.value; state.coverageChecked = false; state.coverageOk = null; refreshNextDisabled(function () { return false; }); }; document.getElementById('wbxCheckCoverageBtn').onclick = function () { var val = postcodeInput.value.trim(); var valid = /^[A-Za-z]{1,2}\d[A-Za-z\d]?\s*\d[A-Za-z]{2}$/.test(val); var err = document.getElementById('wbxPostcodeError'); var result = document.getElementById('wbxCoverageResult'); if (!valid) { err.className = 'wbx-field-error show'; return; } err.className = 'wbx-field-error'; state.postcode = val; state.coverageChecked = true; state.coverageOk = !/^zz/i.test(val.trim()); if (state.coverageOk) { result.className = 'wbx-coverage-result show ok'; result.textContent = 'Good news — engineers cover ' + val.toUpperCase() + '.'; } else { result.className = 'wbx-coverage-result show no'; result.textContent = 'We don\'t have an engineer covering ' + val.toUpperCase() + ' yet. Leave your details on our contact page and we\'ll let you know when we launch there.'; } renderSummary(); refreshNextDisabled(function () { return !!state.coverageOk; }); }; } if (state.step === 2) { document.getElementById('wbxBoilerMake').onchange = function (e) { state.boilerMake = e.target.value; refreshNextDisabled(canContinueStep2); }; document.getElementById('wbxBoilerAge').onchange = function (e) { state.boilerAge = e.target.value; refreshNextDisabled(canContinueStep2); }; document.getElementById('wbxBedrooms').onchange = function (e) { state.bedrooms = e.target.value; refreshNextDisabled(canContinueStep2); }; activatePills('#wbxFuelGroup .wbx-radio-pill', 'data-fuel', function (value) { state.fuel = value; renderSummary(); renderStep(); }); activatePills('#wbxExistingGroup .wbx-radio-pill', 'data-existing', function (value) { state.existingCustomer = value; renderStep(); }); } if (state.step === 3) { var map = { wbxFullName: 'fullName', wbxEmail: 'email', wbxPhone: 'phone', wbxAddressLine: 'addressLine' }; Object.keys(map).forEach(function (id) { var el = document.getElementById(id); el.oninput = function () { state[map[id]] = el.value; refreshNextDisabled(canContinueStep3); renderSummary(); }; }); } if (state.step === 4) { ['wbxAccountName','wbxSortCode','wbxAccountNumber'].forEach(function (id) { var el = document.getElementById(id); var key = id === 'wbxAccountName' ? 'accountName' : (id === 'wbxSortCode' ? 'sortCode' : 'accountNumber'); el.oninput = function () { state[key] = el.value; refreshNextDisabled(canContinueStep4); }; }); } if (state.step === 5) { document.getElementById('wbxAgreeTerms').onchange = function (e) { state.agreeTerms = e.target.checked; renderStep(); }; document.getElementById('wbxAgreeCoolingOff').onchange = function (e) { state.agreeCoolingOff = e.target.checked; renderStep(); }; var submitBtn = document.getElementById('wbxSubmitBtn'); if (submitBtn) submitBtn.onclick = submitToGravityForms; } } var GRAVITY_FORM_ID = 35; function getWordPressRestRoot() { var link = document.querySelector('link[rel="https://api.w.org/"]'); if (link && link.href) return link.href.replace(/\/$/, ''); return window.location.origin.replace(/\/$/, '') + '/wp-json'; } function buildGravityPayload() { var p = getPlan(); if (!p) throw new Error('No WarmBox plan is selected.'); var fee = p.requestFee > 0 ? fmt(p.requestFee) + ' per engineer request' : (p.joinFee > 0 ? fmt(p.joinFee) + ' joining fee' : 'None'); var payload = { input_1: currentSet().label, input_4: p.name, input_5: p.tag || 'Standard', input_6: fmt(p.monthly) + ' per month', input_7: fee, input_8: p.id, input_10: state.postcode.toUpperCase(), input_11: state.coverageOk ? 'Covered' : 'Not covered', input_13: state.boilerMake, input_14: state.boilerAge, input_15: state.bedrooms, input_16: state.fuel, input_17: state.existingCustomer, input_19: state.fullName.trim(), input_20: state.email.trim(), input_21: state.phone.trim(), input_22: state.addressLine.trim(), input_24: state.accountName.trim(), input_25: state.sortCode.trim(), input_26: state.accountNumber.trim(), input_30_1: state.agreeTerms ? 'terms_accepted' : '', input_31_1: state.agreeCoolingOff ? 'cooling_off_acknowledged' : '', input_32: state.reference, input_33: 'Direct Debit details received - mandate setup pending', input_34: 'WarmBox single Elementor HTML block', source_page: 6, target_page: 0 }; if (state.customerType === 'homeowner') payload.input_2 = p.id; else payload.input_3 = p.id; return payload; } function friendlyValidationMessage(messages) { if (!messages) return ''; var labels = { '1': 'Membership type', '2': 'Homeowner plan', '3': 'Landlord plan', '10': 'Postcode', '13': 'Boiler make', '14': 'Boiler age', '15': 'Property size', '16': 'Fuel type', '17': 'Previous WarmBox service/install', '19': 'Full name', '20': 'Email', '21': 'Phone', '22': 'Address', '24': 'Account holder name', '25': 'Sort code', '26': 'Account number', '30': 'Terms acceptance', '31': 'Cooling-off acknowledgement' }; var parts = []; Object.keys(messages).forEach(function (id) { var msg = messages[id]; if (Array.isArray(msg)) msg = msg.join(' '); parts.push((labels[id] ? labels[id] + ': ' : '') + String(msg)); }); return parts.join(' '); } function gravityEndpoint() { return getWordPressRestRoot() + '/gf/v2/forms/' + GRAVITY_FORM_ID + '/submissions'; } function encodeGravityPayload(payload) { var body = new URLSearchParams(); Object.keys(payload).forEach(function (key) { var value = payload[key]; if (value === null || typeof value === 'undefined') value = ''; body.append(key, String(value)); }); return body.toString(); } async function postGravitySubmission(payload) { var endpoint = gravityEndpoint(); var response; try { response = await fetch(endpoint, { method: 'POST', credentials: 'same-origin', headers: { 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, body: encodeGravityPayload(payload) }); } catch (networkError) { throw new Error('Could not reach Gravity Forms. Please try again.'); } var raw = await response.text(); var data = null; try { data = raw ? JSON.parse(raw) : null; } catch (parseError) {} if (response.status === 404) { throw new Error('Gravity Forms REST API is disabled on this site. In WordPress go to Forms > Settings > REST API, tick Enabled, then click Update.'); } if (response.status === 401 || response.status === 403) { throw new Error('The server blocked the Gravity Forms submission endpoint (HTTP ' + response.status + '). Check any security/firewall rule blocking /wp-json/gf/v2/.'); } if (!response.ok) { var apiMessage = data && (data.message || (data.data && data.data.message)); throw new Error(apiMessage || ('Gravity Forms returned HTTP ' + response.status + '.')); } if (!data) { throw new Error('Gravity Forms returned a non-JSON response. Check the Gravity Forms REST API setting and any cache/security plugin affecting /wp-json/.'); } if (data.is_valid !== true) { var validation = friendlyValidationMessage(data.validation_messages); throw new Error(validation || 'Gravity Forms rejected the application.'); } return data; } async function submitToGravityForms() { if (state.submitting || !state.agreeTerms || !state.agreeCoolingOff) return; state.submitError = ''; state.reference = state.reference || ('WBX' + Math.floor(1000000 + Math.random() * 9000000)); state.submitting = true; renderStep(); try { var payload = buildGravityPayload(); var result = await postGravitySubmission(payload); if (!result || result.is_valid !== true) { throw new Error('Gravity Forms did not confirm the application.'); } state.submitting = false; state.submitError = ''; setStep(6); } catch (err) { console.error('WarmBox submission failed:', err); state.submitting = false; state.submitError = err && err.message ? err.message : 'The application could not be submitted. Please try again.'; renderStep(); } } renderGauge(); renderSummary(); renderStep(); })();