PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.2.1
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.2.1
2.4.1 2.4.0 2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.2 2.2.1 2.2.0 2.1.2 2.1.1 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 All 68 releases
better-payment / src / blocks / payment-form / src / components / layouts / layout-1.js

layout-1.js in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More 2.2.1, at src/blocks/payment-form/src/components/layouts/layout-1.js

366 lines 24.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { useState } from '@wordpress/element';
2 import { getCurrencySymbol, normalizeIconClass, titleToSnake } from '../../helpers/helpers';
3
4 const Layout1 = ({ attributes, setAttributes, serialized, pageId }) => {
5 const {
6 formFields,
7 showSidebar,
8 currencyAlign,
9 showAmountList,
10 amountList,
11 transactionTitle,
12 transactionSubTitle,
13 amountText,
14 paypalButtonText,
15 stripeButtonText,
16 paystackButtonText,
17 paypalEnabled,
18 stripeEnabled,
19 paystackEnabled,
20 currency,
21 paymentSource,
22 blockId,
23 productName,
24 productPrice,
25 productPermalink,
26 transactionDetailsProductTitle,
27 } = attributes;
28
29 // --- Computed layout variables (mirrors layout-vars.php) ---
30 const currencySymbol = currency ? getCurrencySymbol(currency) : '';
31 const currencyAlignment = currencyAlign || 'left';
32 const currencyLeft = currencyAlignment === 'left' ? currencySymbol : '';
33 const currencyRight = currencyAlignment === 'right' ? currencySymbol : '';
34
35 const isPaymentTypeWoocommerce = paymentSource === 'woocommerce';
36 const isPaymentTypeFluentcart = paymentSource === 'fluentcart';
37 const isPaymentTypeStripe = paymentSource === 'stripe';
38 const layoutPutAmountFieldHideShow = (isPaymentTypeWoocommerce || isPaymentTypeFluentcart || isPaymentTypeStripe) ? 'is-hidden' : '';
39 const layoutDynamicPaymentHideShow = (isPaymentTypeWoocommerce || isPaymentTypeFluentcart || isPaymentTypeStripe) ? '' : 'is-hidden';
40 // Show product title only for WooCommerce and FluentCart (not Stripe)
41 const showProductTitle = isPaymentTypeWoocommerce || isPaymentTypeFluentcart;
42
43 // Three-items-enabled class (when all 3 payment methods are on)
44 const threeItemsClass = (paypalEnabled && stripeEnabled && paystackEnabled) ? 'bp-three-items-enabled' : '';
45
46 // Count enabled payment methods for single-item logic
47 const enabledCount = [paypalEnabled, stripeEnabled, paystackEnabled].filter(Boolean).length;
48
49 const assetsUrl = window?.betterPaymentBlockData?.assetsUrl || '';
50
51 // Return null if all payment methods are disabled (mirrors Elementor widget behavior)
52 if (!paypalEnabled && !stripeEnabled && !paystackEnabled) {
53 return null;
54 }
55
56 // Track amount entered in the payment amount input field
57 const [enteredAmount, setEnteredAmount] = useState('');
58
59 // Track selected payment method for dynamic button display
60 const getDefaultPaymentMethod = () => {
61 if (paypalEnabled) return 'paypal';
62 if (stripeEnabled) return 'stripe';
63 if (paystackEnabled) return 'paystack';
64 return 'paypal';
65 };
66 const [selectedPaymentMethod, setSelectedPaymentMethod] = useState(getDefaultPaymentMethod());
67
68 // Track selected amount from amount list
69 const [selectedAmountIndex, setSelectedAmountIndex] = useState(-1);
70
71 // Determine display amount: product price takes precedence when available
72 const hasProductPrice = (isPaymentTypeWoocommerce || isPaymentTypeFluentcart || isPaymentTypeStripe) && productPrice !== '';
73 const displayAmount = hasProductPrice ? parseFloat(productPrice) : (enteredAmount || '');
74
75 // Handle amount list item click
76 const handleAmountSelect = (amountValue, index) => {
77 setSelectedAmountIndex(index);
78 setEnteredAmount(String(amountValue));
79 };
80
81 // Handle payment method change
82 const handlePaymentMethodChange = (method) => {
83 setSelectedPaymentMethod(method);
84 };
85
86 // --- Render helper: icon for a form field (mirrors layout-repeater-vars.php icon logic) ---
87 const renderFieldIcon = (item, isPaymentAmountField) => {
88 // Determine the default icon based on field type (mirrors layout-repeater-vars.php line 26)
89 const fieldType = item.type || 'text';
90 const defaultIcon = fieldType === 'email' ? 'bp-icon bp-envelope' : 'bp-icon bp-user';
91 const icon = normalizeIconClass(item.icon || defaultIcon);
92
93 const isImage = icon.startsWith('http://') || icon.startsWith('https://') || icon.startsWith('/');
94
95 // For payment amount field, always show currency symbol
96 if (isPaymentAmountField) {
97 return (
98 <span className="icon is-medium is-left">
99 <span className="icon is-medium is-left">
100 <span className="bp-currency-symbol">{currencySymbol}</span>
101 </span>
102 </span>
103 );
104 }
105
106 if (isImage) {
107 return (
108 <span className="icon is-medium is-left">
109 <img src={icon} alt="Icon" width="20" />
110 </span>
111 );
112 }
113
114 // FontAwesome icons start with 'fas', 'far', 'fab' etc.
115 // bp-icon icons start with 'bp-icon'
116 return (
117 <span className="icon is-medium is-left">
118 <i className={icon}></i>
119 </span>
120 );
121 };
122
123 // --- Render amount list (mirrors render_amount_element) ---
124 const renderAmountList = () => {
125 if (!showAmountList || !amountList || amountList.length === 0) {
126 return null;
127 }
128 return (
129 <>
130 {amountList.map((amountItem, idx) => {
131 const amountValue = parseFloat(amountItem.label) || 0;
132 const uniqueId = `bp_payment_amount_${blockId}_${idx}`;
133 return (
134 <div key={idx} className="bp-form__group pt-5">
135 <input
136 type="radio"
137 value={amountValue}
138 id={uniqueId}
139 className="bp-form__control bp-form_pay-radio"
140 name="primary_payment_amount_radio"
141 checked={selectedAmountIndex === idx}
142 onChange={() => handleAmountSelect(amountValue, idx)}
143 />
144 <label htmlFor={uniqueId}>{currencyLeft}{amountValue}{currencyRight}</label>
145 </div>
146 );
147 })}
148 </>
149 );
150 };
151
152 return (
153 <div className={`better-payment ${blockId ? 'bp-' + blockId : ''}`}>
154 <div className="better-payment--wrapper">
155 <div className="better-payment--container">
156 <form
157 name={`better-payment-form-${blockId}`}
158 data-better-payment={serialized}
159 className="better-payment-form"
160 id={`better-payment-form-${blockId}`}
161 method="post"
162 >
163 <input type="hidden" name="better_payment_page_id" value={pageId} />
164 <input type="hidden" name="better_payment_widget_id" value={blockId} />
165
166 <div className="payment-form-layout-1 payment-form-layout">
167 <div className="tile is-ancestor m-0">
168 {showSidebar && (
169 <div className="tile is-parent dynamic-amount-section">
170 <div className="tile is-child">
171 <div className="dynamic-amount-section-inner has-text-white p-6">
172 <div className="pt-3 pb-3">
173 <h4 className="is-size-2-fix bp-dynamic-amount-section-amount-summary">
174 {currencyLeft}
175 <span className="bp-transaction-details-amount-text">{displayAmount}</span>
176 {currencyRight}
177 </h4>
178 <h4 className="is-size-5-fix pt-5 bp-dynamic-amount-section-title">
179 {transactionTitle}
180 </h4>
181 <p className="is-size-6-fix pt-3 bp-dynamic-amount-section-sub-title">
182 {transactionSubTitle}
183 </p>
184 <p className="is-size-6-fix pt-2 bp-dynamic-amount-section-amount">
185 {showProductTitle && (
186 <>
187 {transactionDetailsProductTitle}{' '}
188 <a className="has-text-white" href={productPermalink || '#'} style={{ textDecoration: 'none', pointerEvents: 'none' }}>
189 {productName}
190 </a>
191 <br />
192 </>
193 )}
194 {amountText}{' '}
195 <span className="bp-transaction-details-amount-text">{displayAmount}</span>
196 </p>
197 </div>
198 </div>
199 </div>
200 </div>
201 )}
202
203 <div className={`tile is-parent has-background-white-fix form-content-section ${showSidebar ? 'is-8' : 'is-12'}`}>
204 <div className="tile is-child">
205 <div className="form-content-section-inner p-6">
206 <div className="pt-3 pb-3">
207 <div className="form-content-section-fields">
208 {/* Payment method radio buttons */}
209 <div className={`field-payment_method ${threeItemsClass ? threeItemsClass + '-wrap' : ''}`}>
210 <div className="control bp-radio-box">
211 {paypalEnabled && (
212 (enabledCount > 1) ? (
213 <label className={`radio pr-6 payment-method-checkbox payment-method-checkbox-paypal ${threeItemsClass}`}>
214 <input type="radio" name="payment_method" className="layout-payment-method-paypal" checked={selectedPaymentMethod === 'paypal'} onChange={() => handlePaymentMethodChange('paypal')} />
215 {'PayPal'}
216 </label>
217 ) : (
218 <label className={`radio pr-6 mb-5 payment-method-checkbox payment-method-checkbox-paypal single-item active column has-text-centered ${threeItemsClass}`}>
219 <input type="radio" name="payment_method" className="layout-payment-method-paypal" checked={selectedPaymentMethod === 'paypal'} onChange={() => handlePaymentMethodChange('paypal')} />
220 <img src={`${assetsUrl}/img/paypal.png`} alt="PayPal" />
221 </label>
222 )
223 )}
224
225 {stripeEnabled && (
226 (enabledCount > 1) ? (
227 <label className={`radio pr-6 payment-method-checkbox payment-method-checkbox-stripe ${threeItemsClass}`}>
228 <input type="radio" name="payment_method" className="layout-payment-method-stripe" checked={selectedPaymentMethod === 'stripe'} onChange={() => handlePaymentMethodChange('stripe')} />
229 {'Stripe'}
230 </label>
231 ) : (
232 <label className={`radio pr-6 mb-5 payment-method-checkbox payment-method-checkbox-stripe single-item active column has-text-centered ${threeItemsClass}`}>
233 <input type="radio" name="payment_method" className="layout-payment-method-stripe" checked={selectedPaymentMethod === 'stripe'} onChange={() => handlePaymentMethodChange('stripe')} />
234 <img src={`${assetsUrl}/img/stripe.svg`} alt="Stripe" />
235 </label>
236 )
237 )}
238
239 {paystackEnabled && (
240 (enabledCount > 1) ? (
241 <label className={`radio payment-method-checkbox payment-method-checkbox-paystack ${threeItemsClass}`}>
242 <input type="radio" name="payment_method" className="layout-payment-method-paystack" checked={selectedPaymentMethod === 'paystack'} onChange={() => handlePaymentMethodChange('paystack')} />
243 {'Paystack'}
244 </label>
245 ) : (
246 <label className={`radio mb-5 payment-method-checkbox payment-method-checkbox-paystack single-item active column has-text-centered ${threeItemsClass}`}>
247 <input type="radio" name="payment_method" className="layout-payment-method-paystack" checked={selectedPaymentMethod === 'paystack'} onChange={() => handlePaymentMethodChange('paystack')} />
248 <img src={`${assetsUrl}/img/paystack.svg`} alt="Paystack" />
249 </label>
250 )
251 )}
252 </div>
253 </div>
254
255 {/* Form fields */}
256 {formFields.map((item, index) => {
257 const { displayInline, fieldWidth, label, placeholder, primaryFieldType, required, show, type } = item;
258
259 // --- Repeater vars (mirrors layout-repeater-vars.php) ---
260 const isPaymentAmountField = primaryFieldType === 'primary_payment_amount';
261 const isPrimaryEmailField = primaryFieldType === 'primary_email';
262 const isPrimaryField = [
263 'primary_first_name', 'primary_last_name', 'primary_email',
264 'primary_payment_amount', 'primary_coupon_code', 'primary_reference_number'
265 ].includes(primaryFieldType);
266
267 // Field name: use primaryFieldType for known types, else convert label to snake_case
268 const fieldName = isPrimaryField ? primaryFieldType : titleToSnake(label);
269
270 // Required logic
271 const isRequired = required || isPaymentAmountField || isPrimaryEmailField;
272 const requiredClass = isRequired ? ' required' : '';
273 const requiredPlaceholder = isRequired ? ' *' : '';
274
275 // Visibility
276 const visibleClass = show ? '' : ' is-hidden';
277
278 // Field type overrides
279 let fieldType = type || 'text';
280 if (isPaymentAmountField) fieldType = 'number';
281 if (isPrimaryEmailField) fieldType = 'email';
282
283 // Placeholder
284 const fieldPlaceholder = (placeholder || '') + requiredPlaceholder;
285
286 // Payment amount field class
287 const paymentAmountFieldClass = isPaymentAmountField ? 'bp-custom-payment-amount' : '';
288
289 // Inline display
290 const inlineClass = displayInline ? ' field-display-inline' : '';
291 const inlineStyle = displayInline && fieldWidth ? { width: `${fieldWidth}%` } : {};
292
293 return (
294 <div key={index}>
295 {/* Amount list before payment amount field */}
296 {isPaymentAmountField && (
297 <div className={`bp-payment-amount-wrap ${layoutPutAmountFieldHideShow}`}>
298 {renderAmountList()}
299 </div>
300 )}
301
302 <div
303 className={`better-payment-field-advanced-layout field-${fieldName} pt-5 elementor-repeater-item-${index}${inlineClass} ${paymentAmountFieldClass}${visibleClass}${isPaymentAmountField ? ' ' + layoutPutAmountFieldHideShow : ''}`}
304 style={inlineStyle}
305 >
306 <div className="control has-icons-left">
307 <input
308 className={`input is-medium${requiredClass}${visibleClass}`}
309 type={fieldType}
310 placeholder={fieldPlaceholder}
311 name={fieldName}
312 {...(isRequired ? { required: 'required' } : {})}
313 {...(isPaymentAmountField ? { step: 'any', min: '1' } : {})}
314 {...(isPaymentAmountField ? {
315 value: hasProductPrice ? String(parseFloat(productPrice)) : enteredAmount,
316 onChange: (e) => setEnteredAmount(e.target.value),
317 ...(hasProductPrice ? { readOnly: true } : {}),
318 } : {})}
319 />
320 {renderFieldIcon(item, isPaymentAmountField)}
321 </div>
322 </div>
323 </div>
324 );
325 })}
326
327 <div className="pb-5"></div>
328
329 {/* Payment buttons */}
330 <div className="field-process_payment_button pt-3">
331 <div className="control">
332 <div className="payment__option">
333 {paypalEnabled && (
334 <button type="button" className="button is-medium is-fullwidth better-payment-paypal-bt bp-payment-btn" style={{ display: selectedPaymentMethod === 'paypal' ? 'block' : 'none' }}>
335 {paypalButtonText || 'Proceed to Payment'}
336 </button>
337 )}
338 {stripeEnabled && (
339 <button type="button" className="button is-medium is-fullwidth better-payment-stripe-bt bp-payment-btn" style={{ display: selectedPaymentMethod === 'stripe' ? 'block' : 'none' }}>
340 {stripeButtonText || 'Proceed to Payment'}
341 </button>
342 )}
343 {paystackEnabled && (
344 <button type="button" className="button is-medium is-fullwidth better-payment-paystack-bt bp-payment-btn" style={{ display: selectedPaymentMethod === 'paystack' ? 'block' : 'none' }}>
345 {paystackButtonText || 'Proceed to Payment'}
346 </button>
347 )}
348 </div>
349 </div>
350 </div>
351 </div>
352 </div>
353 </div>
354 </div>
355 </div>
356 </div>
357 </div>
358 </form>
359 </div>
360 </div>
361 </div>
362 );
363 };
364
365 export default Layout1;
366