Skip to content

Commit

Permalink
Earn: Move donations block to Jetpack Beta Blocks (#16545)
Browse files Browse the repository at this point in the history
We're moving development of the Donation block to Jetpack from the FSE plugin. This block will allow customers on Jetpack and WordPress.com to accept one-time or recurring monthly/annual donations.
  • Loading branch information
gwwar authored Jul 27, 2020
1 parent 53fa313 commit fecc196
Show file tree
Hide file tree
Showing 20 changed files with 805 additions and 50 deletions.
1 change: 1 addition & 0 deletions class.jetpack-plan.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class Jetpack_Plan {
),
'supports' => array(
'akismet',
'donations',
'recurring-payments',
),
),
Expand Down
64 changes: 64 additions & 0 deletions extensions/blocks/donations/attributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* WordPress dependencies
*/
// eslint-disable-next-line wpcalypso/import-docblock
import { __ } from '@wordpress/i18n';

export default {
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', 'jetpack' ),
},
monthlyHeading: {
type: 'string',
default: __( 'Make a monthly donation', 'jetpack' ),
},
annualHeading: {
type: 'string',
default: __( 'Make a yearly donation', 'jetpack' ),
},
chooseAmountText: {
type: 'string',
default: __( 'Choose an amount (USD)', 'jetpack' ),
},
customAmountText: {
type: 'string',
default: __( 'Or enter a custom amount', 'jetpack' ),
},
extraText: {
type: 'string',
default: __( 'Your contribution is appreciated.', 'jetpack' ),
},
oneTimeButtonText: {
type: 'string',
default: __( 'Donate', 'jetpack' ),
},
monthlyButtonText: {
type: 'string',
default: __( 'Donate monthly', 'jetpack' ),
},
annualButtonText: {
type: 'string',
default: __( 'Donate yearly', 'jetpack' ),
},
};
11 changes: 11 additions & 0 deletions extensions/blocks/donations/context.js
Original file line number Diff line number Diff line change
@@ -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;
42 changes: 42 additions & 0 deletions extensions/blocks/donations/controls.js
Original file line number Diff line number Diff line change
@@ -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 (
<InspectorControls>
<PanelBody title={ __( 'Settings', 'jetpack' ) }>
<ToggleControl
checked={ !! monthlyPlanId }
onChange={ value =>
setAttributes( { monthlyPlanId: value ? products[ '1 month' ] : null } )
}
label={ __( 'Show monthly donations', 'jetpack' ) }
/>
<ToggleControl
checked={ !! annuallyPlanId }
onChange={ value =>
setAttributes( { annuallyPlanId: value ? products[ '1 year' ] : null } )
}
label={ __( 'Show annual donations', 'jetpack' ) }
/>
<ToggleControl
checked={ showCustomAmount }
onChange={ value => setAttributes( { showCustomAmount: value } ) }
label={ __( 'Show custom amount option', 'jetpack' ) }
/>
<ExternalLink href={ `https://wordpress.com/earn/payments/${ siteSlug }` }>
{ __( 'View donation earnings', 'jetpack' ) }
</ExternalLink>
</PanelBody>
</InspectorControls>
);
};

export default Controls;
44 changes: 44 additions & 0 deletions extensions/blocks/donations/donations.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
/**
* Donations Block.
*
* @since 8.x
*
* @package Jetpack
*/

namespace Automattic\Jetpack\Extensions\Donations;

use Jetpack_Gutenberg;

const FEATURE_NAME = 'donations';
const BLOCK_NAME = 'jetpack/' . FEATURE_NAME;

/**
* Registers the block for use in Gutenberg
* This is done via an action so that we can disable
* registration if we need to.
*/
function register_block() {
jetpack_register_block(
BLOCK_NAME,
array(
'render_callback' => __NAMESPACE__ . '\load_assets',
'plan_check' => true,
)
);
}
add_action( 'init', __NAMESPACE__ . '\register_block' );

/**
* Donations block registration/dependency declaration.
*
* @param array $attr Array containing the Donations block attributes.
* @param string $content String containing the Donations block content.
*
* @return string
*/
function load_assets( $attr, $content ) {
Jetpack_Gutenberg::load_assets_as_required( FEATURE_NAME );
return $content;
}
112 changes: 112 additions & 0 deletions extensions/blocks/donations/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* WordPress dependencies
*/
import { useState, useEffect } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import Tabs from './tabs';
import LoadingError from './loading-error';
import LoadingStatus from './loading-status';
import fetchDefaultProducts from './fetch-default-products';
import fetchStatus from './fetch-status';

const Edit = props => {
const { attributes, className } = props;
const { currency } = attributes;

const [ isLoading, setIsLoading ] = useState( true );
const [ loadingError, setLoadingError ] = useState( '' );
const [ shouldUpgrade, setShouldUpgrade ] = useState( false );
const [ stripeConnectUrl, setStripeConnectUrl ] = useState( false );
const [ products, setProducts ] = useState( [] );
const [ siteSlug, setSiteSlug ] = useState( '' );

const apiError = message => {
setLoadingError( message );
setIsLoading( false );
};

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 );

return (
intervals.includes( 'one-time' ) &&
intervals.includes( '1 month' ) &&
intervals.includes( '1 year' )
);
};

const mapStatusToState = result => {
if ( ( ! result && typeof result !== 'object' ) || result.errors ) {
setLoadingError( __( 'Could not load data from WordPress.com.', 'jetpack' ) );
setIsLoading( false );
return;
}
setShouldUpgrade( result.should_upgrade_to_access_memberships );
setStripeConnectUrl( result.connect_url );
setSiteSlug( result.site_slug );

const filteredProducts = filterProducts( result.products );

if ( hasRequiredProducts( filteredProducts ) ) {
setProducts( filteredProducts );
setIsLoading( false );
return;
}

// Set fake products when plan should be upgraded or there is no connection to Stripe so users can still try the
// block in the editor.
if ( result.should_upgrade_to_access_memberships || result.connect_url ) {
setIsLoading( false );
setProducts( {
'one-time': -1,
'1 month': -1,
'1 year': -1,
} );
return;
}

// Only create products if we have the correct plan and stripe connection.
fetchDefaultProducts( currency ).then( defaultProducts => {
setIsLoading( false );
return setProducts( filterProducts( defaultProducts ) );
}, apiError );
};

useEffect( () => {
const updateData = () => fetchStatus( 'donation' ).then( mapStatusToState, apiError );
updateData();
}, [] );

if ( isLoading ) {
return <LoadingStatus className={ className } />;
}

if ( loadingError ) {
return <LoadingError className={ className } error={ loadingError } />;
}

return (
<Tabs
{ ...props }
className={ className }
products={ products }
shouldUpgrade={ shouldUpgrade }
siteSlug={ siteSlug }
stripeConnectUrl={ stripeConnectUrl }
/>
);
};

export default Edit;
7 changes: 7 additions & 0 deletions extensions/blocks/donations/editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Internal dependencies
*/
import registerJetpackBlock from '../../shared/register-jetpack-block';
import { name, settings } from '.';

registerJetpackBlock( name, settings );
92 changes: 92 additions & 0 deletions extensions/blocks/donations/editor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
@import '../../shared/styles/gutenberg-base-styles.scss';

.wp-block-jetpack-donations {
.donations__container {
border: 1px solid $light-gray-700;

.donations__tabs {
display: flex;
border-bottom: 1px solid $light-gray-700;

.donations__tab {
font-weight: bold;
display: inline-block;
flex-grow: 1;
text-align: center;
font-size: 16px;
padding: 16px;
height: auto;
border-radius: 0;
border-left: 1px solid $light-gray-700;
background-color: $white;
color: $dark-gray-800;
box-shadow: none;

&:first-child {
border-left: none;
}

// Complex selector needed to override specificity.
&:not(:disabled):not([aria-disabled=true]):not(.is-secondary):not(.is-primary):not(.is-tertiary):not(.is-link):hover {
background-color: $light-gray-100;
box-shadow: none;
}

// Complex selector needed to override specificity.
&.is-active,
&.is-active:not(:disabled):not([aria-disabled=true]):not(.is-secondary):not(.is-primary):not(.is-tertiary):not(.is-link):hover {
background-color: $blue-wordpress-700;
box-shadow: none;
color: $white;
}
}
}

.donations__content {
padding: 0 24px;
}

.donations__amounts {
margin-top: 30px;
margin-bottom: 30px;

.donations__amount:not( .alignleft ):not( .alignright ) {
margin-top: 0;
margin-bottom: 0;
}
}

.donations__amount .wp-block-button__link {
background-color: $white;
color: $dark-gray-800;
border: 1px solid $light-gray-700;
}

.donations__custom-amount {
margin-bottom: 30px;
}

.donations__custom-amount .wp-block-button__link {
cursor: default;
}

.donations__custom-amount-placeholder {
margin-left: 8px;
color: $light-gray-700;
padding-right: 40px;
}

.donations__separator {
line-height: 8px;
height: 8px;
}

.donations__donate-button {
margin-bottom: 30px;
}
}

.jetpack-block-nudge {
max-width: none;
}
}
Loading

0 comments on commit fecc196

Please sign in to comment.