/** * WordPress dependencies */ import apiFetch from '@safe-wordpress/api-fetch'; import { Button, CheckboxControl, Dashicon, Modal, TextControl, TextareaControl, } from '@safe-wordpress/components'; import { useState } from '@safe-wordpress/element'; import { _x, sprintf } from '@safe-wordpress/i18n'; import { isEmail } from '@safe-wordpress/url'; /** * External dependencies */ import { trim } from 'lodash'; import { RadioControl } from '@nab/components'; import { usePluginSetting } from '@nab/data'; import type { SiteId, Url } from '@nab/types'; /** * Internal dependencies */ import './style.scss'; export type DeactivationActionProps = { readonly isSubscribed: boolean; readonly isStagingSite: boolean; readonly cleanNonce: string; readonly deactivationUrl: string; }; type State = { readonly isModalOpen: boolean; readonly isDeactivating: boolean; readonly isLocked: boolean; readonly deleteStagingDataOnly: boolean; readonly reason: { readonly value: | 'temporary-deactivation' | 'clean-stuff' | 'plugin-no-longer-needed' | 'plugin-doesnt-work' | 'better-plugin-found' | 'other'; readonly details: string; }; }; type ContactFormState = { readonly isTicketSubmitting: boolean; readonly description: string; readonly email: string; readonly submissionError: boolean; }; export const DeactivationAction = ( props: DeactivationActionProps ): JSX.Element => { const state = useModalState( props ); return (
); }; // ============= // HELPERS VIEWS // ============= type DeactivationModalProps = { readonly state: ModalState; }; const DeactivationModal = ( { state, }: DeactivationModalProps ): JSX.Element | null => { if ( ! state.isModalOpen ) { return null; } let Body: typeof DefaultDeactivationBody; switch ( state.reason.value ) { case 'temporary-deactivation': Body = TemporaryDeactivationBody; break; case 'plugin-doesnt-work': Body = ContactFormBody; break; default: Body = DefaultDeactivationBody; } return ( ); }; const TemporaryDeactivationBody = ( { deactivate, isDeactivating, mainActionLabel, reason, setReason, }: ModalState ): JSX.Element => ( <> setReason( { value, details: '' } ) } disabled={ isDeactivating } />
); const ContactFormBody = ( { closeModal, lock, setReason, }: ModalState ): JSX.Element => { const [ state, setState ] = useState( INIT_CONTACT_FORM_STATE ); const { isTicketSubmitting, description, email, submissionError } = state; const isFormValid = trim( description ).length && isEmail( email ); const setAttributes = ( attrs: Partial< ContactFormState > ) => setState( { ...state, ...attrs } ); const siteId = usePluginSetting( 'siteId' ); const homeUrl = usePluginSetting( 'homeUrl' ); const isSubscribed = !! usePluginSetting( 'subscription' ); const apiUrl = usePluginSetting( 'apiUrl' ); const sendTicket = () => { lock(); setAttributes( { isTicketSubmitting: true } ); void apiFetch( { url: `${ apiUrl }/ticket`, credentials: 'omit', method: 'POST', mode: 'cors', data: { email, description: getDescription( description, siteId, homeUrl, isSubscribed ), subject: 'I can’t get the plugin to work and need help', }, } ) .then( closeModal ) .catch( () => { setAttributes( { submissionError: true, isTicketSubmitting: false, } ); lock( false ); } ); }; return ( <> setAttributes( { description: value } ) } /> setAttributes( { email: value } ) } /> { submissionError && (

{ sprintf( /* translators: %s: Email address. */ _x( 'Something went wrong and we couldn’t receive your support request. Please try again later or send us an email to %s.', 'user', 'nelio-ab-testing' ), 'support@neliosoftware.com' ) }

) }
); }; const DefaultDeactivationBody = ( { isLocked, isSubscribed, isStagingSite, mainActionLabel, cleanAndDeactivate, reason, setReason, deleteStagingDataOnly, setDeleteStagingDataOnly, }: ModalState ): JSX.Element => ( <>

{ _x( 'If you have a moment, please share why you are deactivating Nelio A/B Testing:', 'user', 'nelio-ab-testing' ) }

setReason( { value, details: '' } ) } extraValue={ reason.details } onExtraChange={ ( details ) => setReason( { ...reason, details } ) } disabled={ isLocked } /> { isSubscribed && isStagingSite && (

) } { isSubscribed && (

{ _x( 'Deactivating the plugin does not cancel your Nelio A/B Testing subscription. To unsubscribe, visit the plugin’s Account page first.', 'user', 'nelio-ab-testing' ) }

) }
{ isLocked ? ( ) : ( ) }
); // ===== // HOOKS // ===== type ModalState = ReturnType< typeof useModalState >; const useModalState = ( { cleanNonce, deactivationUrl, isSubscribed, isStagingSite, }: DeactivationActionProps ) => { const [ state, setState ] = useState( INIT_STATE ); const redirect = () => { window.location.href = deactivationUrl; }; const openModal = () => setState( { ...INIT_STATE, isModalOpen: true, } ); const closeModal = () => setState( { ...INIT_STATE, isModalOpen: false, } ); const deactivate = () => { setState( { ...state, reason: INIT_STATE.reason, isDeactivating: true, isLocked: true, } ); redirect(); }; const lock = ( isLocked = true ) => setState( { ...state, isLocked } ); const cleanAndDeactivate = ( deleteStagingDataOnly: boolean, reason?: string ): void => { setState( { ...state, isDeactivating: true } ); void apiFetch( { path: '/nab/v1/plugin/clean', method: 'POST', data: { deleteStagingDataOnly, reason, nabnonce: cleanNonce }, } ).then( redirect, // on success closeModal // on error ); }; const setReason = ( reason: State[ 'reason' ] ) => setState( { ...state, reason } ); const setDeleteStagingDataOnly = ( deleteStagingDataOnly: boolean ) => setState( { ...state, deleteStagingDataOnly } ); const mainActionLabel = 'temporary-deactivation' === state.reason.value ? getDeactivationLabel( state.isDeactivating ) : getCleanAndDeactivateLabel( state.isDeactivating ); return { isModalOpen: state.isModalOpen, openModal, closeModal, isDeactivating: state.isDeactivating, isSubscribed, isStagingSite, mainActionLabel, deactivate, cleanAndDeactivate, isLocked: state.isLocked, lock, reason: state.reason, setReason, deleteStagingDataOnly: state.deleteStagingDataOnly, setDeleteStagingDataOnly, }; }; const getDeactivationLabel = ( isDeactivating: boolean ) => isDeactivating ? _x( 'Deactivating…', 'text', 'nelio-ab-testing' ) : _x( 'Deactivate', 'command', 'nelio-ab-testing' ); const getCleanAndDeactivateLabel = ( isDeleting: boolean ) => isDeleting ? _x( 'Deleting Data…', 'text', 'nelio-ab-testing' ) : _x( 'Submit and Delete Data', 'command', 'nelio-ab-testing' ); // ==== // DATA // ==== const INIT_STATE: State = { isModalOpen: false, isDeactivating: false, isLocked: false, reason: { value: 'temporary-deactivation', details: '', }, deleteStagingDataOnly: true, }; const INIT_CONTACT_FORM_STATE: ContactFormState = { isTicketSubmitting: false, email: '', description: '', submissionError: false, }; const DEACTIVATION_MODES: ReadonlyArray< { readonly value: State[ 'reason' ][ 'value' ]; readonly label: string; } > = [ { value: 'temporary-deactivation', label: _x( 'It’s a temporary deactivation', 'text', 'nelio-ab-testing' ), }, { value: 'clean-stuff', label: _x( 'Delete Nelio A/B Testing’s data and deactivate plugin', 'text', 'nelio-ab-testing' ), }, ]; const DEACTIVATION_REASONS: ReadonlyArray< { readonly value: State[ 'reason' ][ 'value' ]; readonly label: string; readonly extra?: string; } > = [ { value: 'plugin-no-longer-needed', label: _x( 'I no longer need the plugin', 'text', 'nelio-ab-testing' ), }, { value: 'plugin-doesnt-work', label: _x( 'I couldn’t get the plugin to work', 'text', 'nelio-ab-testing' ), }, { value: 'better-plugin-found', label: _x( 'I found a better plugin', 'text', 'nelio-ab-testing' ), extra: _x( 'What’s the plugin’s name?', 'text', 'nelio-ab-testing' ), }, { value: 'other', label: _x( 'Other', 'text', 'nelio-ab-testing' ), extra: _x( 'Please share the reason…', 'user', 'nelio-ab-testing' ), }, ]; function getDescription( description: string, siteId: SiteId, homeUrl: Url, isSubscribed: boolean ): string { const div = document.createElement( 'div' ); const br = () => document.createElement( 'br' ); const extra = document.createElement( 'strong' ); extra.textContent = 'Additional Information'; const ul = document.createElement( 'ul' ); const li1 = document.createElement( 'li' ); li1.textContent = `Site ID: ${ siteId }`; const li2 = document.createElement( 'li' ); li2.textContent = `Site URL: ${ homeUrl }`; const li3 = document.createElement( 'li' ); li3.textContent = `Subscribed: ${ isSubscribed ? 'yes' : 'no' }`; ul.appendChild( li1 ); ul.appendChild( li2 ); ul.appendChild( li3 ); trim( description ) .split( /\n+/ ) .forEach( ( t ) => { const p = document.createTextNode( t ); div.appendChild( p ); div.appendChild( br() ); div.appendChild( br() ); } ); div.appendChild( extra ); div.appendChild( br() ); div.appendChild( ul ); return div.innerHTML; }