PageSourceSearch

https://microorganisms.com/js/marketplace-escrow-modal.js?v=1790698586

js microorganisms.com collected 2026-09-29 16:16:28 UTC 9,122 bytes, 244 lines download raw bytes

1/**
2 * Shared "Pay via Escrow.com" modal controller.
3 *
4 * Used by both the listing page (Buy Now) and the deal page (accepted offer).
5 * Exposes window.openEscrowModal({...}) so per-page handlers can trigger it
6 * without doing prompt+fetch themselves.
7 *
8 * Calling convention:
9 *
10 *   openEscrowModal({
11 *     mode:           'listing' | 'transaction',
12 *     listingId:      <int>          // when mode === 'listing'
13 *     transactionId:  <int>          // when mode === 'transaction'
14 *     triggerBtn:     <HTMLElement>  // optional; will be disabled while
15 *                                    // the request is in flight
16 *   });
17 *
18 * On success, redirects window.location to Escrow.com's agreement URL.
19 * On failure, surfaces the error inside the modal and keeps it open.
20 *
21 * The user's last-used email is remembered in localStorage for next time.
22 */
23(function () {
24    'use strict';
25
26    var LS_KEY            = 'mkt.escrowEmail';
27    var LS_EXPLAINER_SEEN = 'mkt.escrowExplainerSeen';
28
29    var modal, overlay, closeBtn, cancelBtn, continueBtn, emailInput, errorEl;
30    var explainerToggle, explainerPanel, explainerChevron, explainerLabel;
31    var currentMode = null;       // 'listing' or 'transaction'
32    var currentListingId = null;
33    var currentTxnId = null;
34    var currentTrigger = null;
35    var origTriggerLabel = '';
36    var initialized = false;
37
38    function init() {
39        if (initialized) return;
40        modal       = document.getElementById('escrowModal');
41        if (!modal) return; // page didn't include the partial; nothing to do
42        overlay     = document.getElementById('escrowModalOverlay');
43        closeBtn    = document.getElementById('escrowModalClose');
44        cancelBtn   = document.getElementById('escrowModalCancel');
45        continueBtn = document.getElementById('escrowModalContinue');
46        emailInput  = document.getElementById('escrowModalEmail');
47        errorEl     = document.getElementById('escrowModalError');
48
49        // Close handlers
50        closeBtn.addEventListener('click', closeModal);
51        cancelBtn.addEventListener('click', closeModal);
52        overlay.addEventListener('click', closeModal);
53        document.addEventListener('keydown', function (e) {
54            if (e.key === 'Escape' && isOpen()) closeModal();
55        });
56
57        // Submit handlers
58        continueBtn.addEventListener('click', submit);
59        emailInput.addEventListener('keydown', function (e) {
60            if (e.key === 'Enter') {
61                e.preventDefault();
62                submit();
63            }
64        });
65
66        // "What is Escrow.com?" explainer toggle (optional element)
67        explainerToggle  = document.getElementById('escrowExplainerToggle');
68        explainerPanel   = document.getElementById('escrowExplainerPanel');
69        explainerChevron = document.getElementById('escrowExplainerChevron');
70        explainerLabel   = document.getElementById('escrowExplainerToggleLabel');
71        if (explainerToggle && explainerPanel) {
72            explainerToggle.addEventListener('click', function () {
73                setExplainer(explainerPanel.hidden);
74            });
75        }
76
77        initialized = true;
78    }
79
80    function setExplainer(expanded) {
81        if (!explainerToggle || !explainerPanel) return;
82        explainerPanel.hidden = !expanded;
83        explainerToggle.setAttribute('aria-expanded', expanded ? 'true' : 'false');
84        if (explainerChevron) {
85            explainerChevron.style.transform = expanded ? 'rotate(180deg)' : 'rotate(0deg)';
86        }
87        if (explainerLabel) {
88            explainerLabel.textContent = expanded ? 'Hide details' : 'What is Escrow.com?';
89        }
90    }
91
92    function isOpen() {
93        return modal && modal.style.display !== 'none' && modal.style.display !== '';
94    }
95
96    function bestPrefill() {
97        // localStorage > server-rendered account email > empty.
98        try {
99            var fromLs = window.localStorage.getItem(LS_KEY);
100            if (fromLs) return fromLs;
101        } catch (e) { /* localStorage may throw in private mode */ }
102        return (modal && modal.dataset && modal.dataset.prefillEmail) || '';
103    }
104
105    function openModal(opts) {
106        init();
107        if (!modal) {
108            // No modal in this page — fall back to prompt() so we don't break
109            // the flow entirely. Should never happen if the partial is included.
110            var fallback = window.prompt('Enter your Escrow.com email:', bestPrefill());
111            if (!fallback) return;
112            doRequest(fallback, opts);
113            return;
114        }
115
116        currentMode      = opts.mode || null;
117        currentListingId = opts.listingId || null;
118        currentTxnId     = opts.transactionId || null;
119        currentTrigger   = opts.triggerBtn || null;
120        origTriggerLabel = currentTrigger ? currentTrigger.innerHTML : '';
121
122        // Reset state on each open
123        hideError();
124        setBusy(false);
125        emailInput.value = bestPrefill();
126
127        // First-time visitors see the "What is Escrow.com?" panel expanded by
128        // default so they don't have to discover it. Returning visitors get
129        // the cleaner collapsed view.
130        var firstTime = false;
131        try {
132            firstTime = !window.localStorage.getItem(LS_EXPLAINER_SEEN);
133            if (firstTime) window.localStorage.setItem(LS_EXPLAINER_SEEN, '1');
134        } catch (e) { /* localStorage may throw in private mode */ }
135        setExplainer(firstTime);
136
137        modal.style.display = 'flex';
138        // Defer focus so the input is reachable after layout settles
139        setTimeout(function () { emailInput.focus(); emailInput.select(); }, 0);
140    }
141
142    function closeModal() {
143        if (!modal) return;
144        modal.style.display = 'none';
145        // Restore trigger if we left it spinning
146        restoreTrigger();
147        currentMode = null;
148        currentListingId = null;
149        currentTxnId = null;
150        currentTrigger = null;
151    }
152
153    function setBusy(busy) {
154        continueBtn.disabled = busy;
155        cancelBtn.disabled   = busy;
156        emailInput.disabled  = busy;
157        continueBtn.innerHTML = busy
158            ? '<i class="fas fa-spinner fa-spin"></i> Creating escrow transaction...'
159            : 'Continue to Escrow.com';
160    }
161
162    function showError(msg) {
163        errorEl.textContent = msg;
164        errorEl.hidden = false;
165    }
166
167    function hideError() {
168        errorEl.textContent = '';
169        errorEl.hidden = true;
170    }
171
172    function restoreTrigger() {
173        if (currentTrigger && origTriggerLabel) {
174            currentTrigger.disabled = false;
175            currentTrigger.innerHTML = origTriggerLabel;
176        }
177    }
178
179    function submit() {
180        hideError();
181        var email = (emailInput.value || '').trim();
182        if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
183            showError('Please enter a valid email address.');
184            emailInput.focus();
185            return;
186        }
187
188        setBusy(true);
189        if (currentTrigger) {
190            currentTrigger.disabled = true;
191            currentTrigger.innerHTML = '<i class="fas fa-spinner fa-spin"></i> ...';
192        }
193
194        doRequest(email, {
195            mode: currentMode,
196            listingId: currentListingId,
197            transactionId: currentTxnId
198        });
199    }
200
201    function doRequest(email, opts) {
202        var payload = { buyer_email: email };
203        if (opts.mode === 'transaction' && opts.transactionId) {
204            payload.transaction_id = opts.transactionId;
205        } else if (opts.mode === 'listing' && opts.listingId) {
206            payload.listing_id = opts.listingId;
207        } else {
208            setBusy(false);
209            showError('Internal error — missing listing or transaction id.');
210            return;
211        }
212
213        fetch('/api/marketplace/escrow-checkout', {
214            method: 'POST',
215            headers: { 'Content-Type': 'application/json' },
216            body: JSON.stringify(payload)
217        }).then(function (r) {
218            return r.json().then(function (d) { return { ok: r.ok, data: d }; });
219        }).then(function (res) {
220            if (res.ok && res.data.success && res.data.agreement_url) {
221                // Remember the email for next time on this device.
222                try { window.localStorage.setItem(LS_KEY, email); } catch (e) { /* ignore */ }
223                window.location = res.data.agreement_url;
224                return;
225            }
226            setBusy(false);
227            showError((res.data && res.data.error) || 'Could not start the Escrow.com transaction. Please try again.');
228        }).catch(function () {
229            setBusy(false);
230            showError('Network error. Please try again.');
231        });
232    }
233
234    // Expose to per-page handlers
235    window.openEscrowModal = openModal;
236
237    // Auto-init when DOM is ready (handles the case where this script is
238    // included after the partial but before the page's other handlers).
239    if (document.readyState === 'loading') {
240        document.addEventListener('DOMContentLoaded', init);
241    } else {
242        init();
243    }
244})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.