From f4ca1c223253d5624a35d96b6017d74224ed6dcb Mon Sep 17 00:00:00 2001 From: smb2268 Date: Thu, 2 May 2024 17:17:41 -0400 Subject: [PATCH] Add exit prevention screen --- .../QuickTransferFlow/ConfirmExitModal.tsx | 54 +++++++++++++++++++ .../QuickTransferFlow/SelectDestLabware.tsx | 11 ++-- .../QuickTransferFlow/SelectSourceLabware.tsx | 13 ++--- .../__tests__/ConfirmExitModal.test.tsx | 42 +++++++++++++++ app/src/organisms/QuickTransferFlow/index.tsx | 37 +++++++++---- app/src/organisms/QuickTransferFlow/utils.ts | 10 ++-- 6 files changed, 139 insertions(+), 28 deletions(-) create mode 100644 app/src/organisms/QuickTransferFlow/ConfirmExitModal.tsx create mode 100644 app/src/organisms/QuickTransferFlow/__tests__/ConfirmExitModal.test.tsx diff --git a/app/src/organisms/QuickTransferFlow/ConfirmExitModal.tsx b/app/src/organisms/QuickTransferFlow/ConfirmExitModal.tsx new file mode 100644 index 00000000000..817e9fe9a1d --- /dev/null +++ b/app/src/organisms/QuickTransferFlow/ConfirmExitModal.tsx @@ -0,0 +1,54 @@ +import * as React from 'react' +import { useTranslation } from 'react-i18next' +import { + SPACING, + COLORS, + StyledText, + Flex, + DIRECTION_COLUMN, + TYPOGRAPHY, +} from '@opentrons/components' +import { Modal } from '../../molecules/Modal' +import { SmallButton } from '../../atoms/buttons' + +interface ConfirmExitModalProps { + confirmExit: () => void + cancelExit: () => void +} + +export const ConfirmExitModal = (props: ConfirmExitModalProps): JSX.Element => { + const { i18n, t } = useTranslation(['quick_transfer', 'shared']) + + return ( + + + + {t('lose_all_progress')} + + + + + + + + ) +} diff --git a/app/src/organisms/QuickTransferFlow/SelectDestLabware.tsx b/app/src/organisms/QuickTransferFlow/SelectDestLabware.tsx index b996053845c..8ba80f2ce8a 100644 --- a/app/src/organisms/QuickTransferFlow/SelectDestLabware.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectDestLabware.tsx @@ -34,7 +34,6 @@ export function SelectDestLabware( ): JSX.Element | null { const { onNext, onBack, exitButtonProps, state, dispatch } = props const { i18n, t } = useTranslation(['quick_transfer', 'shared']) - if (state.pipette == null) return null const labwareDisplayCategoryFilters: LabwareFilter[] = [ 'all', 'wellPlate', @@ -46,15 +45,17 @@ export function SelectDestLabware( const [selectedCategory, setSelectedCategory] = React.useState( 'all' ) + const [selectedLabware, setSelectedLabware] = React.useState< + LabwareDefinition2 | 'source' | undefined + >(state.destination) + + if (state.pipette == null) return null + const compatibleLabwareDefinitions = getCompatibleLabwareByCategory( state.pipette.channels, selectedCategory ) - const [selectedLabware, setSelectedLabware] = React.useState< - LabwareDefinition2 | 'source' | undefined - >(state.destination) - const handleClickNext = (): void => { // the button will be disabled if this values is null if (selectedLabware != null) { diff --git a/app/src/organisms/QuickTransferFlow/SelectSourceLabware.tsx b/app/src/organisms/QuickTransferFlow/SelectSourceLabware.tsx index 95e623be5cd..5c2889b91f7 100644 --- a/app/src/organisms/QuickTransferFlow/SelectSourceLabware.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectSourceLabware.tsx @@ -34,28 +34,29 @@ export function SelectSourceLabware( ): JSX.Element | null { const { onNext, onBack, exitButtonProps, state, dispatch } = props const { i18n, t } = useTranslation(['quick_transfer', 'shared']) - if (state.pipette == null) return null const labwareDisplayCategoryFilters: LabwareFilter[] = [ 'all', 'wellPlate', 'reservoir', ] - if (state.pipette.channels === 1) { + if (state.pipette?.channels === 1) { labwareDisplayCategoryFilters.push('tubeRack') } const [selectedCategory, setSelectedCategory] = React.useState( 'all' ) + const [selectedLabware, setSelectedLabware] = React.useState< + LabwareDefinition2 | undefined + >(state.source) + + if (state.pipette == null) return null + const compatibleLabwareDefinitions = getCompatibleLabwareByCategory( state.pipette.channels, selectedCategory ) - const [selectedLabware, setSelectedLabware] = React.useState< - LabwareDefinition2 | undefined - >(state.source) - const handleClickNext = (): void => { // the button will be disabled if this values is null if (selectedLabware != null) { diff --git a/app/src/organisms/QuickTransferFlow/__tests__/ConfirmExitModal.test.tsx b/app/src/organisms/QuickTransferFlow/__tests__/ConfirmExitModal.test.tsx new file mode 100644 index 00000000000..f1e3681a93a --- /dev/null +++ b/app/src/organisms/QuickTransferFlow/__tests__/ConfirmExitModal.test.tsx @@ -0,0 +1,42 @@ +import * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' + +import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '../../../i18n' +import { ConfirmExitModal } from '../ConfirmExitModal' + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} + +describe('ConfirmExitModal', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + confirmExit: vi.fn(), + cancelExit: vi.fn(), + } + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('renders the create new transfer screen and header', () => { + render(props) + screen.getByText('Exit quick transfer?') + screen.getByText('You will lose all progress on this quick transfer.') + }) + it('renders exit and cancel buttons and they work as expected', () => { + render(props) + const cancelBtn = screen.getByText('Cancel') + fireEvent.click(cancelBtn) + expect(props.cancelExit).toHaveBeenCalled() + const deleteBtn = screen.getByText('Delete') + fireEvent.click(deleteBtn) + expect(props.confirmExit).toHaveBeenCalled() + }) +}) diff --git a/app/src/organisms/QuickTransferFlow/index.tsx b/app/src/organisms/QuickTransferFlow/index.tsx index 804acf36a95..a6ddcf55840 100644 --- a/app/src/organisms/QuickTransferFlow/index.tsx +++ b/app/src/organisms/QuickTransferFlow/index.tsx @@ -1,8 +1,13 @@ import * as React from 'react' import { useHistory } from 'react-router-dom' import { useTranslation } from 'react-i18next' -import { StepMeter, POSITION_STICKY } from '@opentrons/components' +import { + useConditionalConfirm, + StepMeter, + POSITION_STICKY, +} from '@opentrons/components' import { SmallButton } from '../../atoms/buttons' +import { ConfirmExitModal } from './ConfirmExitModal' import { CreateNewTransfer } from './CreateNewTransfer' import { SelectPipette } from './SelectPipette' import { SelectTipRack } from './SelectTipRack' @@ -27,12 +32,16 @@ export const QuickTransferFlow = (): JSX.Element => { ) const [currentStep, setCurrentStep] = React.useState(1) + const { + confirm: confirmExit, + showConfirmation: showConfirmExit, + cancel: cancelExit, + } = useConditionalConfirm(() => history.push('protocols'), true) + const exitButtonProps: React.ComponentProps = { buttonType: 'tertiaryLowLight', buttonText: i18n.format(t('shared:exit'), 'capitalize'), - onClick: () => { - history.push('protocols') - }, + onClick: confirmExit, } React.useEffect(() => { @@ -128,13 +137,19 @@ export const QuickTransferFlow = (): JSX.Element => { return ( <> - - {modalContent} + {showConfirmExit ? ( + + ) : ( + <> + + {modalContent} + + )} ) } diff --git a/app/src/organisms/QuickTransferFlow/utils.ts b/app/src/organisms/QuickTransferFlow/utils.ts index 907c08a52a6..41de2d86269 100644 --- a/app/src/organisms/QuickTransferFlow/utils.ts +++ b/app/src/organisms/QuickTransferFlow/utils.ts @@ -158,12 +158,10 @@ export function getVolumeLimits( const destLabwareVolume = Math.min( ...state.destinationWells.map(well => { - { - if (state.source == null || state.destination == null) return 0 - return state.destination === 'source' - ? state.source.wells[well].totalLiquidVolume - : state.destination.wells[well].totalLiquidVolume - } + if (state.source == null || state.destination == null) return 0 + return state.destination === 'source' + ? state.source.wells[well].totalLiquidVolume + : state.destination.wells[well].totalLiquidVolume }) ) let maxVolume = maxPipetteVolume