diff --git a/apps/full-site-editing/full-site-editing-plugin/donations/context.js b/apps/full-site-editing/full-site-editing-plugin/donations/context.js new file mode 100644 index 00000000000000..7a0e02fc466829 --- /dev/null +++ b/apps/full-site-editing/full-site-editing-plugin/donations/context.js @@ -0,0 +1,11 @@ +/** + * WordPress dependencies + */ +// eslint-disable-next-line wpcalypso/import-docblock +import { createContext } from '@wordpress/element'; + +const Context = createContext( { + activeTab: 'one-time', +} ); + +export default Context; diff --git a/apps/full-site-editing/full-site-editing-plugin/donations/controls.js b/apps/full-site-editing/full-site-editing-plugin/donations/controls.js new file mode 100644 index 00000000000000..53054049a74bbd --- /dev/null +++ b/apps/full-site-editing/full-site-editing-plugin/donations/controls.js @@ -0,0 +1,42 @@ +/** + * WordPress dependencies + */ +// eslint-disable-next-line wpcalypso/import-docblock +import { ExternalLink, PanelBody, ToggleControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { InspectorControls } from '@wordpress/block-editor'; + +const Controls = ( props ) => { + const { attributes, setAttributes, products, siteSlug } = props; + const { monthlyPlanId, annuallyPlanId, showCustomAmount } = attributes; + return ( + + + + setAttributes( { monthlyPlanId: value ? products[ '1 month' ] : null } ) + } + label={ __( 'Show monthly donations', 'full-site-editing' ) } + /> + + setAttributes( { annuallyPlanId: value ? products[ '1 year' ] : null } ) + } + label={ __( 'Show annual donations', 'full-site-editing' ) } + /> + setAttributes( { showCustomAmount: value } ) } + label={ __( 'Show custom amount option', 'full-site-editing' ) } + /> + + { __( 'View donation earnings', 'full-site-editing' ) } + + + + ); +}; + +export default Controls; diff --git a/apps/full-site-editing/full-site-editing-plugin/donations/donations.js b/apps/full-site-editing/full-site-editing-plugin/donations/donations.js deleted file mode 100644 index 77733c928c91a8..00000000000000 --- a/apps/full-site-editing/full-site-editing-plugin/donations/donations.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * External dependencies - */ -import classNames from 'classnames'; - -/** - * WordPress dependencies - */ -import { InspectorControls, __experimentalBlock as Block } from '@wordpress/block-editor'; -import { Button, ExternalLink, PanelBody, ToggleControl } from '@wordpress/components'; -import { useEffect, useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import StripeNudge from './stripe-nudge'; - -const Donations = ( { attributes, products, setAttributes, siteSlug, stripeConnectUrl } ) => { - const { oneTimePlanId, monthlyPlanId, annuallyPlanId, showCustomAmount } = attributes; - const [ activeTab, setActiveTab ] = useState( 'one-time' ); - - const isTabActive = ( tab ) => activeTab === tab; - - useEffect( () => { - if ( ! oneTimePlanId ) { - setAttributes( { oneTimePlanId: products[ 'one-time' ] } ); - } - }, [ oneTimePlanId ] ); - - return ( - - { stripeConnectUrl && } -
- { ( monthlyPlanId || annuallyPlanId ) && ( -
- - { monthlyPlanId && ( - - ) } - { annuallyPlanId && ( - - ) } -
- ) } -
-
- { isTabActive( 'one-time' ) && __( 'Make a one-time donation', 'full-site-editing' ) } - { isTabActive( 'monthly' ) && __( 'Make a monthly donation', 'full-site-editing' ) } - { isTabActive( 'annually' ) && __( 'Make a yearly donation', 'full-site-editing' ) } -
- { showCustomAmount && ( -
{ __( 'Or enter a custom amount', 'full-site-editing' ) }
- ) } -
-
- - - { - setAttributes( { - monthlyPlanId: showMonthlyDonations ? products[ '1 month' ] : null, - } ); - } } - label={ __( 'Show monthly donations', 'full-site-editing' ) } - /> - { - setAttributes( { - annuallyPlanId: showAnnuallyDonations ? products[ '1 year' ] : null, - } ); - } } - label={ __( 'Show annual donations', 'full-site-editing' ) } - /> - { - setAttributes( { - showCustomAmount: newShowCustomAmountValue, - } ); - } } - label={ __( 'Show custom amount option', 'full-site-editing' ) } - /> - - { __( 'View donation earnings', 'full-site-editing' ) } - - - -
- ); -}; - -export default Donations; diff --git a/apps/full-site-editing/full-site-editing-plugin/donations/edit.js b/apps/full-site-editing/full-site-editing-plugin/donations/edit.js index 6f1874bd30b80e..813de56d8d95ec 100644 --- a/apps/full-site-editing/full-site-editing-plugin/donations/edit.js +++ b/apps/full-site-editing/full-site-editing-plugin/donations/edit.js @@ -7,14 +7,17 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import Donations from './donations'; +import Tabs from './tabs'; import LoadingError from './loading-error'; import LoadingStatus from './loading-status'; import UpgradePlan from './upgrade-plan'; import fetchDefaultProducts from './fetch-default-products'; import fetchStatus from './fetch-status'; -const Edit = ( { attributes, setAttributes } ) => { +const Edit = ( props ) => { + const { attributes } = props; + const { currency } = attributes; + const [ isLoading, setIsLoading ] = useState( true ); const [ loadingError, setLoadingError ] = useState( '' ); const [ shouldUpgrade, setShouldUpgrade ] = useState( false ); @@ -28,14 +31,13 @@ const Edit = ( { attributes, setAttributes } ) => { setIsLoading( false ); }; - const filterProducts = ( productList, productCurrency ) => { - return productList.reduce( ( filteredProducts, { id, currency, type, interval } ) => { - if ( currency === productCurrency && type === 'donation' ) { + const filterProducts = ( productList ) => + productList.reduce( ( filteredProducts, { id, currency: productCurrency, type, interval } ) => { + if ( productCurrency === currency && type === 'donation' ) { filteredProducts[ interval ] = id; } return filteredProducts; }, {} ); - }; const hasRequiredProducts = ( productIdsPerInterval ) => { const intervals = Object.keys( productIdsPerInterval ); @@ -56,13 +58,13 @@ const Edit = ( { attributes, setAttributes } ) => { setStripeConnectUrl( result.connect_url ); setSiteSlug( result.site_slug ); - const filteredProducts = filterProducts( result.products, 'USD' ); + const filteredProducts = filterProducts( result.products ); if ( hasRequiredProducts( filteredProducts ) ) { setProducts( filteredProducts ); } else { - fetchDefaultProducts( 'USD' ).then( - ( defaultProducts ) => setProducts( filterProducts( defaultProducts, 'USD' ) ), + fetchDefaultProducts( currency ).then( + ( defaultProducts ) => setProducts( filterProducts( defaultProducts ) ), apiError ); } @@ -89,11 +91,10 @@ const Edit = ( { attributes, setAttributes } ) => { } return ( - ); diff --git a/apps/full-site-editing/full-site-editing-plugin/donations/index.js b/apps/full-site-editing/full-site-editing-plugin/donations/index.js index eec152e91022af..bad1600fc0627c 100644 --- a/apps/full-site-editing/full-site-editing-plugin/donations/index.js +++ b/apps/full-site-editing/full-site-editing-plugin/donations/index.js @@ -4,14 +4,106 @@ import apiFetch from '@wordpress/api-fetch'; import { getBlockType, registerBlockType, unregisterBlockType } from '@wordpress/blocks'; import { __, _x } from '@wordpress/i18n'; -/* eslint-enable wpcalypso/import-docblock */ /** * Internal dependencies */ import edit from './edit'; + +/** + * Style dependencies + */ import './style.scss'; +const name = 'a8c/donations'; + +const settings = { + title: __( 'Donations (a8c-only)', 'full-site-editing' ), + description: __( + 'Collect one-time, monthly, or annually recurring donations.', + 'full-site-editing' + ), + attributes: { + currency: { + type: 'string', + default: 'USD', + }, + oneTimePlanId: { + type: 'number', + default: null, + }, + monthlyPlanId: { + type: 'number', + default: null, + }, + annuallyPlanId: { + type: 'number', + default: null, + }, + showCustomAmount: { + type: 'boolean', + default: true, + }, + oneTimeHeading: { + type: 'string', + default: __( 'Make a one-time donation', 'full-site-editing' ), + }, + monthlyHeading: { + type: 'string', + default: __( 'Make a monthly donation', 'full-site-editing' ), + }, + annualHeading: { + type: 'string', + default: __( 'Make a yearly donation', 'full-site-editing' ), + }, + chooseAmountText: { + type: 'string', + default: __( 'Choose an amount (USD)', 'full-site-editing' ), + }, + customAmountText: { + type: 'string', + default: __( 'Or enter a custom amount', 'full-site-editing' ), + }, + extraText: { + type: 'string', + default: __( 'Your contribution is appreciated.', 'full-site-editing' ), + }, + oneTimeButtonText: { + type: 'string', + default: __( 'Donate', 'full-site-editing' ), + }, + monthlyButtonText: { + type: 'string', + default: __( 'Donate monthly', 'full-site-editing' ), + }, + annualButtonText: { + type: 'string', + default: __( 'Donate yearly', 'full-site-editing' ), + }, + }, + category: 'common', + icon: ( + + + + + + ), + supports: { + html: false, + }, + edit, + save: () => null, +}; + /** * Appends a "paid" tag to the Donations block title if site requires an upgrade. */ @@ -25,7 +117,7 @@ const addPaidBlockFlags = async () => { } const shouldUpgrade = membershipsStatus.should_upgrade_to_access_memberships; if ( shouldUpgrade ) { - const donationsBlock = getBlockType( 'a8c/donations' ); + const donationsBlock = getBlockType( name ); if ( ! donationsBlock ) { return; } @@ -36,8 +128,8 @@ const addPaidBlockFlags = async () => { 'full-site-editing' ); - unregisterBlockType( 'a8c/donations' ); - registerBlockType( 'a8c/donations', { + unregisterBlockType( name ); + registerBlockType( name, { ...donationsBlock, title: `${ donationsBlock.title } (${ paidFlag })`, } ); @@ -45,67 +137,7 @@ const addPaidBlockFlags = async () => { }; function registerDonationsBlock() { - registerBlockType( 'a8c/donations', { - title: __( 'Donations (a8c-only)', 'full-site-editing' ), - description: __( - 'Collect one-time, monthly, or annually recurring donations.', - 'full-site-editing' - ), - attributes: { - oneTimePlanId: { - type: 'number', - default: null, - }, - monthlyPlanId: { - type: 'number', - default: null, - }, - annuallyPlanId: { - type: 'number', - default: null, - }, - showCustomAmount: { - type: 'boolean', - default: false, - }, - }, - category: 'common', - icon: ( - - - - - - ), - supports: { - html: false, - }, - edit, - save() { - return
Donations block
; - }, - } ); + registerBlockType( name, settings ); // Done after registration so the status API request doesn't suspend the execution. addPaidBlockFlags(); diff --git a/apps/full-site-editing/full-site-editing-plugin/donations/stripe-nudge.js b/apps/full-site-editing/full-site-editing-plugin/donations/stripe-nudge.js index f25bdd17663921..d7fe7216b90588 100644 --- a/apps/full-site-editing/full-site-editing-plugin/donations/stripe-nudge.js +++ b/apps/full-site-editing/full-site-editing-plugin/donations/stripe-nudge.js @@ -1,20 +1,13 @@ -/** - * External dependencies - */ /** * WordPress dependencies */ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line wpcalypso/import-docblock,no-restricted-imports import { Button, Dashicon } from '@wordpress/components'; import { Warning } from '@wordpress/block-editor'; import { compose } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; import { withDispatch } from '@wordpress/data'; -/** - * Internal dependencies - */ - const StripeNudge = ( { autosaveAndRedirect, stripeConnectUrl } ) => ( { + const { attributes, interval, setAttributes } = props; + + const getAttribute = ( attributeName ) => { + if ( attributeName in attributesPerInterval ) { + return attributes[ attributesPerInterval[ attributeName ][ interval ] ]; + } + return attributes[ attributeName ]; + }; + + const setAttribute = ( attributeName, value ) => { + if ( attributeName in attributesPerInterval ) { + return setAttributes( { + [ attributesPerInterval[ attributeName ][ interval ] ]: value, + } ); + } + return setAttributes( { [ attributeName ]: value } ); + }; + const currency = getAttribute( 'currency' ); + + const minAmount = minimumTransactionAmountForCurrency( currency ); + // TODO: This generates good amounts for USD, but let's revisit once we support more currencies. + const tiers = [ + minAmount * 10, // USD 5 + minAmount * 30, // USD 15 + minAmount * 200, // USD 100 + ]; + const customAmountPlaceholder = minAmount * 100; // USD 50 + + return ( + + { ( { activeTab } ) => ( + + ) } + + ); +}; + +export default Tab; diff --git a/apps/full-site-editing/full-site-editing-plugin/donations/tabs.js b/apps/full-site-editing/full-site-editing-plugin/donations/tabs.js new file mode 100644 index 00000000000000..864c45f124285d --- /dev/null +++ b/apps/full-site-editing/full-site-editing-plugin/donations/tabs.js @@ -0,0 +1,88 @@ +/** + * External dependencies + */ +import classNames from 'classnames'; + +/** + * WordPress dependencies + */ +import { __experimentalBlock as Block } from '@wordpress/block-editor'; +import { Button } from '@wordpress/components'; +import { useEffect, useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import Context from './context'; +import Controls from './controls'; +import Tab from './tab'; +import StripeNudge from './stripe-nudge'; + +const Tabs = ( props ) => { + const { attributes, products, setAttributes, stripeConnectUrl } = props; + const { oneTimePlanId, monthlyPlanId, annuallyPlanId } = attributes; + const [ activeTab, setActiveTab ] = useState( 'one-time' ); + + const isTabActive = ( tab ) => activeTab === tab; + + const tabs = { + 'one-time': { title: __( 'One-Time', 'full-site-editing' ) }, + ...( monthlyPlanId && { '1 month': { title: __( 'Monthly', 'full-site-editing' ) } } ), + ...( annuallyPlanId && { '1 year': { title: __( 'Yearly', 'full-site-editing' ) } } ), + }; + + // Sets the plans when the block is inserted. + useEffect( () => { + // Since there is no setting for disabling the one-time option, we can assume that the block has been just + // inserted if the attribute `oneTimePlanId` is not set. + if ( ! oneTimePlanId ) { + setAttributes( { + oneTimePlanId: products[ 'one-time' ], + monthlyPlanId: products[ '1 month' ], + annuallyPlanId: products[ '1 year' ], + } ); + } + }, [ oneTimePlanId ] ); + + // Activates the one-time tab if the interval of the current active tab is disabled. + useEffect( () => { + if ( ! monthlyPlanId && isTabActive( '1 month' ) ) { + setActiveTab( 'one-time' ); + } + + if ( ! annuallyPlanId && isTabActive( '1 year' ) ) { + setActiveTab( 'one-time' ); + } + }, [ monthlyPlanId, annuallyPlanId ] ); + + return ( + + { stripeConnectUrl && } +
+ { Object.keys( tabs ).length > 1 && ( +
+ { Object.entries( tabs ).map( ( [ interval, { title } ] ) => ( + + ) ) } +
+ ) } +
+ + { Object.keys( tabs ).map( ( interval ) => ( + + ) ) } + +
+
+ +
+ ); +}; + +export default Tabs;