From 6a034de7111566f5205f3b9a5d8722e483179769 Mon Sep 17 00:00:00 2001 From: anik-fahmid Date: Fri, 20 Feb 2026 12:33:18 +0600 Subject: [PATCH 1/5] payment page redesing --- assets/js/admin/settings.js | 329 ++++++++++++++++++++++++++++++++++-- 1 file changed, 312 insertions(+), 17 deletions(-) diff --git a/assets/js/admin/settings.js b/assets/js/admin/settings.js index f02ae4839..5359f3e55 100644 --- a/assets/js/admin/settings.js +++ b/assets/js/admin/settings.js @@ -1,59 +1,59 @@ (function () { - document.addEventListener('DOMContentLoaded',function () { - var tabs = document.querySelector('.wpuf-settings-wrap').querySelectorAll('h2 a'); + document.addEventListener('DOMContentLoaded', function () { + var tabs = document.querySelector('.wpuf-settings-wrap').querySelectorAll('h2 a'); var content = document.querySelectorAll('.wpuf-settings-wrap .metabox-holder th'); - var close = document.querySelector('#wpuf-search-section span'); + var close = document.querySelector('#wpuf-search-section span'); var search_input = document.querySelector('#wpuf-settings-search'); search_input.addEventListener('keyup', function (e) { var search_value = e.target.value.toLowerCase(); - var value_tab = []; + var value_tab = []; - if ( search_value.length ) { + if (search_value.length) { close.style.display = 'flex'; content.forEach(function (row, index) { var content_id = row.closest('div').getAttribute('id'); - var tab_id = content_id + '-tab'; - var found_value = row.innerText.toLowerCase().includes( search_value ); + var tab_id = content_id + '-tab'; + var found_value = row.innerText.toLowerCase().includes(search_value); - if ( found_value ){ + if (found_value) { row.closest('tr').style.display = 'table-row'; - }else { + } else { row.closest('tr').style.display = 'none'; } - if ( 'wpuf_mails' === content_id ){ + if ('wpuf_mails' === content_id) { row.closest('tbody').querySelectorAll('tr').forEach(function (tr) { tr.style.display = ''; }); } - if ( found_value === true && ! value_tab.includes( tab_id ) ) { + if (found_value === true && !value_tab.includes(tab_id)) { value_tab.push(tab_id); } }) - if ( value_tab.length ) { + if (value_tab.length) { document.getElementById(value_tab[0]).click(); } tabs.forEach(function (tab) { var tab_id = tab.getAttribute('id'); - if ( ! value_tab.includes( tab_id ) ){ + if (!value_tab.includes(tab_id)) { document.getElementById(tab_id).style.display = 'none'; - }else { + } else { document.getElementById(tab_id).style.display = 'block'; } }); - }else { + } else { wpuf_search_reset(); } }) - close.addEventListener('click',function (event) { + close.addEventListener('click', function (event) { wpuf_search_reset(); search_input.value = ''; close.style.display = 'none'; @@ -62,7 +62,7 @@ function wpuf_search_reset() { content.forEach(function (row, index) { var content_id = row.closest('div').getAttribute('id'); - var tab_id = content_id + '-tab'; + var tab_id = content_id + '-tab'; document.getElementById(content_id).style.display = ''; document.getElementById(tab_id).style.display = ''; document.getElementById('wpuf_general-tab').click(); @@ -72,5 +72,300 @@ }); } + + // --- Accordion for Payment Gateways --- + var paymentTab = document.getElementById('wpuf_payment'); + if (paymentTab) { + var table = paymentTab.querySelector('table.form-table'); + if (table) { + var trs = Array.from(table.querySelectorAll('tbody > tr')); + var groups = { + paypal: { + title: 'PayPal', + icon: '', + rows: [], + existingCheckbox: null + }, + bank: { + title: 'Bank Transfer', + icon: '', + rows: [], + existingCheckbox: null + }, + stripe: { + title: 'Credit Card', + icon: '', + rows: [], + existingCheckbox: null + } + }; + + var allowGatewaysTr = null; + + trs.forEach(function (tr) { + var html = tr.innerHTML; + if (html.includes('wpuf_payment[active_gateways]')) { + allowGatewaysTr = tr; + } + }); + + if (allowGatewaysTr) { + var inputs = allowGatewaysTr.querySelectorAll('input[type="checkbox"]'); + inputs.forEach(function (input) { + var k = input.value; + if (groups[k]) { + var wrapperLabel = input.closest('label'); + groups[k].existingCheckbox = wrapperLabel; // Keep a reference to the label containing the checkbox + + // Let's remove any
right after the label to keep HTML clean later + if (wrapperLabel && wrapperLabel.nextSibling && wrapperLabel.nextSibling.nodeName === 'BR') { + wrapperLabel.nextSibling.remove(); + } + } + }); + + allowGatewaysTr.style.display = 'none'; + } + + trs.forEach(function (tr) { + if (tr === allowGatewaysTr) return; + + var th = tr.querySelector('th'); + var label = th ? th.querySelector('label') : null; + var forAttr = label ? label.getAttribute('for') || '' : ''; + + var isPaypal = forAttr.includes('paypal'); + var isBank = forAttr.includes('bank'); + var isStripe = forAttr.includes('stripe'); + + var contentMatches = tr.innerHTML; + + if (!isPaypal && !isBank && !isStripe) { + if (contentMatches.includes('wpuf_payment[paypal') || contentMatches.includes('wpuf-paypal-webhook') || contentMatches.includes('[gate_instruct_paypal]')) { + isPaypal = true; + } else if (contentMatches.includes('wpuf_payment[bank') || contentMatches.includes('[gate_instruct_bank]')) { + isBank = true; + } else if (contentMatches.includes('wpuf_payment[stripe') || contentMatches.includes('[gate_instruct_stripe]')) { + isStripe = true; + } + } + + if (isPaypal) groups.paypal.rows.push(tr); + else if (isBank) groups.bank.rows.push(tr); + else if (isStripe) groups.stripe.rows.push(tr); + }); + + var style = document.createElement('style'); + style.innerHTML = ` + .wpuf-accordion-trigger { + background: #f1f1f1; + color: #333; + cursor: pointer; + padding: 15px; + width: calc(100% - 25px); + border: 1px solid #ccc; + box-sizing: border-box; + text-align: left; + outline: none; + font-size: 15px; + transition: 0.4s; + border-radius: 4px; + display: flex; + align-items: center; + font-weight: 600; + margin-top: 20px; + margin-bottom: 5px; + } + .wpuf-accordion-trigger.active, .wpuf-accordion-trigger:hover { + background: #ddd; + } + .wpuf-accordion-trigger:after { + content: ''; + display: block; + width: 8px; + height: 8px; + border-right: 2px solid #777; + border-bottom: 2px solid #777; + transform: translateY(-2px) rotate(45deg); + float: right; + margin-left: auto; + transition: transform 0.3s ease; + } + .wpuf-accordion-trigger.active:after { + transform: translateY(2px) rotate(225deg); + } + .wpuf-accordion-row { + display: none; + } + .wpuf-accordion-row.active { + display: table-row; + } + .wpuf-accordion-trigger.is-disabled { + background: #f9f9f9; + color: #888; + } + .wpuf-accordion-trigger.is-disabled .wpuf-svg-icon-bg { + border-color: #ccc !important; + color: #ccc !important; + } + .wpuf-accordion-trigger.is-disabled:hover { + background: #f9f9f9; + } + .wpuf-accordion-trigger.is-disabled:after { + display: none; + } + `; + document.head.appendChild(style); + + var theTbody = table.querySelector('tbody'); + + var fallbackProIconSvg = ''; + + // Resolve the existing PRO badge img — search ALL tabs including hidden ones + var existingProIconSrc = ''; + var proIconImg = document.querySelector('.pro-icon img'); + if (!proIconImg) { + // Also try settings fields that may be hidden + proIconImg = document.querySelector('span.pro-icon img'); + } + if (proIconImg) { + existingProIconSrc = 'PRO'; + } else { + // Derive path from any known plugin img on the page, or use absolute path pattern + var anyImg = document.querySelector('img[src*="wp-user-frontend"][src*="assets"]'); + if (anyImg) { + var baseUrl = anyImg.getAttribute('src').split('/assets/')[0]; + existingProIconSrc = 'PRO'; + } else { + existingProIconSrc = fallbackProIconSvg; + } + } + + Object.keys(groups).forEach(function (key) { + var group = groups[key]; + var isProFeatureDisabled = (key === 'stripe' && !group.existingCheckbox); + + if (group.existingCheckbox) { + var checkboxTr = document.createElement('tr'); + var checkboxTh = document.createElement('th'); + checkboxTh.scope = 'row'; + + if (key === 'stripe') { + // Build label with PRO badge — same pattern as native PRO-gated settings + var enableLbl = document.createElement('label'); + enableLbl.appendChild(document.createTextNode('Enable Gateway ')); + var proBadgeSpan = document.createElement('span'); + proBadgeSpan.className = 'pro-icon'; + proBadgeSpan.innerHTML = existingProIconSrc; + enableLbl.appendChild(proBadgeSpan); + checkboxTh.appendChild(enableLbl); + } else { + checkboxTh.innerHTML = 'Enable Gateway'; + } + + var checkboxTd = document.createElement('td'); + + var newFieldset = document.createElement('fieldset'); + newFieldset.appendChild(group.existingCheckbox); + checkboxTd.appendChild(newFieldset); + + checkboxTr.appendChild(checkboxTh); + checkboxTr.appendChild(checkboxTd); + + // Push into the array at the beginning + group.rows.unshift(checkboxTr); + } else if (isProFeatureDisabled) { + var dummyTr = document.createElement('tr'); + var dummyTh = document.createElement('th'); + dummyTh.scope = 'row'; + + // "Enable Gateway" label + PRO icon badge — same pattern used throughout the plugin: + // + var enableLabel = document.createElement('label'); + enableLabel.appendChild(document.createTextNode('Enable Gateway ')); + var proIconSpan = document.createElement('span'); + proIconSpan.className = 'pro-icon'; + proIconSpan.innerHTML = existingProIconSrc; + enableLabel.appendChild(proIconSpan); + dummyTh.appendChild(enableLabel); + + var dummyTd = document.createElement('td'); + dummyTd.innerHTML = '

Available with WPUF Pro.

'; + + dummyTr.appendChild(dummyTh); + dummyTr.appendChild(dummyTd); + group.rows.push(dummyTr); + } + + if (group.rows.length === 0) return; + + var triggerRow = document.createElement('tr'); + var triggerCell = document.createElement('td'); + triggerCell.colSpan = 2; + triggerCell.style.padding = '0'; + + var triggerBtn = document.createElement('button'); + triggerBtn.type = 'button'; + triggerBtn.className = 'wpuf-accordion-trigger'; + triggerBtn.setAttribute('data-target-group', key); + + var titleSpan = document.createElement('span'); + titleSpan.innerHTML = group.icon + group.title; + titleSpan.style.display = 'flex'; + titleSpan.style.alignItems = 'center'; + + if (isProFeatureDisabled) { + triggerBtn.classList.add('is-disabled'); + var badgeWrapper = document.createElement('div'); + badgeWrapper.innerHTML = '' + existingProIconSrc + ''; + badgeWrapper.style.display = 'inline-flex'; + badgeWrapper.style.alignItems = 'center'; + titleSpan.appendChild(badgeWrapper); + } + + triggerBtn.appendChild(titleSpan); + triggerCell.appendChild(triggerBtn); + triggerRow.appendChild(triggerCell); + + theTbody.appendChild(triggerRow); + + // We add a specific class to the gateway setting rows to hide them by default + group.rows.forEach(function (r) { + r.classList.add('wpuf-accordion-row'); + r.setAttribute('data-group', key); + theTbody.appendChild(r); + }); + + // Event listener for toggle + triggerBtn.addEventListener('click', function (e) { + e.preventDefault(); + + if (isProFeatureDisabled) { + return; + } + + var isActive = this.classList.contains('active'); + + // Close all accordions + document.querySelectorAll('.wpuf-accordion-trigger').forEach(function (btn) { + btn.classList.remove('active'); + var g = btn.getAttribute('data-target-group'); + document.querySelectorAll('tr[data-group="' + g + '"]').forEach(function (tr) { + tr.classList.remove('active'); + }); + }); + + // Toggle current + if (!isActive) { + this.classList.add('active'); + document.querySelectorAll('tr[data-group="' + key + '"]').forEach(function (tr) { + tr.classList.add('active'); + }); + } + }); + }); + } + } + }); })(); From 4a66b9ef02dbb4a50f468a86ab1d495cf91dbd15 Mon Sep 17 00:00:00 2001 From: Rubaiyat E Mohammad Date: Wed, 25 Feb 2026 15:11:36 +0600 Subject: [PATCH 2/5] Update settings.js --- assets/js/admin/settings.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/assets/js/admin/settings.js b/assets/js/admin/settings.js index 5359f3e55..69b3b0c19 100644 --- a/assets/js/admin/settings.js +++ b/assets/js/admin/settings.js @@ -243,25 +243,26 @@ Object.keys(groups).forEach(function (key) { var group = groups[key]; - var isProFeatureDisabled = (key === 'stripe' && !group.existingCheckbox); + + // For stripe, check if it's a PRO preview (Free version adds stripe checkbox with PRO badge in label) + // PRO is disabled if: + // 1. No existingCheckbox exists (stripe not registered at all), OR + // 2. existingCheckbox exists but contains a PRO icon (Free_Loader adds it for preview) + var hasProBadgeInLabel = false; + if (key === 'stripe' && group.existingCheckbox) { + // Check if the checkbox label contains the PRO icon/badge + hasProBadgeInLabel = group.existingCheckbox.querySelector('.pro-icon, .pro-icon-title, img[src*="pro-badge"]') !== null; + } + var isProFeatureDisabled = (key === 'stripe' && (!group.existingCheckbox || hasProBadgeInLabel)); - if (group.existingCheckbox) { + if (group.existingCheckbox && !hasProBadgeInLabel) { + // PRO is active - stripe checkbox exists without PRO badge var checkboxTr = document.createElement('tr'); var checkboxTh = document.createElement('th'); checkboxTh.scope = 'row'; - if (key === 'stripe') { - // Build label with PRO badge — same pattern as native PRO-gated settings - var enableLbl = document.createElement('label'); - enableLbl.appendChild(document.createTextNode('Enable Gateway ')); - var proBadgeSpan = document.createElement('span'); - proBadgeSpan.className = 'pro-icon'; - proBadgeSpan.innerHTML = existingProIconSrc; - enableLbl.appendChild(proBadgeSpan); - checkboxTh.appendChild(enableLbl); - } else { - checkboxTh.innerHTML = 'Enable Gateway'; - } + // When PRO is truly active, no PRO badge needed + checkboxTh.innerHTML = 'Enable Gateway'; var checkboxTd = document.createElement('td'); From 175195d99dfe1ba7b7bb24194178a57d75bcd825 Mon Sep 17 00:00:00 2001 From: Rubaiyat E Mohammad Date: Sun, 26 Apr 2026 01:18:08 +0600 Subject: [PATCH 3/5] Refactor payment gateway accordion styling and DOM build Move inline accordion styles and gateway icon SVGs from settings.js into assets/css/admin/settings.css and enqueue it on the settings page. Replace innerHTML/string-built nodes with createElement to avoid HTML injection paths, and use the data-gateway-id attribute set by the gateway selector instead of substring-matching row markup. Stripe PRO upsell now triggers on empty rows rather than label inspection. Co-Authored-By: Claude Opus 4.7 (1M context) --- assets/css/admin/settings.css | 188 ++++++++++++++++++++++++++++++ assets/js/admin/settings.js | 207 +++++++++------------------------- includes/Admin/Menu.php | 1 + includes/Assets.php | 3 + 4 files changed, 246 insertions(+), 153 deletions(-) create mode 100644 assets/css/admin/settings.css diff --git a/assets/css/admin/settings.css b/assets/css/admin/settings.css new file mode 100644 index 000000000..19ca7fc7d --- /dev/null +++ b/assets/css/admin/settings.css @@ -0,0 +1,188 @@ +/* Payment Gateway Accordion */ +.wpuf-accordion-trigger { + background: #f1f1f1; + color: #333; + cursor: pointer; + padding: 15px; + width: calc(100% - 25px); + border: 1px solid #ccc; + box-sizing: border-box; + text-align: left; + outline: none; + font-size: 15px; + transition: 0.4s; + border-radius: 4px; + display: flex; + align-items: center; + font-weight: 600; + margin-top: 20px; + margin-bottom: 5px; +} +.wpuf-accordion-trigger.active, +.wpuf-accordion-trigger:hover { + background: #ddd; +} +.wpuf-accordion-trigger:after { + content: ''; + display: block; + width: 8px; + height: 8px; + border-right: 2px solid #777; + border-bottom: 2px solid #777; + transform: translateY(-2px) rotate(45deg); + float: right; + margin-left: auto; + transition: transform 0.3s ease; +} +.wpuf-accordion-trigger.active:after { + transform: translateY(2px) rotate(225deg); +} +.wpuf-accordion-row { + display: none !important; +} +.wpuf-accordion-row.active { + display: table-row !important; +} +.wpuf-accordion-trigger.is-disabled, +.wpuf-accordion-trigger.is-disabled:hover { + background: #f9f9f9; + color: #888; +} +.wpuf-accordion-trigger.is-disabled .wpuf-gateway-icon { + border-color: #ccc; + color: #ccc; +} +.wpuf-accordion-trigger.is-disabled:after { + display: none; +} + +/* Gateway icons (color-tinted SVG via CSS mask) */ +.wpuf-gateway-icon { + box-sizing: border-box; + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 50%; + border: 1.5px solid currentColor; + background: transparent; + margin-right: 10px; +} +.wpuf-gateway-icon::before { + content: ""; + display: block; + width: 14px; + height: 14px; + background-color: currentColor; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-size: contain; + mask-size: contain; +} +.wpuf-gateway-icon--paypal { + border: none; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: center; + background-size: contain; +} +.wpuf-gateway-icon--paypal::before { + display: none; +} +.wpuf-gateway-icon--bank { + border: none; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: center; + background-size: contain; +} +.wpuf-gateway-icon--bank::before { + display: none; +} +.wpuf-gateway-icon--stripe { + border: none; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: center; + background-size: contain; +} +.wpuf-gateway-icon--stripe::before { + display: none; +} + +/* Pro badge fallback (used only when plugin's pro-badge.svg cannot be located on page) */ +.wpuf-pro-badge-fallback { + display: inline-block; + width: 20px; + height: 20px; + vertical-align: middle; + background-color: #F59E0B; + -webkit-mask: url("data:image/svg+xml;utf8,") no-repeat center / contain; + mask: url("data:image/svg+xml;utf8,") no-repeat center / contain; +} + +/* Pro upsell paragraph in disabled gateway row */ +.wpuf-gateway-pro-upsell { + color: #888; + margin: 0; + font-style: italic; +} +.wpuf-gateway-pro-upsell a { + color: #0073aa; + font-weight: 600; +} + +/* Pro badge wrapper next to disabled trigger title */ +.wpuf-gateway-pro-badge { + display: inline-flex; + align-items: center; +} +.wpuf-gateway-pro-badge .pro-icon { + margin-left: 8px; + display: inline-flex; + align-items: center; +} + +/* Gateway selector cards — shrink + use brand SVG icons */ +.wpuf-gateway-card { + width: 78px !important; + padding: 10px 6px 6px !important; + border-width: 1px !important; +} +.wpuf-gateway-card__icon { + height: 26px !important; + margin-bottom: 4px !important; + background-repeat: no-repeat; + background-position: center; + background-size: contain; +} +.wpuf-gateway-card__name { + font-size: 11px !important; +} +.wpuf-gateway-card__toggle { + top: 2px !important; + right: 2px !important; + padding: 2px !important; +} +.wpuf-gateway-card__check-on, +.wpuf-gateway-card__check-off { + width: 14px !important; + height: 14px !important; +} +.wpuf-gateway-card[data-gateway="paypal"] .wpuf-gateway-card__icon > *, +.wpuf-gateway-card[data-gateway="bank"] .wpuf-gateway-card__icon > *, +.wpuf-gateway-card[data-gateway="stripe"] .wpuf-gateway-card__icon > * { + display: none !important; +} +.wpuf-gateway-card[data-gateway="paypal"] .wpuf-gateway-card__icon { + background-image: url("data:image/svg+xml;utf8,"); +} +.wpuf-gateway-card[data-gateway="bank"] .wpuf-gateway-card__icon { + background-image: url("data:image/svg+xml;utf8,"); +} +.wpuf-gateway-card[data-gateway="stripe"] .wpuf-gateway-card__icon { + background-image: url("data:image/svg+xml;utf8,"); +} diff --git a/assets/js/admin/settings.js b/assets/js/admin/settings.js index 747fef4f9..68560100c 100644 --- a/assets/js/admin/settings.js +++ b/assets/js/admin/settings.js @@ -85,19 +85,19 @@ var groups = { paypal: { title: 'PayPal', - icon: '', + iconClass: 'wpuf-gateway-icon wpuf-gateway-icon--paypal', rows: [], existingCheckbox: null }, bank: { title: 'Bank Transfer', - icon: '', + iconClass: 'wpuf-gateway-icon wpuf-gateway-icon--bank', rows: [], existingCheckbox: null }, stripe: { title: 'Credit Card', - icon: '', + iconClass: 'wpuf-gateway-icon wpuf-gateway-icon--stripe', rows: [], existingCheckbox: null } @@ -112,173 +112,59 @@ } }); - if (allowGatewaysTr) { - var inputs = allowGatewaysTr.querySelectorAll('input[type="checkbox"]'); - inputs.forEach(function (input) { - var k = input.value; - if (groups[k]) { - var wrapperLabel = input.closest('label'); - groups[k].existingCheckbox = wrapperLabel; // Keep a reference to the label containing the checkbox - - // Let's remove any
right after the label to keep HTML clean later - if (wrapperLabel && wrapperLabel.nextSibling && wrapperLabel.nextSibling.nodeName === 'BR') { - wrapperLabel.nextSibling.remove(); - } - } - }); - - allowGatewaysTr.style.display = 'none'; - } + // Card grid in allowGatewaysTr handles enable/disable. + // Do NOT hide it and do NOT move checkboxes — accordion only groups per-gateway settings rows below. + // Use the data-gateway-id tag set by wpufInitGatewaySelector (runs before this block). + // This includes mapped fields like failed_retry → paypal. trs.forEach(function (tr) { if (tr === allowGatewaysTr) return; - - var th = tr.querySelector('th'); - var label = th ? th.querySelector('label') : null; - var forAttr = label ? label.getAttribute('for') || '' : ''; - - var isPaypal = forAttr.includes('paypal'); - var isBank = forAttr.includes('bank'); - var isStripe = forAttr.includes('stripe'); - - var contentMatches = tr.innerHTML; - - if (!isPaypal && !isBank && !isStripe) { - if (contentMatches.includes('wpuf_payment[paypal') || contentMatches.includes('wpuf-paypal-webhook') || contentMatches.includes('[gate_instruct_paypal]')) { - isPaypal = true; - } else if (contentMatches.includes('wpuf_payment[bank') || contentMatches.includes('[gate_instruct_bank]')) { - isBank = true; - } else if (contentMatches.includes('wpuf_payment[stripe') || contentMatches.includes('[gate_instruct_stripe]')) { - isStripe = true; - } + var gid = tr.getAttribute('data-gateway-id'); + if (gid && groups[gid]) { + groups[gid].rows.push(tr); } - - if (isPaypal) groups.paypal.rows.push(tr); - else if (isBank) groups.bank.rows.push(tr); - else if (isStripe) groups.stripe.rows.push(tr); }); - var style = document.createElement('style'); - style.innerHTML = ` - .wpuf-accordion-trigger { - background: #f1f1f1; - color: #333; - cursor: pointer; - padding: 15px; - width: calc(100% - 25px); - border: 1px solid #ccc; - box-sizing: border-box; - text-align: left; - outline: none; - font-size: 15px; - transition: 0.4s; - border-radius: 4px; - display: flex; - align-items: center; - font-weight: 600; - margin-top: 20px; - margin-bottom: 5px; - } - .wpuf-accordion-trigger.active, .wpuf-accordion-trigger:hover { - background: #ddd; - } - .wpuf-accordion-trigger:after { - content: ''; - display: block; - width: 8px; - height: 8px; - border-right: 2px solid #777; - border-bottom: 2px solid #777; - transform: translateY(-2px) rotate(45deg); - float: right; - margin-left: auto; - transition: transform 0.3s ease; - } - .wpuf-accordion-trigger.active:after { - transform: translateY(2px) rotate(225deg); - } - .wpuf-accordion-row { - display: none; - } - .wpuf-accordion-row.active { - display: table-row; - } - .wpuf-accordion-trigger.is-disabled { - background: #f9f9f9; - color: #888; - } - .wpuf-accordion-trigger.is-disabled .wpuf-svg-icon-bg { - border-color: #ccc !important; - color: #ccc !important; - } - .wpuf-accordion-trigger.is-disabled:hover { - background: #f9f9f9; - } - .wpuf-accordion-trigger.is-disabled:after { - display: none; - } - `; - document.head.appendChild(style); - var theTbody = table.querySelector('tbody'); - var fallbackProIconSvg = ''; - - // Resolve the existing PRO badge img — search ALL tabs including hidden ones - var existingProIconSrc = ''; + // Resolve the existing PRO badge img URL — search ALL tabs including hidden ones + var proIconSrcUrl = ''; var proIconImg = document.querySelector('.pro-icon img'); if (!proIconImg) { - // Also try settings fields that may be hidden proIconImg = document.querySelector('span.pro-icon img'); } if (proIconImg) { - existingProIconSrc = 'PRO'; + proIconSrcUrl = proIconImg.getAttribute('src'); } else { - // Derive path from any known plugin img on the page, or use absolute path pattern var anyImg = document.querySelector('img[src*="wp-user-frontend"][src*="assets"]'); if (anyImg) { - var baseUrl = anyImg.getAttribute('src').split('/assets/')[0]; - existingProIconSrc = 'PRO'; - } else { - existingProIconSrc = fallbackProIconSvg; + proIconSrcUrl = anyImg.getAttribute('src').split('/assets/')[0] + '/assets/images/pro-badge.svg'; } } + function createProIconNode() { + if (proIconSrcUrl) { + var img = document.createElement('img'); + img.src = proIconSrcUrl; + img.alt = 'PRO'; + img.style.verticalAlign = 'middle'; + return img; + } + var fallback = document.createElement('span'); + fallback.className = 'wpuf-pro-badge-fallback'; + fallback.setAttribute('role', 'img'); + fallback.setAttribute('aria-label', 'PRO'); + return fallback; + } + Object.keys(groups).forEach(function (key) { var group = groups[key]; - // For stripe, check if it's a PRO preview (Free version adds stripe checkbox with PRO badge in label) - // PRO is disabled if: - // 1. No existingCheckbox exists (stripe not registered at all), OR - // 2. existingCheckbox exists but contains a PRO icon (Free_Loader adds it for preview) - var hasProBadgeInLabel = false; - if (key === 'stripe' && group.existingCheckbox) { - // Check if the checkbox label contains the PRO icon/badge - hasProBadgeInLabel = group.existingCheckbox.querySelector('.pro-icon, .pro-icon-title, img[src*="pro-badge"]') !== null; - } - var isProFeatureDisabled = (key === 'stripe' && (!group.existingCheckbox || hasProBadgeInLabel)); - - if (group.existingCheckbox && !hasProBadgeInLabel) { - // PRO is active - stripe checkbox exists without PRO badge - var checkboxTr = document.createElement('tr'); - var checkboxTh = document.createElement('th'); - checkboxTh.scope = 'row'; - - // When PRO is truly active, no PRO badge needed - checkboxTh.innerHTML = 'Enable Gateway'; + // Stripe is disabled only when no stripe settings rows are registered in PHP + // (PRO off / stripe module off). If PRO active + stripe module on, rows > 0. + var isProFeatureDisabled = (key === 'stripe' && group.rows.length === 0); - var checkboxTd = document.createElement('td'); - - var newFieldset = document.createElement('fieldset'); - newFieldset.appendChild(group.existingCheckbox); - checkboxTd.appendChild(newFieldset); - - checkboxTr.appendChild(checkboxTh); - checkboxTr.appendChild(checkboxTd); - - // Push into the array at the beginning - group.rows.unshift(checkboxTr); - } else if (isProFeatureDisabled) { + if (isProFeatureDisabled) { var dummyTr = document.createElement('tr'); var dummyTh = document.createElement('th'); dummyTh.scope = 'row'; @@ -289,12 +175,21 @@ enableLabel.appendChild(document.createTextNode('Enable Gateway ')); var proIconSpan = document.createElement('span'); proIconSpan.className = 'pro-icon'; - proIconSpan.innerHTML = existingProIconSrc; + proIconSpan.appendChild(createProIconNode()); enableLabel.appendChild(proIconSpan); dummyTh.appendChild(enableLabel); var dummyTd = document.createElement('td'); - dummyTd.innerHTML = '

Available with WPUF Pro.

'; + var upsellP = document.createElement('p'); + upsellP.className = 'wpuf-gateway-pro-upsell'; + upsellP.appendChild(document.createTextNode('Available with ')); + var upsellLink = document.createElement('a'); + upsellLink.href = 'https://wedevs.com/wp-user-frontend-pro/'; + upsellLink.target = '_blank'; + upsellLink.textContent = 'WPUF Pro'; + upsellP.appendChild(upsellLink); + upsellP.appendChild(document.createTextNode('.')); + dummyTd.appendChild(upsellP); dummyTr.appendChild(dummyTh); dummyTr.appendChild(dummyTd); @@ -314,16 +209,22 @@ triggerBtn.setAttribute('data-target-group', key); var titleSpan = document.createElement('span'); - titleSpan.innerHTML = group.icon + group.title; titleSpan.style.display = 'flex'; titleSpan.style.alignItems = 'center'; + var iconSpan = document.createElement('span'); + iconSpan.className = group.iconClass; + titleSpan.appendChild(iconSpan); + titleSpan.appendChild(document.createTextNode(group.title)); if (isProFeatureDisabled) { triggerBtn.classList.add('is-disabled'); var badgeWrapper = document.createElement('div'); - badgeWrapper.innerHTML = '' + existingProIconSrc + ''; - badgeWrapper.style.display = 'inline-flex'; - badgeWrapper.style.alignItems = 'center'; + badgeWrapper.className = 'wpuf-gateway-pro-badge'; + var badgeSpan = document.createElement('span'); + badgeSpan.className = 'pro-icon'; + badgeSpan.title = 'Pro Feature'; + badgeSpan.appendChild(createProIconNode()); + badgeWrapper.appendChild(badgeSpan); titleSpan.appendChild(badgeWrapper); } diff --git a/includes/Admin/Menu.php b/includes/Admin/Menu.php index 629286c65..69731d191 100644 --- a/includes/Admin/Menu.php +++ b/includes/Admin/Menu.php @@ -367,6 +367,7 @@ public function tools_page() { public function enqueue_settings_page_scripts() { wp_enqueue_script( 'wpuf-subscriptions' ); wp_enqueue_script( 'wpuf-settings' ); + wp_enqueue_style( 'wpuf-settings' ); } /** diff --git a/includes/Assets.php b/includes/Assets.php index 278972d3f..3e9851eae 100644 --- a/includes/Assets.php +++ b/includes/Assets.php @@ -171,6 +171,9 @@ public function get_styles() { 'admin' => [ 'src' => WPUF_ASSET_URI . '/css/admin.css', ], + 'settings' => [ + 'src' => WPUF_ASSET_URI . '/css/admin/settings.css', + ], 'admin-subscriptions' => [ 'src' => WPUF_ASSET_URI . '/css/admin/subscriptions.min.css', ], From 5f0280b4a23c8f44d7cc1b30ad60be24cf11d3f4 Mon Sep 17 00:00:00 2001 From: Rubaiyat E Mohammad Date: Wed, 29 Apr 2026 17:36:55 +0600 Subject: [PATCH 4/5] Payment gateway settings redesign --- Lib/WeDevs_Settings_API.php | 55 ++--- assets/css/admin.css | 48 +--- assets/css/admin/settings.css | 213 +++++------------- assets/js/admin/settings.js | 404 ++++++++++------------------------ assets/less/admin.less | 51 +---- includes/Admin/Menu.php | 9 + wpuf-functions.php | 52 ++++- 7 files changed, 262 insertions(+), 570 deletions(-) diff --git a/Lib/WeDevs_Settings_API.php b/Lib/WeDevs_Settings_API.php index 48cffe45b..836d73ddb 100644 --- a/Lib/WeDevs_Settings_API.php +++ b/Lib/WeDevs_Settings_API.php @@ -345,11 +345,15 @@ function callback_multicheck( $args ) { } /** - * Displays a Texty-style card grid for selecting payment gateways + * Displays a card grid for selecting payment gateways. * - * Renders each gateway as a clickable card with icon and name. - * Multiple cards can be checked (multi-select). Clicking a card - * also reveals that gateway's settings panel below the grid. + * Each gateway renders as a focusable card. Clicking (or pressing + * Enter/Space on) a card opens its settings panel; settings rows for + * non-focused gateways stay hidden. The hidden card checkbox is driven + * by an "Enable Gateway" toggle injected at the top of each panel by + * the settings JS — that is the only control that enables/disables + * the gateway. Pro-preview gateways are filtered out before reaching + * this callback (see wpuf_get_gateways() with 'gateway_selector' context). * * @since 4.3.1 * @@ -361,8 +365,8 @@ function callback_gateway_selector( $args ) { $value = $this->get_option( $args['id'], $args['section'], $args['std'] ); $value = $value ? $value : []; - // Inline SVG fallback icons (no image files exist for these) - $bank_svg = ''; + // Generic fallback icon for unknown gateways without a registered icon URL. + // Known gateways (paypal/bank/stripe) get branded icons via assets/css/admin/settings.css. $generic_svg = ''; ?>
@@ -370,49 +374,36 @@ function callback_gateway_selector( $args ) {
$gateway ) : - $is_checked = in_array( $key, $value, true ); - $is_pro = ! empty( $gateway['is_pro_preview'] ) && $gateway['is_pro_preview']; - $disabled = $is_pro ? 'disabled' : ''; - $active_class = $is_checked ? ' wpuf-gateway-card--active' : ''; - $pro_class = $is_pro ? ' wpuf-gateway-card--pro-locked' : ''; - $icon = ! empty( $gateway['icon'] ) ? $gateway['icon'] : ''; - $admin_label = $gateway['admin_label']; + $is_checked = in_array( $key, $value, true ); + $active_class = $is_checked ? ' wpuf-gateway-card--active' : ''; + $icon = ! empty( $gateway['icon'] ) ? $gateway['icon'] : ''; + // Strip any HTML embedded in the registered admin_label so it cannot + // leak markup into the card name or downstream JS read of textContent. + $admin_label = wp_strip_all_tags( $gateway['admin_label'] ); ?> -
+
- /> - - + />
<?php echo esc_attr( $admin_label ); ?> - -
- +
diff --git a/assets/css/admin.css b/assets/css/admin.css index 7c1b94ca3..78a95b2b5 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -676,7 +676,7 @@ tr.pro-preview .pro-field-overlay, top: 0; left: 0; display: none; - border: 1px dashed #10b981; + border: 1px dashed #059669; } tr.pro-preview-html th { width: 50%; @@ -723,7 +723,7 @@ tr.pro-preview td .wp-picker-container input { background: rgba(236, 253, 245, 0.5); border-radius: 5px; display: none; - border: 1px dashed #10b981; + border: 1px dashed #059669; z-index: 10; pointer-events: auto; } @@ -759,7 +759,7 @@ a.wpuf-button.button-upgrade-to-pro { } .wpuf-subscription-pack-settings nav .tab-current a.wpuf-button.button-upgrade-to-pro:hover, a.wpuf-button.button-upgrade-to-pro:hover { - background: #10b981; + background: #059669; } span.pro-icon { display: inline-flex; @@ -782,12 +782,6 @@ span.pro-icon img { position: relative; top: -1px; } -/* span.pro-icon.icon-white svg path { - fill: #fff; -} */ -/* label span.pro-icon svg path { - fill: #10b981; -} */ img.profile-header, img.user-listing { display: block; @@ -1526,18 +1520,14 @@ body.wpuf-modal-open { box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); } .wpuf-gateway-card--active:hover { - border-color: #10b981; + border-color: #059669; } .wpuf-gateway-card--focused { - border-color: #10b981; - box-shadow: 0 0 0 1px #10b981; + border-color: #059669; + box-shadow: 0 0 0 1px #059669; } .wpuf-gateway-card--focused:hover { - border-color: #10b981; -} -.wpuf-gateway-card--pro-locked { - opacity: 0.6; - cursor: not-allowed; + border-color: #059669; } .wpuf-gateway-card__checkbox { position: absolute; @@ -1546,30 +1536,6 @@ body.wpuf-modal-open { height: 0; pointer-events: none; } -.wpuf-gateway-card__toggle { - position: absolute; - top: 4px; - right: 4px; - cursor: pointer; - line-height: 1; - padding: 4px; - z-index: 2; -} -.wpuf-gateway-card__toggle:hover { - opacity: 0.8; -} -.wpuf-gateway-card__check-on { - display: none; -} -.wpuf-gateway-card__check-off { - display: block; -} -.wpuf-gateway-card--active .wpuf-gateway-card__check-on { - display: block; -} -.wpuf-gateway-card--active .wpuf-gateway-card__check-off { - display: none; -} .wpuf-gateway-card__icon { display: flex; align-items: center; diff --git a/assets/css/admin/settings.css b/assets/css/admin/settings.css index 19ca7fc7d..3078acab0 100644 --- a/assets/css/admin/settings.css +++ b/assets/css/admin/settings.css @@ -1,176 +1,69 @@ -/* Payment Gateway Accordion */ -.wpuf-accordion-trigger { - background: #f1f1f1; - color: #333; - cursor: pointer; - padding: 15px; - width: calc(100% - 25px); - border: 1px solid #ccc; - box-sizing: border-box; - text-align: left; - outline: none; - font-size: 15px; - transition: 0.4s; - border-radius: 4px; - display: flex; - align-items: center; - font-weight: 600; - margin-top: 20px; - margin-bottom: 5px; -} -.wpuf-accordion-trigger.active, -.wpuf-accordion-trigger:hover { - background: #ddd; -} -.wpuf-accordion-trigger:after { - content: ''; - display: block; - width: 8px; - height: 8px; - border-right: 2px solid #777; - border-bottom: 2px solid #777; - transform: translateY(-2px) rotate(45deg); - float: right; - margin-left: auto; - transition: transform 0.3s ease; -} -.wpuf-accordion-trigger.active:after { - transform: translateY(2px) rotate(225deg); -} -.wpuf-accordion-row { - display: none !important; -} -.wpuf-accordion-row.active { - display: table-row !important; -} -.wpuf-accordion-trigger.is-disabled, -.wpuf-accordion-trigger.is-disabled:hover { - background: #f9f9f9; - color: #888; -} -.wpuf-accordion-trigger.is-disabled .wpuf-gateway-icon { - border-color: #ccc; - color: #ccc; -} -.wpuf-accordion-trigger.is-disabled:after { - display: none; -} - -/* Gateway icons (color-tinted SVG via CSS mask) */ -.wpuf-gateway-icon { - box-sizing: border-box; - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - border-radius: 50%; - border: 1.5px solid currentColor; - background: transparent; - margin-right: 10px; -} -.wpuf-gateway-icon::before { - content: ""; - display: block; - width: 14px; - height: 14px; - background-color: currentColor; - -webkit-mask-repeat: no-repeat; - mask-repeat: no-repeat; - -webkit-mask-position: center; - mask-position: center; - -webkit-mask-size: contain; - mask-size: contain; -} -.wpuf-gateway-icon--paypal { - border: none; - background-image: url("data:image/svg+xml;utf8,"); - background-repeat: no-repeat; - background-position: center; - background-size: contain; -} -.wpuf-gateway-icon--paypal::before { - display: none; -} -.wpuf-gateway-icon--bank { - border: none; - background-image: url("data:image/svg+xml;utf8,"); - background-repeat: no-repeat; - background-position: center; - background-size: contain; +/* Gateway selector cards — settings-page sizing + brand SVG icons */ +.wpuf-gateway-card { + width: 140px !important; + padding: 18px 14px 14px !important; + border-width: 1px !important; } -.wpuf-gateway-icon--bank::before { - display: none; +.wpuf-gateway-card:focus-visible { + outline: 2px solid #059669; + outline-offset: 2px; } -.wpuf-gateway-icon--stripe { - border: none; - background-image: url("data:image/svg+xml;utf8,"); +.wpuf-gateway-card__icon { + height: 44px !important; + margin-bottom: 8px !important; background-repeat: no-repeat; background-position: center; - background-size: contain; + background-size: 56px auto !important; + overflow: visible; } -.wpuf-gateway-icon--stripe::before { - display: none; +.wpuf-gateway-card__name { + font-size: 13px !important; } -/* Pro badge fallback (used only when plugin's pro-badge.svg cannot be located on page) */ -.wpuf-pro-badge-fallback { +/* Enable Gateway toggle switch (in per-gateway settings panel) */ +.wpuf-gateway-toggle { + position: relative; display: inline-block; - width: 20px; - height: 20px; + width: 44px; + height: 24px; vertical-align: middle; - background-color: #F59E0B; - -webkit-mask: url("data:image/svg+xml;utf8,") no-repeat center / contain; - mask: url("data:image/svg+xml;utf8,") no-repeat center / contain; -} - -/* Pro upsell paragraph in disabled gateway row */ -.wpuf-gateway-pro-upsell { - color: #888; - margin: 0; - font-style: italic; -} -.wpuf-gateway-pro-upsell a { - color: #0073aa; - font-weight: 600; -} - -/* Pro badge wrapper next to disabled trigger title */ -.wpuf-gateway-pro-badge { - display: inline-flex; - align-items: center; } -.wpuf-gateway-pro-badge .pro-icon { - margin-left: 8px; - display: inline-flex; - align-items: center; +.wpuf-gateway-toggle__input { + opacity: 0; + width: 0; + height: 0; + position: absolute; } - -/* Gateway selector cards — shrink + use brand SVG icons */ -.wpuf-gateway-card { - width: 78px !important; - padding: 10px 6px 6px !important; - border-width: 1px !important; -} -.wpuf-gateway-card__icon { - height: 26px !important; - margin-bottom: 4px !important; - background-repeat: no-repeat; - background-position: center; - background-size: contain; +.wpuf-gateway-toggle__slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: background-color 0.2s ease; + border-radius: 24px; +} +.wpuf-gateway-toggle__slider::before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 3px; + bottom: 3px; + background-color: #fff; + border-radius: 50%; + transition: transform 0.2s ease; } -.wpuf-gateway-card__name { - font-size: 11px !important; +.wpuf-gateway-toggle__input:checked + .wpuf-gateway-toggle__slider { + background-color: #949494; } -.wpuf-gateway-card__toggle { - top: 2px !important; - right: 2px !important; - padding: 2px !important; +.wpuf-gateway-toggle__input:checked + .wpuf-gateway-toggle__slider::before { + transform: translateX(20px); } -.wpuf-gateway-card__check-on, -.wpuf-gateway-card__check-off { - width: 14px !important; - height: 14px !important; +.wpuf-gateway-toggle__input:focus-visible + .wpuf-gateway-toggle__slider { + box-shadow: 0 0 0 2px rgba(148, 148, 148, 0.35); } .wpuf-gateway-card[data-gateway="paypal"] .wpuf-gateway-card__icon > *, .wpuf-gateway-card[data-gateway="bank"] .wpuf-gateway-card__icon > *, @@ -184,5 +77,5 @@ background-image: url("data:image/svg+xml;utf8,"); } .wpuf-gateway-card[data-gateway="stripe"] .wpuf-gateway-card__icon { - background-image: url("data:image/svg+xml;utf8,"); + background-image: url("data:image/svg+xml;utf8,"); } diff --git a/assets/js/admin/settings.js b/assets/js/admin/settings.js index 68560100c..082e2a514 100644 --- a/assets/js/admin/settings.js +++ b/assets/js/admin/settings.js @@ -76,209 +76,24 @@ } - // --- Accordion for Payment Gateways --- - var paymentTab = document.getElementById('wpuf_payment'); - if (paymentTab) { - var table = paymentTab.querySelector('table.form-table'); - if (table) { - var trs = Array.from(table.querySelectorAll('tbody > tr')); - var groups = { - paypal: { - title: 'PayPal', - iconClass: 'wpuf-gateway-icon wpuf-gateway-icon--paypal', - rows: [], - existingCheckbox: null - }, - bank: { - title: 'Bank Transfer', - iconClass: 'wpuf-gateway-icon wpuf-gateway-icon--bank', - rows: [], - existingCheckbox: null - }, - stripe: { - title: 'Credit Card', - iconClass: 'wpuf-gateway-icon wpuf-gateway-icon--stripe', - rows: [], - existingCheckbox: null - } - }; - - var allowGatewaysTr = null; - - trs.forEach(function (tr) { - var html = tr.innerHTML; - if (html.includes('wpuf_payment[active_gateways]')) { - allowGatewaysTr = tr; - } - }); - - // Card grid in allowGatewaysTr handles enable/disable. - // Do NOT hide it and do NOT move checkboxes — accordion only groups per-gateway settings rows below. - - // Use the data-gateway-id tag set by wpufInitGatewaySelector (runs before this block). - // This includes mapped fields like failed_retry → paypal. - trs.forEach(function (tr) { - if (tr === allowGatewaysTr) return; - var gid = tr.getAttribute('data-gateway-id'); - if (gid && groups[gid]) { - groups[gid].rows.push(tr); - } - }); - - var theTbody = table.querySelector('tbody'); - - // Resolve the existing PRO badge img URL — search ALL tabs including hidden ones - var proIconSrcUrl = ''; - var proIconImg = document.querySelector('.pro-icon img'); - if (!proIconImg) { - proIconImg = document.querySelector('span.pro-icon img'); - } - if (proIconImg) { - proIconSrcUrl = proIconImg.getAttribute('src'); - } else { - var anyImg = document.querySelector('img[src*="wp-user-frontend"][src*="assets"]'); - if (anyImg) { - proIconSrcUrl = anyImg.getAttribute('src').split('/assets/')[0] + '/assets/images/pro-badge.svg'; - } - } - - function createProIconNode() { - if (proIconSrcUrl) { - var img = document.createElement('img'); - img.src = proIconSrcUrl; - img.alt = 'PRO'; - img.style.verticalAlign = 'middle'; - return img; - } - var fallback = document.createElement('span'); - fallback.className = 'wpuf-pro-badge-fallback'; - fallback.setAttribute('role', 'img'); - fallback.setAttribute('aria-label', 'PRO'); - return fallback; - } - - Object.keys(groups).forEach(function (key) { - var group = groups[key]; - - // Stripe is disabled only when no stripe settings rows are registered in PHP - // (PRO off / stripe module off). If PRO active + stripe module on, rows > 0. - var isProFeatureDisabled = (key === 'stripe' && group.rows.length === 0); - - if (isProFeatureDisabled) { - var dummyTr = document.createElement('tr'); - var dummyTh = document.createElement('th'); - dummyTh.scope = 'row'; - - // "Enable Gateway" label + PRO icon badge — same pattern used throughout the plugin: - // - var enableLabel = document.createElement('label'); - enableLabel.appendChild(document.createTextNode('Enable Gateway ')); - var proIconSpan = document.createElement('span'); - proIconSpan.className = 'pro-icon'; - proIconSpan.appendChild(createProIconNode()); - enableLabel.appendChild(proIconSpan); - dummyTh.appendChild(enableLabel); - - var dummyTd = document.createElement('td'); - var upsellP = document.createElement('p'); - upsellP.className = 'wpuf-gateway-pro-upsell'; - upsellP.appendChild(document.createTextNode('Available with ')); - var upsellLink = document.createElement('a'); - upsellLink.href = 'https://wedevs.com/wp-user-frontend-pro/'; - upsellLink.target = '_blank'; - upsellLink.textContent = 'WPUF Pro'; - upsellP.appendChild(upsellLink); - upsellP.appendChild(document.createTextNode('.')); - dummyTd.appendChild(upsellP); - - dummyTr.appendChild(dummyTh); - dummyTr.appendChild(dummyTd); - group.rows.push(dummyTr); - } - - if (group.rows.length === 0) return; - - var triggerRow = document.createElement('tr'); - var triggerCell = document.createElement('td'); - triggerCell.colSpan = 2; - triggerCell.style.padding = '0'; - - var triggerBtn = document.createElement('button'); - triggerBtn.type = 'button'; - triggerBtn.className = 'wpuf-accordion-trigger'; - triggerBtn.setAttribute('data-target-group', key); - - var titleSpan = document.createElement('span'); - titleSpan.style.display = 'flex'; - titleSpan.style.alignItems = 'center'; - var iconSpan = document.createElement('span'); - iconSpan.className = group.iconClass; - titleSpan.appendChild(iconSpan); - titleSpan.appendChild(document.createTextNode(group.title)); - - if (isProFeatureDisabled) { - triggerBtn.classList.add('is-disabled'); - var badgeWrapper = document.createElement('div'); - badgeWrapper.className = 'wpuf-gateway-pro-badge'; - var badgeSpan = document.createElement('span'); - badgeSpan.className = 'pro-icon'; - badgeSpan.title = 'Pro Feature'; - badgeSpan.appendChild(createProIconNode()); - badgeWrapper.appendChild(badgeSpan); - titleSpan.appendChild(badgeWrapper); - } - - triggerBtn.appendChild(titleSpan); - triggerCell.appendChild(triggerBtn); - triggerRow.appendChild(triggerCell); - - theTbody.appendChild(triggerRow); - - // We add a specific class to the gateway setting rows to hide them by default - group.rows.forEach(function (r) { - r.classList.add('wpuf-accordion-row'); - r.setAttribute('data-group', key); - theTbody.appendChild(r); - }); - - // Event listener for toggle - triggerBtn.addEventListener('click', function (e) { - e.preventDefault(); - - if (isProFeatureDisabled) { - return; - } - - var isActive = this.classList.contains('active'); - - // Close all accordions - document.querySelectorAll('.wpuf-accordion-trigger').forEach(function (btn) { - btn.classList.remove('active'); - var g = btn.getAttribute('data-target-group'); - document.querySelectorAll('tr[data-group="' + g + '"]').forEach(function (tr) { - tr.classList.remove('active'); - }); - }); - - // Toggle current - if (!isActive) { - this.classList.add('active'); - document.querySelectorAll('tr[data-group="' + key + '"]').forEach(function (tr) { - tr.classList.add('active'); - }); - } - }); - }); - } - } }); + var wpufI18n = window.wpufSettingsI18n || {}; + function wpufSprintf(template, value) { + return String(template).replace('%s', value); + } + function wpufEscapeRegex(str) { + return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + /** * Gateway Selector Card Grid * - * Handles card click to toggle checkbox (multi-select) and - * shows only the clicked gateway's settings rows below. + * Card click opens that gateway's settings panel; settings rows for + * non-focused gateways stay hidden. Within the focused gateway, + * non-enable rows stay hidden until the "Enable Gateway" toggle is on. + * The hidden card checkbox is driven by that toggle. */ function wpufInitGatewaySelector() { var container = document.querySelector('.wpuf-gateway-cards'); @@ -287,20 +102,14 @@ return; } - // Map gateway IDs to their settings field name prefixes. - // PayPal fields: paypal_*, gate_instruct_paypal - // Bank fields: bank_*, gate_instruct_bank - // Generic pattern: field name contains the gateway ID var gatewayCards = container.querySelectorAll('.wpuf-gateway-card'); - - // Find the that contains the gateway selector itself - var selectorRow = container.closest('tr'); + var selectorRow = container.closest('tr'); if ( ! selectorRow ) { return; } - // Collect all siblings after the selector row in the same table + // Collect all siblings after the selector row in the same table. var formTable = selectorRow.closest('table'); var allRows = formTable ? formTable.querySelectorAll('tr') : []; var afterRows = []; @@ -311,28 +120,32 @@ pastSelector = true; return; } - if ( pastSelector ) { afterRows.push(row); } }); - // Fields whose names don't contain a gateway ID but belong to one + // Fields whose names don't contain a gateway ID but belong to one. var fieldGatewayMap = { 'failed_retry': 'paypal', }; + // Build gateway id list + boundary regex map once. + var gatewayIds = []; + var gatewayRegexMap = {}; + gatewayCards.forEach(function(card) { + var id = card.getAttribute('data-gateway'); + if ( ! id ) return; + gatewayIds.push(id); + gatewayRegexMap[id] = new RegExp('(^|_)' + wpufEscapeRegex(id) + '(_|$)'); + }); + /** * Determine which gateway a settings row belongs to by * checking the name attribute of inputs/selects/textareas inside it. */ function getRowGatewayId(row) { var inputs = row.querySelectorAll('input, select, textarea'); - var gatewayIds = []; - - gatewayCards.forEach(function(card) { - gatewayIds.push(card.getAttribute('data-gateway')); - }); for ( var i = 0; i < inputs.length; i++ ) { var name = inputs[i].getAttribute('name') || ''; @@ -349,9 +162,7 @@ for ( var j = 0; j < gatewayIds.length; j++ ) { var gid = gatewayIds[j]; - - // Match: gate_instruct_paypal, paypal_email, bank_success, etc. - if ( fieldName.indexOf(gid) !== -1 || fieldName.indexOf('gate_instruct_' + gid) !== -1 ) { + if ( gatewayRegexMap[gid].test(fieldName) ) { return gid; } } @@ -382,7 +193,7 @@ } for ( var m = 0; m < gatewayIds.length; m++ ) { - if ( forFieldName.indexOf(gatewayIds[m]) !== -1 ) { + if ( gatewayRegexMap[gatewayIds[m]].test(forFieldName) ) { return gatewayIds[m]; } } @@ -403,102 +214,123 @@ } }); - /** - * Update the focused (green border) state on gateway cards - */ - function setFocusedCard(gatewayId) { - gatewayCards.forEach(function(card) { - if ( card.getAttribute('data-gateway') === gatewayId ) { - card.classList.add('wpuf-gateway-card--focused'); - } else { - card.classList.remove('wpuf-gateway-card--focused'); - } - }); - } + var formTbody = formTable ? formTable.querySelector('tbody') : null; + + // Hide all gateway settings rows by default; card click reveals one gateway's rows. + // Within the focused gateway, non-enable rows stay hidden until that gateway is enabled. + function showGateway(gatewayId) { + var focusedCard = container.querySelector('.wpuf-gateway-card[data-gateway="' + gatewayId + '"]'); + var focusedCheckbox = focusedCard ? focusedCard.querySelector('.wpuf-gateway-card__checkbox') : null; + var enabled = focusedCheckbox ? focusedCheckbox.checked : false; - /** - * Show settings rows for a specific gateway, hide others - */ - function showGatewaySettings(gatewayId) { afterRows.forEach(function(row) { - if ( ! row.classList.contains('wpuf-gateway-setting-row') ) { + if ( ! row.classList.contains('wpuf-gateway-setting-row') ) return; + + if ( row.getAttribute('data-gateway-id') !== gatewayId ) { + row.classList.add('wpuf-gateway-setting-hidden'); return; } - if ( row.getAttribute('data-gateway-id') === gatewayId ) { + if ( row.classList.contains('wpuf-gateway-enable-row') || enabled ) { row.classList.remove('wpuf-gateway-setting-hidden'); } else { row.classList.add('wpuf-gateway-setting-hidden'); } }); - setFocusedCard(gatewayId); - } - - /** - * Hide all gateway-specific settings rows - */ - function hideAllGatewaySettings() { - afterRows.forEach(function(row) { - if ( row.classList.contains('wpuf-gateway-setting-row') ) { - row.classList.add('wpuf-gateway-setting-hidden'); + gatewayCards.forEach(function(card) { + if ( card.getAttribute('data-gateway') === gatewayId ) { + card.classList.add('wpuf-gateway-card--focused'); + } else { + card.classList.remove('wpuf-gateway-card--focused'); } }); - - setFocusedCard(null); } - // Checkbox change handler (triggered by the