diff --git a/extensions/blocks/calendly/edit.js b/extensions/blocks/calendly/edit.js index b0e994ee0386..7db04d81371f 100644 --- a/extensions/blocks/calendly/edit.js +++ b/extensions/blocks/calendly/edit.js @@ -15,11 +15,12 @@ import { Notice, PanelBody, Placeholder, + Spinner, ToggleControl, Toolbar, withNotices, } from '@wordpress/components'; -import { useState } from '@wordpress/element'; +import { useEffect, useState } from '@wordpress/element'; import { __, _x } from '@wordpress/i18n'; import { getBlockDefaultClassName } from '@wordpress/blocks'; @@ -34,6 +35,8 @@ import { getValidatedAttributes } from '../../shared/get-validated-attributes'; import SubmitButton from '../../shared/submit-button'; import { getAttributesFromEmbedCode } from './utils'; import BlockStylesSelector from '../../shared/components/block-styles-selector'; +import { CALENDLY_EXAMPLE_URL } from './'; +import testEmbedUrl from '../../shared/test-embed-url'; function CalendlyEdit( props ) { const { @@ -62,6 +65,7 @@ function CalendlyEdit( props ) { url, } = validatedAttributes; const [ embedCode, setEmbedCode ] = useState( '' ); + const [ isResolvingUrl, setIsResolvingUrl ] = useState( false ); const setErrorNotice = () => { noticeOperations.removeAllNotices(); @@ -70,6 +74,16 @@ function CalendlyEdit( props ) { ); }; + useEffect( () => { + if ( ! url || CALENDLY_EXAMPLE_URL === url || 'link' === style ) { + return; + } + testEmbedUrl( url, setIsResolvingUrl ).catch( () => { + setAttributes( { url: undefined } ); + setErrorNotice(); + } ); + }, [] ); + const parseEmbedCode = event => { if ( ! event ) { setErrorNotice(); @@ -84,10 +98,16 @@ function CalendlyEdit( props ) { return; } - const newValidatedAttributes = getValidatedAttributes( attributeDetails, newAttributes ); - - setAttributes( newValidatedAttributes ); - noticeOperations.removeAllNotices(); + testEmbedUrl( newAttributes.url, setIsResolvingUrl ) + .then( () => { + const newValidatedAttributes = getValidatedAttributes( attributeDetails, newAttributes ); + setAttributes( newValidatedAttributes ); + noticeOperations.removeAllNotices(); + } ) + .catch( () => { + setAttributes( { url: undefined } ); + setErrorNotice(); + } ); }; const embedCodeForm = ( @@ -115,6 +135,13 @@ function CalendlyEdit( props ) { ); + const blockEmbedding = ( +
+ +

{ __( 'Embedding…', 'jetpack' ) }

+
+ ); + const blockPlaceholder = ( ); + if ( isResolvingUrl ) { + return blockEmbedding; + } + const classes = `${ className } calendly-style-${ style }`; return ( diff --git a/extensions/blocks/calendly/index.js b/extensions/blocks/calendly/index.js index ae56a7f4b34a..243d598a9a0e 100644 --- a/extensions/blocks/calendly/index.js +++ b/extensions/blocks/calendly/index.js @@ -17,6 +17,8 @@ import { getAttributesFromEmbedCode, REGEX } from './utils'; */ import './editor.scss'; +export const CALENDLY_EXAMPLE_URL = 'https://calendly.com/wordpresscom/jetpack-block-example'; + export const name = 'calendly'; export const title = __( 'Calendly', 'jetpack' ); export const settings = { @@ -44,7 +46,7 @@ export const settings = { submitButtonText: __( 'Schedule time with me', 'jetpack' ), hideEventTypeDetails: false, style: 'inline', - url: 'https://calendly.com/wordpresscom/jetpack-block-example', + url: CALENDLY_EXAMPLE_URL, }, }, transforms: { diff --git a/extensions/shared/test-embed-url.js b/extensions/shared/test-embed-url.js new file mode 100644 index 000000000000..b594f84b7c7d --- /dev/null +++ b/extensions/shared/test-embed-url.js @@ -0,0 +1,41 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; + +/** + * WordPress dependencies + */ +import apiFetch from '@wordpress/api-fetch'; + +/** + * Test if a URL is accessible and respond with status code < 400. + * + * @example + * const [ isResolvingUrl, setIsResolvingUrl ] = useState(); + * testEmbedUrl( url, setIsResolvingUrl ) + * .then( () => setAttributes( url ) ) + * .catch( () => setErrorNotice() ); + * + * @param {String} url The URL to test. + * @param {Function} [setIsResolvingUrl=noop] An optional function to track the resolving state. Typically used to update the calling component's state. + * @returns {Promise} Resolve if the URL is valid, reject otherwise. + */ +export default function testEmbedUrl( url, setIsResolvingUrl = noop ) { + setIsResolvingUrl( true ); + return new Promise( async ( resolve, reject ) => { + try { + const response = await apiFetch( { path: `/wpcom/v2/resolve-redirect/${ url }` } ); + setIsResolvingUrl( false ); + const responseStatusCode = response.status ? parseInt( response.status, 10 ) : null; + if ( responseStatusCode && responseStatusCode >= 400 ) { + reject(); + } else { + resolve(); + } + } catch ( error ) { + setIsResolvingUrl( false ); + reject(); + } + } ); +}