/** * External dependencies */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { Button, ExternalLink } from '@wordpress/components'; import { compose } from '@wordpress/compose'; import { recordEvent } from 'lib/tracks'; import interpolateComponents from 'interpolate-components'; import PropTypes from 'prop-types'; import { get, isArray } from 'lodash'; /** * Internal dependencies */ import '../style.scss'; import DismissModal from '../dismiss-modal'; import { getSetting } from '@woocommerce/wc-admin-settings'; import withSelect from 'wc-api/with-select'; import SetupNotice, { setupErrorTypes } from '../setup-notice'; import { withDispatch } from '@wordpress/data'; import { getWcsAssets, acceptWcsTos } from '../wcs-api'; const wcAdminAssetUrl = getSetting( 'wcAdminAssetUrl', '' ); export class ShippingBanner extends Component { constructor( props ) { super( props ); const orderId = new URL( window.location.href ).searchParams.get( 'post' ); this.state = { showShippingBanner: true, isDismissModalOpen: false, setupErrorReason: setupErrorTypes.SETUP, orderId: parseInt( orderId, 10 ), wcsAssetsLoaded: false, wcsAssetsLoading: false, wcsSetupError: false, isShippingLabelButtonBusy: false, installText: this.getInstallText(), isWcsModalOpen: false, }; } componentDidMount() { const { showShippingBanner } = this.state; if ( showShippingBanner ) { this.trackImpression(); } } componentDidUpdate( prevProps ) { const { activatePlugins, wcsPluginSlug } = this.props; if ( this.justInstalledWcs( prevProps ) ) { activatePlugins( [ wcsPluginSlug ] ); } if ( this.justActivatedWcs( prevProps ) ) { this.acceptTosAndGetWCSAssets(); } } justInstalledWcs( prevProps ) { const { installedPlugins, wcsPluginSlug } = this.props; const wcsNowInstalled = installedPlugins.includes( wcsPluginSlug ); const wcsPrevInstalled = prevProps.installedPlugins.includes( wcsPluginSlug ); return wcsNowInstalled && ! wcsPrevInstalled; } justActivatedWcs( prevProps ) { const { activatedPlugins, wcsPluginSlug } = this.props; const wcsNowActivated = activatedPlugins.includes( wcsPluginSlug ); const wcsPrevActivated = prevProps.activatedPlugins.includes( wcsPluginSlug ); return wcsNowActivated && ! wcsPrevActivated; } hasActivationError = () => { return Boolean( this.props.activationErrors.length ); }; hasInstallationError = () => { return Boolean( this.props.installationErrors.length ); }; isSetupError = () => { return ( this.hasActivationError() || this.hasInstallationError() || this.state.wcsSetupError ); }; setupErrorReason = () => { if ( this.hasInstallationError() ) { return setupErrorTypes.INSTALL; } if ( this.hasActivationError() ) { return setupErrorTypes.ACTIVATE; } return setupErrorTypes.SETUP; }; closeDismissModal = () => { this.setState( { isDismissModalOpen: false } ); this.trackElementClicked( 'shipping_banner_dismiss_modal_close_button' ); }; openDismissModal = () => { this.setState( { isDismissModalOpen: true } ); this.trackElementClicked( 'shipping_banner_dimiss' ); }; hideBanner = () => { this.setState( { showShippingBanner: false } ); }; createShippingLabelClicked = () => { const { wcsPluginSlug, activePlugins } = this.props; this.setState( { isShippingLabelButtonBusy: true } ); this.trackElementClicked( 'shipping_banner_create_label' ); if ( ! activePlugins.includes( wcsPluginSlug ) ) { this.installAndActivatePlugins( wcsPluginSlug ); } else { this.acceptTosAndGetWCSAssets(); } }; installAndActivatePlugins( pluginSlug ) { // Avoid double activating. const { installPlugins, isRequesting } = this.props; if ( isRequesting ) { return false; } installPlugins( [ pluginSlug ] ); } woocommerceServiceLinkClicked = () => { this.trackElementClicked( 'shipping_banner_woocommerce_service_link' ); }; trackBannerEvent = ( eventName, customProps = {} ) => { const { activePlugins, isJetpackConnected, wcsPluginSlug } = this.props; recordEvent( eventName, { banner_name: 'wcadmin_install_wcs_prompt', jetpack_installed: activePlugins.includes( 'jetpack' ), jetpack_connected: isJetpackConnected, wcs_installed: activePlugins.includes( wcsPluginSlug ), ...customProps, } ); }; trackImpression = () => { this.trackBannerEvent( 'banner_impression' ); }; trackElementClicked = ( element ) => { this.trackBannerEvent( 'banner_element_clicked', { element, } ); }; acceptTosAndGetWCSAssets() { return acceptWcsTos() .then( () => getWcsAssets() ) .then( ( wcsAssets ) => this.loadWcsAssets( wcsAssets ) ) .catch( () => this.setState( { wcsSetupError: true } ) ); } generateMetaBoxHtml( nodeId, title, args ) { const argsJsonString = JSON.stringify( args ).replace( /"/g, '"' ); // JS has no native html_entities so we just replace. const togglePanelText = __( 'Toggle panel:', 'woocommerce-admin' ); return `

${ title }

`; } loadWcsAssets( { assets } ) { if ( this.state.wcsAssetsLoaded || this.state.wcsAssetsLoading ) { this.openWcsModal(); return; } this.setState( { wcsAssetsLoading: true } ); const js = assets.wc_connect_admin_script; const styles = assets.wc_connect_admin_style; const { orderId } = this.state; const { itemsCount } = this.props; const shippingLabelContainerHtml = this.generateMetaBoxHtml( 'woocommerce-order-label', __( 'Shipping Label', 'woocommerce-admin' ), { orderId, context: 'shipping_label', items: itemsCount, } ); // Insert shipping label metabox just above main order details box. document .getElementById( 'woocommerce-order-data' ) .insertAdjacentHTML( 'beforebegin', shippingLabelContainerHtml ); const shipmentTrackingHtml = this.generateMetaBoxHtml( 'woocommerce-order-shipment-tracking', __( 'Shipment Tracking', 'woocommerce-admin' ), { orderId, context: 'shipment_tracking', items: itemsCount, } ); // Insert tracking metabox in the side after the order actions. document .getElementById( 'woocommerce-order-actions' ) .insertAdjacentHTML( 'afterend', shipmentTrackingHtml ); if ( window.jQuery ) { // Need to refresh so the new metaboxes are sortable. window.jQuery( '#normal-sortables' ).sortable( 'refresh' ); window.jQuery( '#side-sortables' ).sortable( 'refresh' ); window.jQuery( '#woocommerce-order-label' ).hide(); } Promise.all( [ new Promise( ( resolve, reject ) => { const script = document.createElement( 'script' ); script.src = js; script.async = true; script.onload = resolve; script.onerror = reject; document.body.appendChild( script ); } ), new Promise( ( resolve, reject ) => { const head = document.getElementsByTagName( 'head' )[ 0 ]; const link = document.createElement( 'link' ); link.rel = 'stylesheet'; link.type = 'text/css'; link.href = styles; link.media = 'all'; link.onload = resolve; link.onerror = reject; head.appendChild( link ); } ), ] ).then( () => { this.setState( { wcsAssetsLoaded: true, wcsAssetsLoading: false, isShippingLabelButtonBusy: false, } ); this.openWcsModal(); } ); } getInstallText = () => { const { activePlugins, wcsPluginSlug } = this.props; if ( activePlugins.includes( wcsPluginSlug ) ) { // If WCS is active, then the only remaining step is to agree to the ToS. return __( 'You\'ve already installed WooCommerce Shipping. By clicking "Create shipping label", you agree to its {{tosLink}}Terms of Service{{/tosLink}}.', 'woocommerce-admin' ); } return __( 'By clicking "Create shipping label", {{wcsLink}}WooCommerce Shipping{{/wcsLink}} will be installed and you agree to its {{tosLink}}Terms of Service{{/tosLink}}.', 'woocommerce-admin' ); }; openWcsModal() { if ( window.wcsGetAppStore ) { const wcsStore = window.wcsGetAppStore( 'wc-connect-create-shipping-label' ); const state = wcsStore.getState(); const { orderId } = this.state; const siteId = state.ui.selectedSiteId; const wcsStoreUnsubscribe = wcsStore.subscribe( () => { const latestState = wcsStore.getState(); const shippingLabelState = get( latestState, [ 'extensions', 'woocommerce', 'woocommerceServices', siteId, 'shippingLabel', orderId, ], null ); const labelSettingsState = get( latestState, [ 'extensions', 'woocommerce', 'woocommerceServices', siteId, 'labelSettings', ], null ); const packageState = get( latestState, [ 'extensions', 'woocommerce', 'woocommerceServices', siteId, 'packages', ], null ); const locationsState = get( latestState, [ 'extensions', 'woocommerce', 'sites', siteId, 'data', 'locations', ] ); if ( shippingLabelState && labelSettingsState && labelSettingsState.meta && packageState && locationsState ) { if ( shippingLabelState.loaded && labelSettingsState.meta.isLoaded && packageState.isLoaded && isArray( locationsState ) && ! this.state.isWcsModalOpen ) { if ( window.jQuery ) { this.setState( { isWcsModalOpen: true } ); window .jQuery( '.shipping-label__new-label-button' ) .click(); } wcsStore.dispatch( { type: 'NOTICE_CREATE', notice: { duration: 10000, status: 'is-success', text: __( 'Plugin installed and activated', 'woocommerce-admin' ), }, } ); } else if ( shippingLabelState.showPurchaseDialog ) { wcsStoreUnsubscribe(); if ( window.jQuery ) { window.jQuery( '#woocommerce-order-label' ).show(); } } } } ); document.getElementById( 'woocommerce-admin-print-label' ).style.display = 'none'; } } render() { const { isDismissModalOpen, showShippingBanner, isShippingLabelButtonBusy, } = this.state; if ( ! showShippingBanner ) { return null; } return (
{

{ __( 'Print discounted shipping labels with a click.', 'woocommerce-admin' ) }

{ interpolateComponents( { mixedString: this.state.installText, components: { tosLink: ( ), wcsLink: ( ), }, } ) }

); } } ShippingBanner.propTypes = { itemsCount: PropTypes.number.isRequired, isJetpackConnected: PropTypes.bool.isRequired, activatedPlugins: PropTypes.array.isRequired, activePlugins: PropTypes.array.isRequired, installedPlugins: PropTypes.array.isRequired, wcsPluginSlug: PropTypes.string.isRequired, activationErrors: PropTypes.array.isRequired, installationErrors: PropTypes.array.isRequired, activatePlugins: PropTypes.func.isRequired, installPlugins: PropTypes.func.isRequired, isRequesting: PropTypes.bool.isRequired, }; export default compose( withSelect( ( select ) => { const wcsPluginSlug = 'woocommerce-services'; const { getActivePlugins, getPluginInstallations, getPluginActivations, getPluginActivationErrors, getPluginInstallationErrors, isJetpackConnected, isPluginActivateRequesting, isPluginInstallRequesting, } = select( 'wc-api' ); const isRequesting = isPluginActivateRequesting() || isPluginInstallRequesting(); const allInstallationErrors = getPluginInstallationErrors( [ wcsPluginSlug, ] ); const installedPlugins = Object.keys( getPluginInstallations( [ wcsPluginSlug ] ) ); const allActivationErrors = getPluginActivationErrors( [ wcsPluginSlug, ] ); const activatedPlugins = Object.keys( getPluginActivations( [ wcsPluginSlug ] ) ); const activationErrors = []; const installationErrors = []; Object.keys( allActivationErrors ).map( ( plugin ) => activationErrors.push( allActivationErrors[ plugin ].message ) ); Object.keys( allInstallationErrors ).map( ( plugin ) => installationErrors.push( allInstallationErrors[ plugin ].message ) ); return { activePlugins: getActivePlugins(), isJetpackConnected: isJetpackConnected(), isRequesting, installedPlugins, activatedPlugins, wcsPluginSlug, activationErrors, installationErrors, }; } ), withDispatch( ( dispatch ) => { const { activatePlugins, installPlugins } = dispatch( 'wc-api' ); return { activatePlugins, installPlugins, }; } ) )( ShippingBanner );