var Webflow = Webflow || []; Webflow.push(function () { // ------------------------------------------------------------------ // Config // ------------------------------------------------------------------ const isProduction = false; // TODO: set to true before the production release const DATA_URL_TEST = 'https://myprofile-api-test.remoteapi.ge/myprofileportal/api/Health/GetPolicyInfo'; // TODO: confirmed test URL above; prod URL below is inferred (test -> prod domain swap), verify before go-live const DATA_URL_PROD = 'https://myprofile-api-prod.remoteapi.ge/myprofileportal/api/Health/GetPolicyInfo'; const DATA_URL = isProduction ? DATA_URL_PROD : DATA_URL_TEST; const SMS_URL_TEST = 'https://myprofile-api-test.remoteapi.ge/myprofileportal/api/Health/SendSMSEmail'; // TODO: confirmed test URL above; prod URL below is inferred (test -> prod domain swap), verify before go-live const SMS_URL_PROD = 'https://myprofile-api-prod.remoteapi.ge/myprofileportal/api/Health/SendSMSEmail'; const SMS_URL = isProduction ? SMS_URL_PROD : SMS_URL_TEST; const PAY_URL_TEST = 'https://on-test.tbci.ge/pay?policy='; const PAY_URL_PROD = 'https://on.tbci.ge/pay?policy='; const PAY_URL = isProduction ? PAY_URL_PROD : PAY_URL_TEST; const urlParams = new URLSearchParams(window.location.search); const policyId = urlParams.has('pid') ? urlParams.get('pid') : ''; const contrahentId = urlParams.has('cid') ? urlParams.get('cid') : ''; const REQ_PARAMS = { SecretUserName: 'landingInfo', SecretPassword: 'E3@`{L-Mf%dx', Requester: 'MyProfilePortal', UserId: null, SessionID: '559e670a22874755b351a465dccf4134', PolicyId: policyId, ContrahentID: contrahentId, }; // ------------------------------------------------------------------ // Start // ------------------------------------------------------------------ initModals(); // "terms consent" popup — wire up handlers right away initAppModal(); // "გადმოწერე აპლიკაცია" popup — doesn't depend on API data loadData(); // ------------------------------------------------------------------ // Data loading // ------------------------------------------------------------------ async function loadData() { setLoading(true); let data; try { const resp = await fetch(DATA_URL, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(REQ_PARAMS), }); data = await resp.json(); console.log('[medical-page] DATA:', data); } catch (e) { console.warn('[medical-page] fetch error:', e); setLoading(false); showNotFound(); return; } // CONFIRMED by TBC: the real response shape is wrapped, like the old // CASCO project — {description, status, isSuccess, policyInfo: {...the // actual fields: policyId, policyNumber, firstName, isValid, // firstPaymentIsValid, ...}}. This is the only shape we support now — // gate on isSuccess + policyInfo being present, matching the confirmed // contract exactly. if (data && data.isSuccess === true && data.policyInfo) { processData(data.policyInfo); } else { showNotFound(); } setLoading(false); } function setLoading(show) { const el = document.querySelector('.s-loading'); if (el) el.classList.toggle('is--active', show); } function showNotFound() { const el = document.querySelector('.s-error-not-found'); if (el) el.classList.add('open'); document.body.style.overflow = 'hidden'; // block page scroll while this screen is up } // ------------------------------------------------------------------ // Populating the page with data // ------------------------------------------------------------------ function processData(data) { // ---- name ---- document.querySelectorAll('[contrahentfirstname]').forEach(el => { el.textContent = data.firstName || ''; }); // ---- monthly premium + currency ---- const CURRENCY = data.grossWrittenPremiumCurrency === 1 ? '₾' : data.grossWrittenPremiumCurrency === 2 ? '$' : data.grossWrittenPremiumCurrency === 3 ? '€' : ''; const premiumText = `${new Intl.NumberFormat('fr-FR').format(data.monthPremium)} ${CURRENCY}`; document.querySelectorAll('[instalmentnumber]').forEach(el => { el.textContent = premiumText; }); // ---- services table: show the right product, hide the other one ---- const PRODUCT_A = 13891; const PRODUCT_B = 13990; document.querySelectorAll('.faq-item-2-table').forEach(block => { const first = block.querySelector('.table-info-first'); // product 13891 const second = block.querySelector('.table-info-second'); // product 13990 if (!first || !second) return; if (Number(data.productId) === PRODUCT_A) { first.style.display = ''; second.style.display = 'none'; } else if (Number(data.productId) === PRODUCT_B) { first.style.display = 'none'; second.style.display = ''; } else { console.warn('[medical-page] unknown product id:', data.productId); } }); // ---- policy wording links (policyWordingUrl) ---- // 1) the link inside the consent popup: text = wordingName document.querySelectorAll('[policywordingurl]').forEach(el => { el.textContent = data.wordingName || ''; el.setAttribute('href', data.policyWordingUrl || '#'); el.setAttribute('target', '_blank'); }); // 2) the "სადაზღვევო გამონაკლისები და სრული პირობები" link at the bottom // of the exclusions block document.querySelectorAll('[policywordingurlparameter]').forEach(el => { el.setAttribute('href', data.policyWordingUrl || '#'); el.setAttribute('target', '_blank'); }); // ---- buy buttons: disabled if the first payment already went through ---- const buyButtons = document.querySelectorAll('[data-gdpr-check]'); buyButtons.forEach(btn => { btn.addEventListener('click', e => { e.preventDefault(); handleBuyClick(data); }); if (data.firstPaymentIsValid === true) { setButtonDisabled(btn); } }); // ---- sticky buy popup on scroll: not needed at all if firstPaymentIsValid=true // (per spec: "we don't need this pop-up page" when the first payment already // went through) — don't even attach the scroll observer, and force it hidden // in case something else (e.g. a leftover .open class) would otherwise show it ---- if (data.firstPaymentIsValid !== true) { initStickyBuyPopup(); } else { hideStickyBuyPopup(); } // ---- keep the data around for sendsmsmail / redirect closures ---- // (a read-only copy in a global for debugging convenience) window.__policyData = data; } // ------------------------------------------------------------------ // "პოლისის შეძენა" click (both the main button and the sticky-popup button) // ------------------------------------------------------------------ function handleBuyClick(data) { if (data.firstPaymentIsValid === true) return; // just in case — the button is disabled anyway if (data.isValid === true) { redirectToPay(data.policyNumber); return; } // isValid !== true -> show the terms-consent popup first openTermsPopup(); } function redirectToPay(policyNumber) { window.location.href = PAY_URL + policyNumber; } // Disabled look for "პოლისის შეძენა" when firstPaymentIsValid === true. // Styling itself lives in Webflow (class "is-disable-btn") — this just // toggles the class and blocks interaction/a11y. function setButtonDisabled(btn) { btn.classList.add('is-disable-btn'); btn.style.pointerEvents = 'none'; btn.setAttribute('aria-disabled', 'true'); } // ------------------------------------------------------------------ // "Terms consent" popup (.c-popups-holder / .c-policy-popup) // ------------------------------------------------------------------ function initModals() { const holder = document.querySelector('.c-popups-holder'); const closeBtn = document.querySelector('.c-policy-popup__close'); const bg = document.querySelector('.c-popups-holder__bg'); const checkbox = document.querySelector('.c-policy-popup__chb input[type="checkbox"]'); // IMPORTANT: the class "c-policy-popup__chb__label" sits on TWO different // elements — an empty inside the checkbox // itself (Webflow's default label) and a
with the actual text+link // next to it. Grab the div specifically, otherwise we'd color the wrong element. const chbLabelText = document.querySelector('div.c-policy-popup__chb__label'); const submitBtn = document.querySelector('.c-policy-popup__cta[policynumberlinkaccepted]'); if (!holder) return; if (closeBtn) closeBtn.addEventListener('click', closeTermsPopup); if (bg) bg.addEventListener('click', closeTermsPopup); // clear the error (red text) as soon as the user checks the box if (checkbox && chbLabelText) { checkbox.addEventListener('change', () => { if (checkbox.checked) resetCheckboxError(chbLabelText); }); } if (submitBtn) { submitBtn.addEventListener('click', async e => { e.preventDefault(); if (!checkbox || !checkbox.checked) { showCheckboxError(chbLabelText); return; } const data = window.__policyData || {}; submitBtn.style.pointerEvents = 'none'; submitBtn.style.opacity = '0.6'; setSmsLoading(true); try { const resp = await fetch(SMS_URL, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(REQ_PARAMS), }); // Same contract as the old CASCO example (description === 'Ok' && // status === 0) when the body actually carries that JSON — but // verified live against the test endpoint that it CAN also reply // HTTP 200 with a completely empty body (content-length: 0, no // JSON at all). The old example's plain `resp.json()` would throw // in that case, so here: parse the body when there is one and // trust its description/status like the reference project does; // when there's no body to read, fall back to the HTTP status, // which is the only signal we actually have in that case. const text = await resp.text(); let respData = null; if (text) { try { respData = JSON.parse(text); } catch (e) { respData = null; } } console.log('[medical-page] sendsmsmail:', resp.status, respData); const success = respData ? (respData.description === 'Ok' && respData.status === 0) : resp.ok; if (success) { redirectToPay(data.policyNumber); } else { // there's no dedicated state for this error in the design — reuse // the general "not found" screen so the user isn't left without feedback closeTermsPopup(); showNotFound(); } } catch (err) { console.warn('[medical-page] sendsmsmail error:', err); closeTermsPopup(); showNotFound(); } finally { submitBtn.style.pointerEvents = ''; submitBtn.style.opacity = ''; setSmsLoading(false); } }); } } function openTermsPopup() { const holder = document.querySelector('.c-popups-holder'); if (holder) holder.classList.add('edit-mode'); } function closeTermsPopup() { const holder = document.querySelector('.c-popups-holder'); if (holder) holder.classList.remove('edit-mode'); } function showCheckboxError(labelTextEl) { if (!labelTextEl) return; labelTextEl.style.color = '#e5484d'; // TODO: swap for a brand red token if one exists const link = labelTextEl.querySelector('[policywordingurl]'); if (link) link.style.color = '#e5484d'; // the MED-001/26 link should turn red too (see the example in the spec doc) } function resetCheckboxError(labelTextEl) { if (!labelTextEl) return; labelTextEl.style.color = ''; const link = labelTextEl.querySelector('[policywordingurl]'); if (link) link.style.color = ''; } // Loader shown only while the sendsmsmail request (triggered by "შეიძინე") // is in flight — hidden by default, shown right before the fetch, hidden // again once it settles either way (success or error). function setSmsLoading(show) { const el = document.querySelector('.loading-animation-black_component'); if (!el) return; el.style.display = show ? 'flex' : 'none'; } // ------------------------------------------------------------------ // Sticky buy popup on scroll (#show-popup -> .modal_component-buy-policy) // ------------------------------------------------------------------ function initStickyBuyPopup() { const section = document.getElementById('show-popup'); const modal = document.querySelector('.modal_component-buy-policy'); if (!section || !modal) return; const observer = new IntersectionObserver( entries => { entries.forEach(entry => { if (entry.isIntersecting) { modal.classList.remove('open'); // scrolled back up to the section } else if (entry.boundingClientRect.top < 0) { modal.classList.add('open'); // scrolled the section past, going down } else { modal.classList.remove('open'); // haven't reached the section yet } }); }, { threshold: 0 } ); observer.observe(section); } // Force the sticky popup fully hidden when firstPaymentIsValid === true // (no scroll observer is attached in this case, so .open never gets added — // this just makes that explicit/defensive rather than relying on omission) function hideStickyBuyPopup() { const modal = document.querySelector('.modal_component-buy-policy'); if (!modal) return; modal.classList.remove('open'); modal.style.setProperty('display', 'none', 'important'); } // ------------------------------------------------------------------ // "გადმოწერე აპლიკაცია" popup (app QR code) // ------------------------------------------------------------------ function initAppModal() { const modal = document.querySelector('.modal_component'); if (!modal) return; const trigger = Array.from(document.querySelectorAll('a')).find( el => (el.textContent || '').trim() === 'გადმოწერე აპლიკაცია' ); const closeBtn = modal.querySelector('.modal_close-button'); if (trigger) { trigger.addEventListener('click', e => { e.preventDefault(); modal.classList.add('open'); }); } if (closeBtn) { closeBtn.addEventListener('click', e => { e.preventDefault(); modal.classList.remove('open'); }); } // clicking the modal's dark backdrop (the .modal_component itself) also closes it modal.addEventListener('click', e => { if (e.target === modal) modal.classList.remove('open'); }); } });