From 490a4dd43859e9f50a5e881b7e70a050ed5399e7 Mon Sep 17 00:00:00 2001 From: Kar Rui Date: Tue, 23 Aug 2022 12:21:17 +0800 Subject: [PATCH] feat: add pending state for clicking builder tabs --- .../components/DirtyModal/DirtyModal.tsx | 9 ++- .../BuilderAndDesignContent.tsx | 4 +- .../CreatePageDrawerCloseButton.tsx | 6 +- .../DesignDrawer/DesignDrawer.tsx | 4 +- .../EditEndPageDrawer/EditEndPage.tsx | 2 +- .../common/FormFieldDrawerActions.tsx | 2 +- .../edit-fieldtype/common/useEditFieldForm.ts | 6 +- .../common/BuilderDrawerContainer.tsx | 10 ++- .../useFieldBuilderStore.tsx | 18 +++-- .../CreatePageSidebar/CreatePageSidebar.tsx | 20 ++++-- .../CreatePageSidebarContext.tsx | 70 ++++++++++++------- 11 files changed, 104 insertions(+), 47 deletions(-) diff --git a/frontend/src/features/admin-form/common/components/DirtyModal/DirtyModal.tsx b/frontend/src/features/admin-form/common/components/DirtyModal/DirtyModal.tsx index 640f4c348e..4fa9747567 100644 --- a/frontend/src/features/admin-form/common/components/DirtyModal/DirtyModal.tsx +++ b/frontend/src/features/admin-form/common/components/DirtyModal/DirtyModal.tsx @@ -24,6 +24,7 @@ export const useDirtyModal = () => { handleDesignClick, pendingTab, movePendingToActiveTab, + clearPendingTab, } = useCreatePageSidebar() const { designHoldingState, designMoveFromHolding, designClearHoldingState } = @@ -59,7 +60,7 @@ export const useDirtyModal = () => { } else if (designHoldingState !== null) { designMoveFromHolding() handleDesignClick() - } else if (pendingTab !== null) { + } else if (pendingTab !== undefined) { movePendingToActiveTab() } }, [ @@ -78,19 +79,23 @@ export const useDirtyModal = () => { builderClearHoldingStateData() } else if (designHoldingState !== null) { designClearHoldingState() + } else if (pendingTab !== undefined) { + clearPendingTab() } }, [ builderClearHoldingStateData, builderHoldingStateData, + clearPendingTab, designClearHoldingState, designHoldingState, + pendingTab, ]) const isOpen = useMemo( () => builderHoldingStateData !== null || designHoldingState !== null || - pendingTab !== null, + pendingTab !== undefined, [builderHoldingStateData, designHoldingState, pendingTab], ) diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/BuilderAndDesignContent.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/BuilderAndDesignContent.tsx index 15e8f28d01..9e67927b46 100644 --- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/BuilderAndDesignContent.tsx +++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/BuilderAndDesignContent.tsx @@ -36,7 +36,9 @@ export const BuilderAndDesignContent = ({ ), ) - useEffect(() => setFieldsToInactive, [setFieldsToInactive]) + useEffect(() => { + setFieldsToInactive() + }, [setFieldsToInactive]) return ( diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/CreatePageDrawerCloseButton.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/CreatePageDrawerCloseButton.tsx index e93f1b4995..a35a261a0c 100644 --- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/CreatePageDrawerCloseButton.tsx +++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/CreatePageDrawerCloseButton.tsx @@ -3,13 +3,15 @@ import { BiX } from 'react-icons/bi' import { CloseButton } from '@chakra-ui/react' import { useCreatePageSidebar } from '../../common/CreatePageSidebarContext' +import { isDirtySelector, useFieldBuilderStore } from '../useFieldBuilderStore' export const CreatePageDrawerCloseButton = (): JSX.Element => { + const isDirty = useFieldBuilderStore(isDirtySelector) const { handleClose } = useCreatePageSidebar() const handleCloseDrawer = useCallback(() => { - handleClose() - }, [handleClose]) + handleClose(isDirty) + }, [handleClose, isDirty]) return ( { estTimeTaken: estTimeTakenTransformed, ...rest, }, - { onSuccess: handleClose }, + { onSuccess: () => handleClose() }, ) } else { const customLogoMeta = await handleUploadLogo(attachment) @@ -193,7 +193,7 @@ export const DesignInput = (): JSX.Element | null => { estTimeTaken: estTimeTakenTransformed, ...rest, }, - { onSuccess: handleClose }, + { onSuccess: () => handleClose() }, ) } }, diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditEndPageDrawer/EditEndPage.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditEndPageDrawer/EditEndPage.tsx index c784ee8561..3817201d4c 100644 --- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditEndPageDrawer/EditEndPage.tsx +++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditEndPageDrawer/EditEndPage.tsx @@ -176,7 +176,7 @@ export const EndPageBuilderInput = ({ variant="clear" colorScheme="secondary" isDisabled={endPageMutation.isLoading} - onClick={closeBuilderDrawer} + onClick={() => closeBuilderDrawer()} > Cancel diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/common/FormFieldDrawerActions.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/common/FormFieldDrawerActions.tsx index 0592eddedb..28c346f102 100644 --- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/common/FormFieldDrawerActions.tsx +++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/common/FormFieldDrawerActions.tsx @@ -39,7 +39,7 @@ export const FormFieldDrawerActions = ({ isFullWidth={isMobile} variant="clear" colorScheme="secondary" - onClick={handleCancel} + onClick={() => handleCancel()} > Cancel diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/common/useEditFieldForm.ts b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/common/useEditFieldForm.ts index 75425a79e7..d144d9ba2d 100644 --- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/common/useEditFieldForm.ts +++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/common/useEditFieldForm.ts @@ -167,6 +167,10 @@ export const useEditFieldForm = ({ [stateData, updateCreateState, updateEditState], ) + const handleCancel = useCallback(() => { + setToInactive() + }, [setToInactive]) + useDebounce( () => handleChange(transform.output(clonedWatchedInputs, field)), 300, @@ -183,7 +187,7 @@ export const useEditFieldForm = ({ formMethods: editForm, buttonText, handleUpdateField, - handleCancel: setToInactive, + handleCancel, isLoading: createFieldMutation.isLoading || editFieldMutation.isLoading, } } diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/common/BuilderDrawerContainer.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/common/BuilderDrawerContainer.tsx index 77adb0c737..1b267997be 100644 --- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/common/BuilderDrawerContainer.tsx +++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/common/BuilderDrawerContainer.tsx @@ -1,10 +1,11 @@ -import { ReactNode } from 'react' +import { ReactNode, useCallback } from 'react' import { BiLeftArrowAlt } from 'react-icons/bi' import { Stack, Text } from '@chakra-ui/react' import IconButton from '~components/IconButton' import { + isDirtySelector, setToInactiveSelector, useFieldBuilderStore, } from '../../useFieldBuilderStore' @@ -19,8 +20,13 @@ export const BuilderDrawerContainer = ({ title, children, }: BuilderDrawerContainerProps): JSX.Element | null => { + const isDirty = useFieldBuilderStore(isDirtySelector) const setToInactive = useFieldBuilderStore(setToInactiveSelector) + const handleBack = useCallback(() => { + setToInactive(isDirty) + }, [isDirty, setToInactive]) + return ( <> } /> void updateEditState: (field: FormFieldDto, holding?: boolean) => void setEditEndPage: (holding?: boolean) => void - setToInactive: () => void + setToInactive: (holding?: boolean) => void isDirty: boolean setIsDirty: (isDirty: boolean) => void stateData: @@ -39,7 +39,10 @@ export type FieldBuilderStore = { | { state: FieldBuilderState.Inactive } // Used when there is a dirty state and we want to hold the next state to be set. // Will be used to set stateData if user confirms discarding changes. - holdingStateData: FieldBuilderCreateEditStateData | null + holdingStateData: + | FieldBuilderCreateEditStateData + | { state: FieldBuilderState.Inactive } + | null clearHoldingStateData: () => void moveFromHolding: () => void } @@ -109,8 +112,15 @@ export const useFieldBuilderStore = create( set({ stateData: nextState }) } }, - setToInactive: () => { - set({ stateData: { state: FieldBuilderState.Inactive } }) + setToInactive: (holding?: boolean) => { + const nextState: FieldBuilderStore['holdingStateData'] = { + state: FieldBuilderState.Inactive, + } + if (holding) { + set({ holdingStateData: nextState }) + } else { + set({ stateData: nextState }) + } }, })), ) diff --git a/frontend/src/features/admin-form/create/common/CreatePageSidebar/CreatePageSidebar.tsx b/frontend/src/features/admin-form/create/common/CreatePageSidebar/CreatePageSidebar.tsx index 92cb2608e7..2bd383ba17 100644 --- a/frontend/src/features/admin-form/create/common/CreatePageSidebar/CreatePageSidebar.tsx +++ b/frontend/src/features/admin-form/create/common/CreatePageSidebar/CreatePageSidebar.tsx @@ -12,6 +12,7 @@ import { } from '~features/admin-form/create/common/CreatePageSidebarContext/CreatePageSidebarContext' import { + isDirtySelector, setToInactiveSelector, useFieldBuilderStore, } from '../../builder-and-design/useFieldBuilderStore' @@ -22,6 +23,7 @@ import { DrawerTabIcon } from './DrawerTabIcon' export const CreatePageSidebar = (): JSX.Element | null => { const isMobile = useIsMobile() const setFieldsToInactive = useFieldBuilderStore(setToInactiveSelector) + const isDirty = useFieldBuilderStore(isDirtySelector) const { activeTab, handleBuilderClick, handleDesignClick, handleLogicClick } = useCreatePageSidebar() @@ -30,8 +32,18 @@ export const CreatePageSidebar = (): JSX.Element | null => { if (isMobile) { setFieldsToInactive() } - handleBuilderClick() - }, [handleBuilderClick, isMobile, setFieldsToInactive]) + handleBuilderClick(isDirty) + }, [handleBuilderClick, isDirty, isMobile, setFieldsToInactive]) + + const handleDrawerDesignClick = useCallback( + () => handleDesignClick(isDirty), + [handleDesignClick, isDirty], + ) + + const handleDrawerLogicClick = useCallback( + () => handleLogicClick(isDirty), + [handleLogicClick, isDirty], + ) return ( { } - onClick={handleDesignClick} + onClick={handleDrawerDesignClick} isActive={activeTab === DrawerTabs.Design} id={FEATURE_TOUR[1].id} /> } - onClick={handleLogicClick} + onClick={handleDrawerLogicClick} isActive={activeTab === DrawerTabs.Logic} id={FEATURE_TOUR[2].id} /> diff --git a/frontend/src/features/admin-form/create/common/CreatePageSidebarContext/CreatePageSidebarContext.tsx b/frontend/src/features/admin-form/create/common/CreatePageSidebarContext/CreatePageSidebarContext.tsx index bc7da81924..a88b2033a4 100644 --- a/frontend/src/features/admin-form/create/common/CreatePageSidebarContext/CreatePageSidebarContext.tsx +++ b/frontend/src/features/admin-form/create/common/CreatePageSidebarContext/CreatePageSidebarContext.tsx @@ -17,7 +17,6 @@ import { useDesignStore, } from '../../builder-and-design/useDesignStore' import { - isDirtySelector, setToInactiveSelector, useFieldBuilderStore, } from '../../builder-and-design/useFieldBuilderStore' @@ -30,13 +29,14 @@ export enum DrawerTabs { type CreatePageSidebarContextProps = { activeTab: DrawerTabs | null - pendingTab: DrawerTabs | null + pendingTab?: DrawerTabs | null movePendingToActiveTab: () => void - handleBuilderClick: () => void - handleDesignClick: () => void - handleLogicClick: () => void + clearPendingTab: () => void + handleBuilderClick: (shouldBePending?: boolean) => void + handleDesignClick: (shouldBePending?: boolean) => void + handleLogicClick: (shouldBePending?: boolean) => void + handleClose: (shouldBePending?: boolean) => void isDrawerOpen: boolean - handleClose: () => void fieldListTabIndex: FieldListTabIndex setFieldListTabIndex: (tabIndex: FieldListTabIndex) => void } @@ -60,13 +60,15 @@ export const useCreatePageSidebarContext = const isMobile = useIsMobile() const [activeTab, setActiveTab] = useState(null) // Any pending tab due to unsaved changes. - const [pendingTab, setPendingTab] = useState(null) + // Pending tab can be `null` if the next tab state is to be closed. + const [pendingTab, setPendingTab] = useState< + DrawerTabs | null | undefined + >() const isDrawerOpen = useMemo( () => activeTab !== null && activeTab !== DrawerTabs.Logic, [activeTab], ) const setFieldsToInactive = useFieldBuilderStore(setToInactiveSelector) - const isDirty = useFieldBuilderStore(isDirtySelector) const setDesignState = useDesignStore(setStateSelector) const [fieldListTabIndex, setFieldListTabIndex] = @@ -83,48 +85,62 @@ export const useCreatePageSidebarContext = if (activeTab !== DrawerTabs.Design) setDesignState(DesignState.Inactive) }, [activeTab, setDesignState]) - const setPendingTabIfDirty = useCallback( - (tab: DrawerTabs) => { - if (isDirty) { + const setActiveOrPendingTab = useCallback( + (tab: DrawerTabs | null, shouldBePending?: boolean) => { + if (shouldBePending) { setPendingTab(tab) } else { setActiveTab(tab) + if (tab === null && !isMobile) { + setFieldsToInactive() + } } }, - [isDirty], + [isMobile, setFieldsToInactive], ) + const clearPendingTab = useCallback(() => { + setPendingTab(undefined) + }, []) + const handleBuilderClick = useCallback( - () => setPendingTabIfDirty(DrawerTabs.Builder), - [setPendingTabIfDirty], + (shouldBePending?: boolean) => + setActiveOrPendingTab(DrawerTabs.Builder, shouldBePending), + [setActiveOrPendingTab], ) const handleDesignClick = useCallback( - () => setPendingTabIfDirty(DrawerTabs.Design), - [setPendingTabIfDirty], + (shouldBePending?: boolean) => + setActiveOrPendingTab(DrawerTabs.Design, shouldBePending), + [setActiveOrPendingTab], ) const handleLogicClick = useCallback( - () => setPendingTabIfDirty(DrawerTabs.Logic), - [setPendingTabIfDirty], + (shouldBePending?: boolean) => + setActiveOrPendingTab(DrawerTabs.Logic, shouldBePending), + [setActiveOrPendingTab], ) - const handleClose = useCallback(() => { - if (!isMobile) { - setFieldsToInactive() - } - setActiveTab(null) - }, [isMobile, setFieldsToInactive]) + const handleClose = useCallback( + (shouldBePending?: boolean) => { + setActiveOrPendingTab(null, shouldBePending) + }, + [setActiveOrPendingTab], + ) const movePendingToActiveTab = useCallback(() => { - if (pendingTab === null) return + if (pendingTab === undefined) return setActiveTab(pendingTab) - setPendingTab(null) - }, [pendingTab, setActiveTab, setPendingTab]) + if (pendingTab === null && !isMobile) { + setFieldsToInactive() + } + setPendingTab(undefined) + }, [isMobile, pendingTab, setFieldsToInactive]) return { activeTab, pendingTab, + clearPendingTab, movePendingToActiveTab, isDrawerOpen, handleBuilderClick,