diff --git a/extensions/blocks/donations/amount.js b/extensions/blocks/donations/amount.js new file mode 100644 index 0000000000000..307829891b164 --- /dev/null +++ b/extensions/blocks/donations/amount.js @@ -0,0 +1,150 @@ +/** + * External dependencies + */ +import formatCurrency, { CURRENCIES } from '@automattic/format-currency'; +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { RichText } from '@wordpress/block-editor'; +import { useCallback, useEffect, useRef, useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { minimumTransactionAmountForCurrency } from '../../shared/currencies'; + +const Amount = ( { + className = '', + currency = null, + defaultValue = null, + editable = false, + label = '', + onChange = null, + value = '', +} ) => { + const [ editedValue, setEditedValue ] = useState( + formatCurrency( value, currency, { symbol: '' } ) + ); + const [ isFocused, setIsFocused ] = useState( false ); + const [ isInvalid, setIsInvalid ] = useState( false ); + const richTextRef = useRef( null ); + + const parseAmount = useCallback( + amount => { + if ( ! amount ) { + return null; + } + + if ( typeof amount === 'number' ) { + return amount; + } + + amount = parseFloat( + amount + // Remove any thousand grouping separator. + .replace( new RegExp( '\\' + CURRENCIES[ currency ].grouping, 'g' ), '' ) + // Replace the localized decimal separator with a dot (the standard decimal separator in float numbers). + .replace( new RegExp( '\\' + CURRENCIES[ currency ].decimal, 'g' ), '.' ) + ); + + if ( isNaN( amount ) ) { + return null; + } + + return amount; + }, + [ currency ] + ); + + const setAmount = useCallback( + amount => { + setEditedValue( amount ); + + if ( ! onChange ) { + return; + } + + const parsedAmount = parseAmount( amount, currency ); + if ( parsedAmount && parsedAmount >= minimumTransactionAmountForCurrency( currency ) ) { + onChange( parsedAmount ); + setIsInvalid( false ); + } else if ( amount ) { + setIsInvalid( true ); + } + }, + [ currency, parseAmount, onChange ] + ); + + const setFocus = () => { + if ( ! richTextRef.current ) { + return; + } + + richTextRef.current.focus(); + setIsFocused( true ); + }; + + // Tracks when user clicks out the input. Cannot be done with an `onBlur` prop because `RichText` does not support it. + useEffect( () => { + if ( ! richTextRef.current ) { + return; + } + + richTextRef.current.addEventListener( 'blur', () => setIsFocused( false ) ); + }, [ richTextRef ] ); + + // Sets a default value if empty when user clicks out the input. + useEffect( () => { + if ( isFocused || editedValue ) { + return; + } + + setAmount( formatCurrency( defaultValue, currency, { symbol: '' } ) ); + }, [ currency, defaultValue, editedValue, isFocused, setAmount ] ); + + // Syncs the edited value with the actual value whenever the latter changes (e.g. new default amount after a currency change). + useEffect( () => { + if ( isFocused || isInvalid ) { + return; + } + setEditedValue( formatCurrency( value, currency, { symbol: '' } ) ); + }, [ currency, isFocused, isInvalid, setAmount, value ] ); + + return ( +
+
+ { CURRENCIES[ currency ].symbol } + { editable ? ( + setAmount( amount ) } + placeholder={ formatCurrency( defaultValue, currency, { symbol: '' } ) } + ref={ richTextRef } + value={ editedValue } + withoutInteractiveFormatting + /> + ) : ( + + { formatCurrency( value ? value : defaultValue, currency, { symbol: '' } ) } + + ) } +
+
+ ); +}; + +export default Amount; diff --git a/extensions/blocks/donations/attributes.js b/extensions/blocks/donations/attributes.js index 5446d6a8ce5cf..2beef97025102 100644 --- a/extensions/blocks/donations/attributes.js +++ b/extensions/blocks/donations/attributes.js @@ -8,6 +8,13 @@ export default { type: 'string', default: 'USD', }, + amounts: { + type: 'array', + items: { + type: 'number', + }, + default: [ 5, 15, 100 ], + }, oneTimePlanId: { type: 'number', default: null, @@ -38,7 +45,7 @@ export default { }, chooseAmountText: { type: 'string', - default: __( 'Choose an amount (USD)', 'jetpack' ), + default: __( 'Choose an amount', 'jetpack' ), }, customAmountText: { type: 'string', diff --git a/extensions/blocks/donations/controls.js b/extensions/blocks/donations/controls.js index 46e2c1607cd0a..e60c0c38c5a5d 100644 --- a/extensions/blocks/donations/controls.js +++ b/extensions/blocks/donations/controls.js @@ -1,40 +1,107 @@ /** * WordPress dependencies */ -import { ExternalLink, PanelBody, ToggleControl } from '@wordpress/components'; +import { BlockControls, InspectorControls } from '@wordpress/block-editor'; +import { + Button, + Dashicon, + Dropdown, + ExternalLink, + MenuGroup, + MenuItem, + PanelBody, + ToggleControl, + ToolbarGroup, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { InspectorControls } from '@wordpress/block-editor'; +import { DOWN } from '@wordpress/keycodes'; + +/** + * Internal dependencies + */ +import { SUPPORTED_CURRENCIES } from '../../shared/currencies'; +import { CURRENCIES } from '@automattic/format-currency'; const Controls = props => { const { attributes, setAttributes, products, siteSlug } = props; - const { monthlyPlanId, annuallyPlanId, showCustomAmount } = attributes; + const { currency, monthlyPlanId, annuallyPlanId, showCustomAmount } = attributes; + return ( - - - - setAttributes( { monthlyPlanId: value ? products[ '1 month' ] : null } ) - } - label={ __( 'Show monthly donations', 'jetpack' ) } - /> - - setAttributes( { annuallyPlanId: value ? products[ '1 year' ] : null } ) - } - label={ __( 'Show annual donations', 'jetpack' ) } - /> - setAttributes( { showCustomAmount: value } ) } - label={ __( 'Show custom amount option', 'jetpack' ) } - /> - - { __( 'View donation earnings', 'jetpack' ) } - - - + <> + + + { + const openOnArrowDown = event => { + if ( ! isOpen && event.keyCode === DOWN ) { + event.preventDefault(); + event.stopPropagation(); + onToggle(); + } + }; + + return ( + @@ -88,9 +91,7 @@ const Tabs = props => { ) }
- { Object.keys( tabs ).map( interval => ( - - ) ) } +