PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 4.16.6
GiveWP – Donation Plugin and Fundraising Platform v4.16.6
4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 2.32.0 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.10.0 3.11.0 3.12.0 3.12.1 3.12.2 3.12.3 3.13.0 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.17.0 3.17.1 3.17.2 3.18.0 3.19.0 3.19.1 3.19.2 3.19.3 3.19.4 3.2.0 3.2.1 3.2.2 3.20.0 3.21.0 3.21.1 3.22.0 3.22.1 3.22.2 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.7.0 3.8.0 3.9.0 4.0.0 4.1.0 4.1.1 4.10.0 4.10.1 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.14.5 4.14.6 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.1 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 trunk 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.2 2.11.3 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.13.1 2.13.2 2.13.3 2.13.4 2.14.0 2.15.0 2.16.0 2.16.1 2.17.0 2.17.1 2.17.3 2.18.0 2.18.1 2.19.1 2.19.2 2.19.3 2.19.4 2.19.5 2.19.6 2.19.7 2.19.8 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.20.0 2.20.1 2.20.2 2.21.0 2.21.1 2.21.2 2.21.3 2.21.4 2.22.0 2.22.1 2.22.2 2.22.3 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.24.2 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.27.0 2.27.1 2.27.2 2.27.3 2.28.0 2.29.0 2.29.1 2.29.2
give / src / DonorDashboards / resources / js / app / components / subscription-manager / amount-control / index.js
give / src / DonorDashboards / resources / js / app / components / subscription-manager / amount-control Last commit date
index.js 6 months ago style.scss 1 year ago
index.js
154 lines
1 import {useCallback, useEffect, useState} from 'react';
2 import CurrencyInput, {formatValue} from 'react-currency-input-field';
3 import {__, sprintf} from '@wordpress/i18n';
4
5 import {toUniqueId} from '../../../utils';
6 import FieldRow from '../../field-row';
7 import SelectControl from '../../select-control';
8
9 import './style.scss';
10
11 const CUSTOM_AMOUNT = 'custom_amount';
12
13 /**
14 * Converts a float to minor currency.
15 *
16 * @param {string} float
17 * @param {number} precision
18 * @return {number}
19 */
20 const minorOfFloat = (float, precision) => Number.parseFloat(float) * Math.pow(10, precision);
21
22 /**
23 * Quick and cheap knock-off of the classnames package.
24 * Do not re-use. Just use the real package.
25 *
26 * @param {...any} className
27 * @return {string}
28 */
29 const cx = (...classNames) => classNames.filter(Boolean).join(' ');
30
31 /**
32 * This control provides preset options however it allows the user to specify a
33 * custom option.
34 *
35 * Heads up, you probably want to take a good look at what’s happening before
36 * using this elsewhere.
37 */
38 const AmountControl = ({currency, onChange, value, options, min, max}) => {
39 // This is the configuration for the CurrencyInput as well as formatting
40 // values in validation errors
41 const formatConfig = {
42 decimalScale: currency.numberDecimals,
43 decimalsLimit: currency.numberDecimals,
44 prefix: currency.currencyPosition === 'before' ? currency.symbol : null,
45 suffix: currency.currencyPosition === 'after' ? currency.symbol : null,
46 decimalSeparator: currency.decimalSeparator,
47 groupSeparator: currency.thousandsSeparator,
48 };
49
50 const [validationError, setValidationError] = useState();
51 const clearValidationError = () => setValidationError(null);
52
53 // The select value acts as a proxy for the actual value.
54 const [selectValue, setSelectValue] = useState(
55 // Determine whether the value is one of the predefined values and set
56 // the select input’s initial value accordingly.
57 () => (options.map((option) => option.value).includes(value) ? value : CUSTOM_AMOUNT)
58 );
59 // We only call the onChange if the value is one of the predefined values.
60 // Otherwise, we effectively delegate control to the currency input.
61 useEffect(() => {
62 if (selectValue !== CUSTOM_AMOUNT) {
63 onChange(selectValue);
64 // I’ve omitted this from the deps array since this shouldn’t run
65 // when the validationError changes. This is just checking to see if
66 // we need to clear it.
67 if (validationError) clearValidationError();
68 }
69 }, [selectValue, onChange]);
70
71 // Ideally, we’d just use the value from the event.target, however, that’s
72 // formatted all nicely and we want a float, so we can just use the
73 const validateCustomAmount = useCallback(() => {
74 if (value) {
75 const minorOfValue = minorOfFloat(value, currency.numberDecimals);
76 const minorOfMin = minorOfFloat(min, currency.numberDecimals);
77 const minorOfMax = minorOfFloat(max, currency.numberDecimals);
78
79 if (minorOfValue > minorOfMax) {
80 setValidationError(
81 sprintf(__('Amount must be less than %s.', 'give'), formatValue({value: max, ...formatConfig}))
82 );
83 } else if (minorOfValue < minorOfMin) {
84 setValidationError(
85 sprintf(__('Amount must be more than %s.', 'give'), formatValue({value: min, ...formatConfig}))
86 );
87 } else {
88 clearValidationError();
89 }
90 } else {
91 setValidationError(
92 sprintf(__('Amount must be more than %s.', 'give'), formatValue({value: min, ...formatConfig}))
93 );
94 }
95 }, [currency.numberDecimals, min, max, value]);
96
97 const customAmountInputId = toUniqueId();
98
99 return (
100 <div className="give-donor-dashboard-amount-inputs">
101 <FieldRow>
102 <SelectControl
103 label={__('Subscription Amount', 'give')}
104 options={options}
105 value={selectValue}
106 onChange={setSelectValue}
107 />
108 {selectValue === CUSTOM_AMOUNT && (
109 <div className="give-donor-dashboard-currency-control">
110 <label
111 className="give-donor-dashboard-currency-control__label"
112 htmlFor={customAmountInputId}
113 >
114 {__('Custom Amount', 'give')}
115 </label>
116 <div
117 className={cx(
118 'give-donor-dashboard-currency-control__input',
119 validationError && 'has-error'
120 )}
121 >
122 <CurrencyInput
123 id={customAmountInputId}
124 name="custom-amount"
125 placeholder={__('Enter amount...', 'give')}
126 defaultValue={value ?? ''}
127 onValueChange={(value, name, values) => {
128 // 4.14.1 Normalize to standard decimal format (period as decimal separator) or consistent API communication regardless of locale
129 const normalizedValue = values?.float != null
130 ? values.float.toFixed(formatConfig.decimalScale)
131 : value;
132 onChange(normalizedValue);
133 }}
134 onBlur={validateCustomAmount}
135 allowNegativeValue={false}
136 {...formatConfig}
137 />
138 </div>
139 </div>
140 )}
141 </FieldRow>
142 {validationError && (
143 <FieldRow>
144 <div className="give-donor-dashboard-amount-inputs__validation-error-message">
145 {validationError}
146 </div>
147 </FieldRow>
148 )}
149 </div>
150 );
151 };
152
153 export default AmountControl;
154