From aecdbd05c0455d629083094f7ff227f0fb9e6927 Mon Sep 17 00:00:00 2001 From: Sapayth Hossain Date: Fri, 13 Mar 2026 10:52:25 +0600 Subject: [PATCH 1/2] feat: add card grid UI for payment gateway selection Replace plain multicheck field with a visual card grid for selecting payment gateways in Settings > Payments. Each gateway renders as a clickable card with icon, name, and toggle. Clicking a card shows its settings below; toggling the checkmark enables/disables it. - Add gateway_selector field type and callback in WeDevs_Settings_API - Add gateway_selector context to wpuf_get_gateways() for rich data - Add JS gateway row detection and show/hide logic - Add LESS/CSS styles with BEM naming and focused/active states - Use official PayPal SVG mark icon, inline SVG for bank, generic credit card fallback for gateways without icons --- Lib/Gateway/Paypal_Gateway.php | 2 +- Lib/WeDevs_Settings_API.php | 79 ++++++++ assets/css/admin.css | 98 ++++++++++ assets/images/paypal-mark.svg | 1 + assets/js/admin/settings.js | 231 ++++++++++++++++++++++++ assets/less/admin.less | 123 +++++++++++++ includes/functions/settings-options.php | 6 +- wpuf-functions.php | 7 + 8 files changed, 543 insertions(+), 4 deletions(-) create mode 100644 assets/images/paypal-mark.svg diff --git a/Lib/Gateway/Paypal_Gateway.php b/Lib/Gateway/Paypal_Gateway.php index 3180b89eb..26c4263cc 100644 --- a/Lib/Gateway/Paypal_Gateway.php +++ b/Lib/Gateway/Paypal_Gateway.php @@ -67,7 +67,7 @@ protected function init() { $this->id = 'paypal'; $this->admin_label = __( 'PayPal', 'wp-user-frontend' ); $this->checkout_label = __( 'PayPal', 'wp-user-frontend' ); - $this->icon = apply_filters( 'wpuf_paypal_checkout_icon', WPUF_ASSET_URI . '/images/paypal.png' ); + $this->icon = apply_filters( 'wpuf_paypal_checkout_icon', WPUF_ASSET_URI . '/images/paypal-mark.svg' ); $this->supports_subscription = true; // Initialize PayPal-specific properties diff --git a/Lib/WeDevs_Settings_API.php b/Lib/WeDevs_Settings_API.php index 05347bae2..0d701ab36 100644 --- a/Lib/WeDevs_Settings_API.php +++ b/Lib/WeDevs_Settings_API.php @@ -344,6 +344,85 @@ function callback_multicheck( $args ) { echo wp_kses( $html, array('fieldset' => [],'label' => ['for' => []],'input' => ['type' => [],'class' => [],'id' => [],'name' => [],'value' => [],'checked' => [],],'br' => [],'span' => ['class' => []],'svg' => ['width' => [],'height' => [],'viewBox' => [],'fill' => [],'xmlns' => [],],'path' => ['d' => [], 'fill' => []],'p' => ['class' => [] ] ) ); } + /** + * Displays a Texty-style 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. + * + * @since WPUF_SINCE + * + * @param array $args settings field args + * + * @return void + */ + 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_svg = ''; + ?> +
+ + +
+ $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']; + ?> +
+ + + /> + + + +
+ + <?php echo esc_attr( $admin_label ); ?> + + + + + +
+ +
+ +
+
+ +
+ + get_field_description( $args ) ); ?> +
+ \ No newline at end of file diff --git a/assets/js/admin/settings.js b/assets/js/admin/settings.js index f02ae4839..964533b21 100644 --- a/assets/js/admin/settings.js +++ b/assets/js/admin/settings.js @@ -59,6 +59,9 @@ close.style.display = 'none'; }) + // Gateway Selector Card Grid + wpufInitGatewaySelector(); + function wpuf_search_reset() { content.forEach(function (row, index) { var content_id = row.closest('div').getAttribute('id'); @@ -73,4 +76,232 @@ } }); + + /** + * Gateway Selector Card Grid + * + * Handles card click to toggle checkbox (multi-select) and + * shows only the clicked gateway's settings rows below. + */ + function wpufInitGatewaySelector() { + var container = document.querySelector('.wpuf-gateway-cards'); + + if ( ! container ) { + 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'); + + if ( ! selectorRow ) { + return; + } + + // Collect all siblings after the selector row in the same table + var formTable = selectorRow.closest('table'); + var allRows = formTable ? formTable.querySelectorAll('tr') : []; + var afterRows = []; + var pastSelector = false; + + allRows.forEach(function(row) { + if ( row === selectorRow ) { + pastSelector = true; + return; + } + + if ( pastSelector ) { + afterRows.push(row); + } + }); + + // Fields whose names don't contain a gateway ID but belong to one + var fieldGatewayMap = { + 'failed_retry': 'paypal', + }; + + /** + * 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') || ''; + // Extract field name from wpuf_payment[field_name] + var match = name.match(/\[([^\]]+)\]$/); + + if ( match ) { + var fieldName = match[1]; + + // Check explicit field-to-gateway mapping first + if ( fieldGatewayMap[fieldName] ) { + return fieldGatewayMap[fieldName]; + } + + 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 ) { + return gid; + } + } + } + } + + // Check the label text for a gateway ID match + var thLabel = row.querySelector('th'); + if ( thLabel ) { + var labelText = thLabel.getAttribute('scope') === 'row' ? (thLabel.textContent || '') : ''; + + for ( var k = 0; k < gatewayIds.length; k++ ) { + if ( labelText.toLowerCase().indexOf(gatewayIds[k]) !== -1 ) { + return gatewayIds[k]; + } + } + + // Check the label[for] attribute (e.g. "wpuf_payment[paypal_webhook_events_info]") + var labelEl = thLabel.querySelector('label[for]'); + if ( labelEl ) { + var forAttr = labelEl.getAttribute('for') || ''; + var forMatch = forAttr.match(/\[([^\]]+)\]$/); + if ( forMatch ) { + var forFieldName = forMatch[1]; + + if ( fieldGatewayMap[forFieldName] ) { + return fieldGatewayMap[forFieldName]; + } + + for ( var m = 0; m < gatewayIds.length; m++ ) { + if ( forFieldName.indexOf(gatewayIds[m]) !== -1 ) { + return gatewayIds[m]; + } + } + } + } + } + + return null; + } + + // Tag each row with its gateway ID + afterRows.forEach(function(row) { + var gid = getRowGatewayId(row); + + if ( gid ) { + row.classList.add('wpuf-gateway-setting-row'); + row.setAttribute('data-gateway-id', gid); + } + }); + + /** + * 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'); + } + }); + } + + /** + * Show settings rows for a specific gateway, hide others + */ + function showGatewaySettings(gatewayId) { + afterRows.forEach(function(row) { + if ( ! row.classList.contains('wpuf-gateway-setting-row') ) { + return; + } + + if ( row.getAttribute('data-gateway-id') === gatewayId ) { + 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'); + } + }); + + setFocusedCard(null); + } + + // Checkbox change handler (triggered by the