summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/jetpack/extensions/blocks/simple-payments/edit.js')
-rw-r--r--plugins/jetpack/extensions/blocks/simple-payments/edit.js579
1 files changed, 0 insertions, 579 deletions
diff --git a/plugins/jetpack/extensions/blocks/simple-payments/edit.js b/plugins/jetpack/extensions/blocks/simple-payments/edit.js
deleted file mode 100644
index f49ca94d..00000000
--- a/plugins/jetpack/extensions/blocks/simple-payments/edit.js
+++ /dev/null
@@ -1,579 +0,0 @@
-/**
- * External dependencies
- */
-import classNames from 'classnames';
-import emailValidator from 'email-validator';
-import { __, _n, sprintf } from '@wordpress/i18n';
-import { Component } from '@wordpress/element';
-import { compose, withInstanceId } from '@wordpress/compose';
-import { dispatch, withSelect } from '@wordpress/data';
-import { get, isEmpty, isEqual, pick, trimEnd } from 'lodash';
-import { getCurrencyDefaults } from '@automattic/format-currency';
-import {
- Disabled,
- ExternalLink,
- SelectControl,
- TextareaControl,
- TextControl,
- ToggleControl,
-} from '@wordpress/components';
-
-/**
- * Internal dependencies
- */
-import HelpMessage from './help-message';
-import ProductPlaceholder from './product-placeholder';
-import FeaturedMedia from './featured-media';
-import { decimalPlaces, formatPrice } from './utils';
-import { SIMPLE_PAYMENTS_PRODUCT_POST_TYPE, SUPPORTED_CURRENCY_LIST } from './constants';
-
-class SimplePaymentsEdit extends Component {
- state = {
- fieldEmailError: null,
- fieldPriceError: null,
- fieldTitleError: null,
- isSavingProduct: false,
- };
-
- /**
- * We'll use this flag to inject attributes one time when the product entity is loaded.
- *
- * It is based on the presence of a `productId` attribute.
- *
- * If present, initially we are waiting for attributes to be injected.
- * If absent, we may save the product in the future but do not need to inject attributes based
- * on the response as they will have come from our product submission.
- */
- shouldInjectPaymentAttributes = !! this.props.attributes.productId;
-
- componentDidMount() {
- // Try to get the simplePayment loaded into attributes if possible.
- this.injectPaymentAttributes();
-
- const { attributes, hasPublishAction } = this.props;
- const { productId } = attributes;
-
- // If the user can publish save an empty product so that we have an ID and can save
- // concurrently with the post that contains the Simple Payment.
- if ( ! productId && hasPublishAction ) {
- this.saveProduct();
- }
- }
-
- componentDidUpdate( prevProps ) {
- const { hasPublishAction, isSelected } = this.props;
-
- if ( ! isEqual( prevProps.simplePayment, this.props.simplePayment ) ) {
- this.injectPaymentAttributes();
- }
-
- if (
- ! prevProps.isSaving &&
- this.props.isSaving &&
- hasPublishAction &&
- this.validateAttributes()
- ) {
- // Validate and save product on post save
- this.saveProduct();
- } else if ( prevProps.isSelected && ! isSelected ) {
- // Validate on block deselect
- this.validateAttributes();
- }
- }
-
- injectPaymentAttributes() {
- /**
- * Prevent injecting the product attributes when not desired.
- *
- * When we first load a product, we should inject its attributes as our initial form state.
- * When subsequent saves occur, we should avoid injecting attributes so that we do not
- * overwrite changes that the user has made with stale state from the previous save.
- */
-
- const { simplePayment } = this.props;
- if ( ! this.shouldInjectPaymentAttributes || isEmpty( simplePayment ) ) {
- return;
- }
-
- const { attributes, setAttributes } = this.props;
- const { content, currency, email, featuredMediaId, multiple, price, title } = attributes;
-
- setAttributes( {
- content: get( simplePayment, [ 'content', 'raw' ], content ),
- currency: get( simplePayment, [ 'meta', 'spay_currency' ], currency ),
- email: get( simplePayment, [ 'meta', 'spay_email' ], email ),
- featuredMediaId: get( simplePayment, [ 'featured_media' ], featuredMediaId ),
- multiple: Boolean( get( simplePayment, [ 'meta', 'spay_multiple' ], Boolean( multiple ) ) ),
- price: get( simplePayment, [ 'meta', 'spay_price' ], price || undefined ),
- title: get( simplePayment, [ 'title', 'raw' ], title ),
- } );
-
- this.shouldInjectPaymentAttributes = ! this.shouldInjectPaymentAttributes;
- }
-
- toApi() {
- const { attributes } = this.props;
- const {
- content,
- currency,
- email,
- featuredMediaId,
- multiple,
- price,
- productId,
- title,
- } = attributes;
-
- return {
- id: productId,
- content,
- featured_media: featuredMediaId,
- meta: {
- spay_currency: currency,
- spay_email: email,
- spay_multiple: multiple,
- spay_price: price,
- },
- status: productId ? 'publish' : 'draft',
- title,
- };
- }
-
- saveProduct() {
- if ( this.state.isSavingProduct ) {
- return;
- }
-
- const { attributes, setAttributes } = this.props;
- const { email } = attributes;
- const { saveEntityRecord } = dispatch( 'core' );
-
- this.setState( { isSavingProduct: true }, () => {
- saveEntityRecord( 'postType', SIMPLE_PAYMENTS_PRODUCT_POST_TYPE, this.toApi() )
- .then( record => {
- if ( record ) {
- setAttributes( { productId: record.id } );
- }
-
- return record;
- } )
- .catch( error => {
- // Nothing we can do about errors without details at the moment
- if ( ! error || ! error.data ) {
- return;
- }
-
- const {
- data: { key: apiErrorKey },
- } = error;
-
- // @TODO errors in other fields
- this.setState( {
- fieldEmailError:
- apiErrorKey === 'spay_email'
- ? sprintf( __( '%s is not a valid email address.', 'jetpack' ), email )
- : null,
- fieldPriceError:
- apiErrorKey === 'spay_price' ? __( 'Invalid price.', 'jetpack' ) : null,
- } );
- } )
- .finally( () => {
- this.setState( {
- isSavingProduct: false,
- } );
- } );
- } );
- }
-
- validateAttributes = () => {
- const isPriceValid = this.validatePrice();
- const isTitleValid = this.validateTitle();
- const isEmailValid = this.validateEmail();
- const isCurrencyValid = this.validateCurrency();
-
- return isPriceValid && isTitleValid && isEmailValid && isCurrencyValid;
- };
-
- /**
- * Validate currency
- *
- * This method does not include validation UI. Currency selection should not allow for invalid
- * values. It is primarily to ensure that the currency is valid to save.
- *
- * @return {boolean} True if currency is valid
- */
- validateCurrency = () => {
- const { currency } = this.props.attributes;
- return SUPPORTED_CURRENCY_LIST.includes( currency );
- };
-
- /**
- * Validate price
- *
- * Stores error message in state.fieldPriceError
- *
- * @returns {Boolean} True when valid, false when invalid
- */
- validatePrice = () => {
- const { currency, price } = this.props.attributes;
- const { precision } = getCurrencyDefaults( currency );
-
- if ( ! price || parseFloat( price ) === 0 ) {
- this.setState( {
- fieldPriceError: __(
- 'If you’re selling something, you need a price tag. Add yours here.',
- 'jetpack'
- ),
- } );
- return false;
- }
-
- if ( Number.isNaN( parseFloat( price ) ) ) {
- this.setState( {
- fieldPriceError: __( 'Invalid price', 'jetpack' ),
- } );
- return false;
- }
-
- if ( parseFloat( price ) < 0 ) {
- this.setState( {
- fieldPriceError: __(
- 'Your price is negative — enter a positive number so people can pay the right amount.',
- 'jetpack'
- ),
- } );
- return false;
- }
-
- if ( decimalPlaces( price ) > precision ) {
- if ( precision === 0 ) {
- this.setState( {
- fieldPriceError: __(
- 'We know every penny counts, but prices in this currency can’t contain decimal values.',
- 'jetpack'
- ),
- } );
- return false;
- }
-
- this.setState( {
- fieldPriceError: sprintf(
- _n(
- 'The price cannot have more than %d decimal place.',
- 'The price cannot have more than %d decimal places.',
- precision,
- 'jetpack'
- ),
- precision
- ),
- } );
- return false;
- }
-
- if ( this.state.fieldPriceError ) {
- this.setState( { fieldPriceError: null } );
- }
-
- return true;
- };
-
- /**
- * Validate email
- *
- * Stores error message in state.fieldEmailError
- *
- * @returns {Boolean} True when valid, false when invalid
- */
- validateEmail = () => {
- const { email } = this.props.attributes;
- if ( ! email ) {
- this.setState( {
- fieldEmailError: __(
- 'We want to make sure payments reach you, so please add an email address.',
- 'jetpack'
- ),
- } );
- return false;
- }
-
- if ( ! emailValidator.validate( email ) ) {
- this.setState( {
- fieldEmailError: sprintf( __( '%s is not a valid email address.', 'jetpack' ), email ),
- } );
- return false;
- }
-
- if ( this.state.fieldEmailError ) {
- this.setState( { fieldEmailError: null } );
- }
-
- return true;
- };
-
- /**
- * Validate title
- *
- * Stores error message in state.fieldTitleError
- *
- * @returns {Boolean} True when valid, false when invalid
- */
- validateTitle = () => {
- const { title } = this.props.attributes;
- if ( ! title ) {
- this.setState( {
- fieldTitleError: __(
- 'Please add a brief title so that people know what they’re paying for.',
- 'jetpack'
- ),
- } );
- return false;
- }
-
- if ( this.state.fieldTitleError ) {
- this.setState( { fieldTitleError: null } );
- }
-
- return true;
- };
-
- handleEmailChange = email => {
- this.props.setAttributes( { email } );
- this.setState( { fieldEmailError: null } );
- };
-
- handleFeaturedMediaSelect = media => {
- this.props.setAttributes( { featuredMediaId: get( media, 'id', 0 ) } );
- };
-
- handleContentChange = content => {
- this.props.setAttributes( { content } );
- };
-
- handlePriceChange = price => {
- price = parseFloat( price );
- if ( ! isNaN( price ) ) {
- this.props.setAttributes( { price } );
- } else {
- this.props.setAttributes( { price: undefined } );
- }
- this.setState( { fieldPriceError: null } );
- };
-
- handleCurrencyChange = currency => {
- this.props.setAttributes( { currency } );
- };
-
- handleMultipleChange = multiple => {
- this.props.setAttributes( { multiple: !! multiple } );
- };
-
- handleTitleChange = title => {
- this.props.setAttributes( { title } );
- this.setState( { fieldTitleError: null } );
- };
-
- getCurrencyList = SUPPORTED_CURRENCY_LIST.map( value => {
- const { symbol } = getCurrencyDefaults( value );
- // if symbol is equal to the code (e.g., 'CHF' === 'CHF'), don't duplicate it.
- // trim the dot at the end, e.g., 'kr.' becomes 'kr'
- const label = symbol === value ? value : `${ value } ${ trimEnd( symbol, '.' ) }`;
- return { value, label };
- } );
-
- render() {
- const { fieldEmailError, fieldPriceError, fieldTitleError } = this.state;
- const {
- attributes,
- featuredMedia,
- instanceId,
- isSelected,
- setAttributes,
- simplePayment,
- } = this.props;
- const {
- content,
- currency,
- email,
- featuredMediaId,
- featuredMediaUrl: featuredMediaUrlAttribute,
- featuredMediaTitle: featuredMediaTitleAttribute,
- multiple,
- price,
- productId,
- title,
- } = attributes;
-
- const featuredMediaUrl =
- featuredMediaUrlAttribute || ( featuredMedia && featuredMedia.source_url );
- const featuredMediaTitle =
- featuredMediaTitleAttribute || ( featuredMedia && featuredMedia.alt_text );
-
- /**
- * The only disabled state that concerns us is when we expect a product but don't have it in
- * local state.
- */
- const isDisabled = productId && isEmpty( simplePayment );
-
- if ( ! isSelected && isDisabled ) {
- return (
- <div className="simple-payments__loading">
- <ProductPlaceholder
- aria-busy="true"
- content="█████"
- formattedPrice="█████"
- title="█████"
- />
- </div>
- );
- }
-
- if (
- ! isSelected &&
- email &&
- price &&
- title &&
- ! fieldEmailError &&
- ! fieldPriceError &&
- ! fieldTitleError
- ) {
- return (
- <ProductPlaceholder
- aria-busy="false"
- content={ content }
- featuredMediaUrl={ featuredMediaUrl }
- featuredMediaTitle={ featuredMediaTitle }
- formattedPrice={ formatPrice( price, currency ) }
- multiple={ multiple }
- title={ title }
- />
- );
- }
-
- const Wrapper = isDisabled ? Disabled : 'div';
-
- return (
- <Wrapper className="wp-block-jetpack-simple-payments">
- <FeaturedMedia
- { ...{ featuredMediaId, featuredMediaUrl, featuredMediaTitle, setAttributes } }
- />
- <div>
- <TextControl
- aria-describedby={ `${ instanceId }-title-error` }
- className={ classNames( 'simple-payments__field', 'simple-payments__field-title', {
- 'simple-payments__field-has-error': fieldTitleError,
- } ) }
- label={ __( 'Item name', 'jetpack' ) }
- onChange={ this.handleTitleChange }
- placeholder={ __( 'Item name', 'jetpack' ) }
- required
- type="text"
- value={ title }
- />
- <HelpMessage id={ `${ instanceId }-title-error` } isError>
- { fieldTitleError }
- </HelpMessage>
-
- <TextareaControl
- className="simple-payments__field simple-payments__field-content"
- label={ __( 'Describe your item in a few words', 'jetpack' ) }
- onChange={ this.handleContentChange }
- placeholder={ __( 'Describe your item in a few words', 'jetpack' ) }
- value={ content }
- />
-
- <div className="simple-payments__price-container">
- <SelectControl
- className="simple-payments__field simple-payments__field-currency"
- label={ __( 'Currency', 'jetpack' ) }
- onChange={ this.handleCurrencyChange }
- options={ this.getCurrencyList }
- value={ currency }
- />
- <TextControl
- aria-describedby={ `${ instanceId }-price-error` }
- className={ classNames( 'simple-payments__field', 'simple-payments__field-price', {
- 'simple-payments__field-has-error': fieldPriceError,
- } ) }
- label={ __( 'Price', 'jetpack' ) }
- onChange={ this.handlePriceChange }
- placeholder={ formatPrice( 0, currency, false ) }
- required
- step="1"
- type="number"
- value={ price || '' }
- />
- <HelpMessage id={ `${ instanceId }-price-error` } isError>
- { fieldPriceError }
- </HelpMessage>
- </div>
-
- <div className="simple-payments__field-multiple">
- <ToggleControl
- checked={ Boolean( multiple ) }
- label={ __( 'Allow people to buy more than one item at a time', 'jetpack' ) }
- onChange={ this.handleMultipleChange }
- />
- </div>
-
- <TextControl
- aria-describedby={ `${ instanceId }-email-${ fieldEmailError ? 'error' : 'help' }` }
- className={ classNames( 'simple-payments__field', 'simple-payments__field-email', {
- 'simple-payments__field-has-error': fieldEmailError,
- } ) }
- label={ __( 'Email', 'jetpack' ) }
- onChange={ this.handleEmailChange }
- placeholder={ __( 'Email', 'jetpack' ) }
- required
- type="email"
- value={ email }
- />
- <HelpMessage id={ `${ instanceId }-email-error` } isError>
- { fieldEmailError }
- </HelpMessage>
- <HelpMessage id={ `${ instanceId }-email-help` }>
- { __(
- 'Enter the email address associated with your PayPal account. Don’t have an account?',
- 'jetpack'
- ) + ' ' }
- <ExternalLink href="https://www.paypal.com/">
- { __( 'Create one on PayPal', 'jetpack' ) }
- </ExternalLink>
- </HelpMessage>
- </div>
- </Wrapper>
- );
- }
-}
-
-const mapSelectToProps = withSelect( ( select, props ) => {
- const { getEntityRecord, getMedia } = select( 'core' );
- const { isSavingPost, getCurrentPost } = select( 'core/editor' );
-
- const { productId, featuredMediaId } = props.attributes;
-
- const fields = [
- [ 'content' ],
- [ 'meta', 'spay_currency' ],
- [ 'meta', 'spay_email' ],
- [ 'meta', 'spay_multiple' ],
- [ 'meta', 'spay_price' ],
- [ 'title', 'raw' ],
- [ 'featured_media' ],
- ];
-
- const simplePayment = productId
- ? pick( getEntityRecord( 'postType', SIMPLE_PAYMENTS_PRODUCT_POST_TYPE, productId ), fields )
- : undefined;
-
- return {
- hasPublishAction: !! get( getCurrentPost(), [ '_links', 'wp:action-publish' ] ),
- isSaving: !! isSavingPost(),
- simplePayment,
- featuredMedia: featuredMediaId ? getMedia( featuredMediaId ) : null,
- };
-} );
-
-export default compose(
- mapSelectToProps,
- withInstanceId
-)( SimplePaymentsEdit );