/** * WordPress dependencies */ import { Button, CheckboxControl } from '@safe-wordpress/components'; import { useDispatch, useSelect } from '@safe-wordpress/data'; import { createInterpolateElement, useEffect, useState, } from '@safe-wordpress/element'; import { _x } from '@safe-wordpress/i18n'; /** * External dependencies */ import clsx from 'clsx'; import { HelpIcon } from '@nab/components'; import { store as NAB_EXPERIMENTS } from '@nab/experiments'; import { createAlternative, duplicateAlternative, getLetter, omitUndefineds, } from '@nab/utils'; import type { Alternative, AlternativeCustomAction, AlternativeId, Dict, ExperimentEditProps, Maybe, } from '@nab/types'; /** * Internal dependencies */ import { useCanAddAlternatives, useExperimentAttribute } from '../hooks'; import { store as NAB_EDITOR } from '../../store'; export type AlternativeContainerProps = { readonly alternativeId: AlternativeId; readonly disabled?: boolean; readonly index: number; }; export const AlternativeContainer = ( { alternativeId, disabled, index, }: AlternativeContainerProps ): JSX.Element | null => { const control = useAlternative( 'control' ); const alternative = useAlternative( alternativeId ); const AlternativeView = useView( index ); const canBeRemoved = useCanBeRemoved(); const experimentType = useExperimentType(); const help = useHelp( index ); const isBeingSaved = useIsBeingSaved(); const isExternalEdit = useIsExternalEdit(); const isControl = ! index; const canAddAlternatives = useCanAddAlternatives(); const duplicate = useDuplicator( alternative ); const isPreviewDisabled = useIsPreviewDisabled( alternative, control ); const customActions = useCustomActions( alternative, control ); const { saveExperimentAndEditAlternative, saveExperimentAndPreviewAlternative, removeAlternatives, replaceAlternatives, setAlternative, showAiAlternativeModal, } = useDispatch( NAB_EDITOR ); if ( ! alternative || ! control ) { return null; } const setAttributes = ( attributes: Dict ) => setAlternative( alternative.id, { ...alternative, attributes: omitUndefineds( { ...alternative.attributes, ...attributes, } ), } ); const resetAlternative = () => replaceAlternatives( alternativeId, { ...createAlternative(), attributes: { name: alternative.ai?.name || '', chance: alternative.attributes.chance, }, ai: alternative.ai ? { ...alternative.ai, isReady: false } : undefined, } ); return (

{ getLetter( index ) }

{ !! alternative.ai && (
setAlternative( alternative.id, { ...alternative, ai: alternative.ai ? { ...alternative.ai, isReady } : undefined, } ) } label={ createInterpolateElement( _x( 'I’ve already applied all Nelio AI’s recommended changes to this variant', 'text', 'nelio-ab-testing' ), { a: (
) } { 0 !== index && (
    { customActions.map( ( action ) => ! action.isVisible || action.isVisible( alternative.attributes, control.attributes ) ? (
  • ) : null ) } { isExternalEdit && (
  • ) }
  • { isExternalEdit && ! alternative.base && canAddAlternatives && (
  • ) } { isExternalEdit && (
  • ) } { canBeRemoved && (
  • ) }
) }
{ !! help && }
); }; // ===== // HOOKS // ===== const useExperimentType = () => useSelect( ( select ) => select( NAB_EDITOR ).getExperimentType(), [] ); const useDefaults = () => { const type = useExperimentType(); return useSelect( ( select ) => select( NAB_EXPERIMENTS ).getExperimentType( type )?.defaults, [ type ] ); }; const useDuplicator = ( alternative: Maybe< Alternative > ) => { const alternatives = useSelect( ( select ) => select( NAB_EDITOR ).getAlternatives(), [] ); const { replaceAlternatives } = useDispatch( NAB_EDITOR ); return alternative ? () => void replaceAlternatives( alternatives.map( ( a ) => a.id ), [ ...alternatives, duplicateAlternative( alternative ) ] ) : () => void null; }; const useAlternative = ( alternativeId: AlternativeId ) => { const [ didApplyDefaults, markApplyDefaults ] = useState( false ); const defaults = useDefaults(); const alternative = useSelect( ( select ) => select( NAB_EDITOR ).getAlternative( alternativeId ), [ alternativeId ] ); const { setAlternative } = useDispatch( NAB_EDITOR ); useEffect( () => { if ( didApplyDefaults ) { return; } if ( ! defaults || ! alternative ) { return; } const defaultAttributes = 'control' === alternative.id ? defaults.original : defaults.alternative; void setAlternative( alternative.id, { ...alternative, attributes: { ...defaultAttributes, ...alternative.attributes, }, } ); markApplyDefaults( true ); }, [ defaults, alternative, didApplyDefaults, setAlternative ] ); return didApplyDefaults ? alternative : undefined; }; const useCanBeRemoved = () => { const [ status ] = useExperimentAttribute( 'status' ); return useSelect( ( select ) => { const { getAlternativeIds } = select( NAB_EDITOR ); const isPaused = ( status || '' ).includes( 'paused' ); return getAlternativeIds().length > 2 && ! isPaused; }, [ status ] ); }; const useIsBeingSaved = () => useSelect( ( select ) => select( NAB_EDITOR ).isExperimentBeingSaved(), [] ); type View = ( props: ExperimentEditProps< Dict > ) => JSX.Element; const EMPTY_VIEW: View = () => <>; const useView = ( index: number ): View => useSelect( ( select ) => { const { getExperimentView } = select( NAB_EXPERIMENTS ); const { getExperimentType } = select( NAB_EDITOR ); const view = ! index ? getExperimentView( getExperimentType(), 'original' ) : getExperimentView( getExperimentType(), 'alternative' ); return view ?? EMPTY_VIEW; }, [ index ] ); const useHelp = ( index: number ) => useSelect( ( select ) => { const { getExperimentType } = select( NAB_EDITOR ); const { getHelpString } = select( NAB_EXPERIMENTS ); if ( 0 === index ) { return getHelpString( getExperimentType(), 'original' ); } if ( 1 === index ) { return getHelpString( getExperimentType(), 'alternative' ); } return ''; }, [ index ] ); const useIsExternalEdit = () => { const type = useExperimentType(); const control = useSelect( ( select ) => select( NAB_EDITOR ).getAlternative( 'control' ), [] ); return useSelect( ( select ) => { const { getExperimentSupport } = select( NAB_EXPERIMENTS ); const alternativeEdition = getExperimentSupport( type, 'alternativeEdition' ); if ( 'external' !== alternativeEdition?.type ) { return false; } if ( 'function' !== typeof alternativeEdition.enabled ) { return true; } return ( !! control && alternativeEdition.enabled( control.attributes ) ); }, [ type, control ] ); }; const useIsPreviewDisabled = ( alternative: Maybe< Alternative >, control: Maybe< Alternative > ) => useSelect( ( select ): boolean => { const { getExperimentType } = select( NAB_EXPERIMENTS ); const type = getExperimentType( select( NAB_EDITOR ).getExperimentType() ); if ( ! type || ! alternative || ! control ) { return false; } return !! type.checks.isAlternativePreviewDisabled?.( alternative.attributes, control.attributes, select ); }, [ control, alternative ] ); const useCustomActions = ( alternative: Maybe< Alternative >, control: Maybe< Alternative > ) => useSelect( ( select ): ReadonlyArray< AlternativeCustomAction > => { const { getExperimentType } = select( NAB_EXPERIMENTS ); const type = getExperimentType( select( NAB_EDITOR ).getExperimentType() ); if ( ! type || ! alternative || ! control ) { return []; } return type.supports.alternativeCustomActions ?? []; }, [ control, alternative ] );