diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/BuilderFields.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/BuilderFields.tsx
index d156b78ebd..eb7ab1dab5 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/BuilderFields.tsx
+++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/BuilderFields.tsx
@@ -4,13 +4,14 @@ import { useCreatePageSidebar } from '~features/admin-form/create/common/CreateP
import { augmentWithQuestionNo } from '~features/form/utils'
import { FieldIdSet } from '~features/logic/types'
-import { useBuilderAndDesignContext } from '../BuilderAndDesignContext'
import { PENDING_CREATE_FIELD_ID } from '../constants'
+import { isDirtySelector, useDirtyFieldStore } from '../useDirtyFieldStore'
import {
FieldBuilderState,
stateDataSelector,
useFieldBuilderStore,
} from '../useFieldBuilderStore'
+import { useDesignColorTheme } from '../utils/useDesignColorTheme'
import FieldRow from './FieldRow'
@@ -29,31 +30,41 @@ export const BuilderFields = ({
const stateData = useFieldBuilderStore(stateDataSelector)
const { handleBuilderClick } = useCreatePageSidebar()
- const {
- deleteFieldModalDisclosure: { onOpen: onDeleteModalOpen },
- } = useBuilderAndDesignContext()
+
+ const activeFieldId =
+ stateData.state === FieldBuilderState.EditingField
+ ? stateData.field._id
+ : stateData.state === FieldBuilderState.CreatingField
+ ? PENDING_CREATE_FIELD_ID
+ : null
+
+ const colorTheme = useDesignColorTheme()
+
+ const isDirty = useDirtyFieldStore(isDirtySelector)
return (
<>
- {fieldsWithQuestionNos.map((f, i) => (
-
- ))}
+ {fieldsWithQuestionNos.map((f, i) => {
+ const activeFieldExtraProps =
+ f._id === activeFieldId
+ ? {
+ isDraggingOver,
+ fieldBuilderState: stateData.state,
+ }
+ : {}
+ return (
+
+ )
+ })}
>
)
}
diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/FieldRowContainer.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/FieldRowContainer.tsx
index 90ba9beedc..c8c99ca8ed 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/FieldRowContainer.tsx
+++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/FieldRowContainer.tsx
@@ -55,7 +55,7 @@ import {
isMyInfo,
} from '~features/myinfo/utils'
-import { BuilderAndDesignContextProps } from '../../BuilderAndDesignContext'
+import { useBuilderAndDesignContext } from '../../BuilderAndDesignContext'
import {
setToInactiveSelector as setPaymentToInactiveSelector,
usePaymentStore,
@@ -68,7 +68,6 @@ import {
setStateSelector as setDesignStateSelector,
useDesignStore,
} from '../../useDesignStore'
-import { isDirtySelector, useDirtyFieldStore } from '../../useDirtyFieldStore'
import {
FieldBuilderState,
setToInactiveSelector,
@@ -76,7 +75,6 @@ import {
useFieldBuilderStore,
} from '../../useFieldBuilderStore'
import { getAttachmentSizeLimit } from '../../utils/getAttachmentSizeLimit'
-import { useDesignColorTheme } from '../../utils/useDesignColorTheme'
import { SectionFieldRow } from './SectionFieldRow'
import { VerifiableFieldBuilderContainer } from './VerifiableFieldBuilderContainer'
@@ -85,11 +83,13 @@ export interface FieldRowContainerProps {
field: FormFieldDto
index: number
isHiddenByLogic: boolean
- isDraggingOver: boolean
- isActive: boolean
- fieldBuilderState: FieldBuilderState
+ isDraggingOver?: boolean
+ // Field only needs to know fieldBuilderState if it is active, else it is agnostic to state
+ fieldBuilderState?: FieldBuilderState
+ isDirty: boolean
+ colorTheme?: FormColorTheme
+ // handleBuilderClick is passed down to prevent unnecessary re-renders from useContext
handleBuilderClick: CreatePageSidebarContextProps['handleBuilderClick']
- onDeleteModalOpen: BuilderAndDesignContextProps['deleteFieldModalDisclosure']['onOpen']
}
const FieldRowContainer = ({
@@ -97,31 +97,26 @@ const FieldRowContainer = ({
index,
isHiddenByLogic,
isDraggingOver,
- isActive,
fieldBuilderState,
+ isDirty,
+ colorTheme,
handleBuilderClick,
- onDeleteModalOpen,
}: FieldRowContainerProps): JSX.Element => {
const isMobile = useIsMobile()
- const { data: form } = useCreateTabForm()
const numFormFieldMutations = useIsMutating(adminFormKeys.base)
- const setToInactive = useFieldBuilderStore(setToInactiveSelector)
const updateEditState = useFieldBuilderStore(updateEditStateSelector)
- const isDirty = useDirtyFieldStore(isDirtySelector)
- const toast = useToast({ status: 'danger', isClosable: true })
-
const setDesignState = useDesignStore(setDesignStateSelector)
const setPaymentStateToInactive = usePaymentStore(
setPaymentToInactiveSelector,
)
- const { duplicateFieldMutation } = useDuplicateFormField()
- const { deleteFieldMutation } = useDeleteFormField()
-
- const colorTheme = useDesignColorTheme()
const isMyInfoField = useMemo(() => isMyInfo(field), [field])
+ // Explicitly defining isActive here to prevent constant checks to undefined
+ // due to falsy nature of FieldBuilderState.CreatingField = 0
+ const isActive = fieldBuilderState !== undefined
+
const defaultFieldValues = useMemo(() => {
if (field.fieldType === BasicField.Table) {
return {
@@ -191,52 +186,6 @@ const FieldRowContainer = ({
[handleFieldClick],
)
- const handleEditFieldClick = useCallback(() => {
- if (isMobile) {
- handleBuilderClick(false)
- }
- }, [handleBuilderClick, isMobile])
-
- const handleDuplicateClick = useCallback(() => {
- if (!form) return
- // Duplicate button should be hidden if field is not yet created, but guard here just in case
- if (fieldBuilderState === FieldBuilderState.CreatingField) return
- // Disallow duplicating attachment fields if after the dupe, the filesize exceeds the limit
- if (field.fieldType === BasicField.Attachment) {
- const existingAttachmentsSize = form.form_fields.reduce(
- (sum, ff) =>
- ff.fieldType === BasicField.Attachment
- ? sum + Number(ff.attachmentSize)
- : sum,
- 0,
- )
- const remainingAvailableSize =
- getAttachmentSizeLimit(form.responseMode) - existingAttachmentsSize
- const thisAttachmentSize = Number(field.attachmentSize)
- if (thisAttachmentSize > remainingAvailableSize) {
- toast({
- useMarkdown: true,
- description: `The field "${field.title}" could not be duplicated. The attachment size of **${thisAttachmentSize} MB** exceeds the form's remaining available attachment size of **${remainingAvailableSize} MB**.`,
- })
- return
- }
- }
- duplicateFieldMutation.mutate(field._id)
- }, [fieldBuilderState, field, duplicateFieldMutation, form, toast])
-
- const handleDeleteClick = useCallback(() => {
- if (fieldBuilderState === FieldBuilderState.CreatingField) {
- setToInactive()
- } else if (fieldBuilderState === FieldBuilderState.EditingField) {
- onDeleteModalOpen()
- }
- }, [setToInactive, fieldBuilderState, onDeleteModalOpen])
-
- const isAnyMutationLoading = useMemo(
- () => duplicateFieldMutation.isLoading || deleteFieldMutation.isLoading,
- [duplicateFieldMutation, deleteFieldMutation],
- )
-
const isDragDisabled = useMemo(() => {
return (
!isActive ||
@@ -347,52 +296,14 @@ const FieldRowContainer = ({
-
-
- {isMobile ? (
- }
- onClick={handleEditFieldClick}
- />
- ) : null}
- {
- // Fields which are not yet created cannot be duplicated
- fieldBuilderState !== FieldBuilderState.CreatingField && (
-
- }
- />
-
- )
- }
-
- }
- onClick={handleDeleteClick}
- isLoading={deleteFieldMutation.isLoading}
- isDisabled={isAnyMutationLoading}
- />
-
-
-
+ {isActive && (
+
+ )}
@@ -406,6 +317,124 @@ export const MemoizedFieldRow = memo(FieldRowContainer, (prevProps, newProps) =>
isEqual(prevProps, newProps),
)
+type FieldButtonGroupProps = {
+ field: FormFieldDto
+ fieldBuilderState: FieldBuilderState
+ isMobile: boolean
+ handleBuilderClick: CreatePageSidebarContextProps['handleBuilderClick']
+}
+
+const FieldButtonGroup = ({
+ field,
+ fieldBuilderState,
+ isMobile,
+ handleBuilderClick,
+}: FieldButtonGroupProps) => {
+ const setToInactive = useFieldBuilderStore(setToInactiveSelector)
+
+ const { data: form } = useCreateTabForm()
+
+ const toast = useToast({ status: 'danger', isClosable: true })
+
+ const { duplicateFieldMutation } = useDuplicateFormField()
+ const { deleteFieldMutation } = useDeleteFormField()
+ const {
+ deleteFieldModalDisclosure: { onOpen: onDeleteModalOpen },
+ } = useBuilderAndDesignContext()
+
+ const handleEditFieldClick = useCallback(() => {
+ if (isMobile) {
+ handleBuilderClick(false)
+ }
+ }, [handleBuilderClick, isMobile])
+
+ const isAnyMutationLoading = useMemo(
+ () => duplicateFieldMutation.isLoading || deleteFieldMutation.isLoading,
+ [duplicateFieldMutation, deleteFieldMutation],
+ )
+ const handleDuplicateClick = useCallback(() => {
+ // Duplicate button should be hidden if field is not yet created, but guard here just in case
+ if (fieldBuilderState === FieldBuilderState.CreatingField) return
+ // Disallow duplicating attachment fields if after the dupe, the filesize exceeds the limit
+
+ if (field.fieldType === BasicField.Attachment) {
+ // Get remaining available attachment size limit
+ const availableAttachmentSize = form
+ ? getAttachmentSizeLimit(form.responseMode) -
+ form.form_fields.reduce(
+ (sum, ff) =>
+ ff.fieldType === BasicField.Attachment
+ ? sum + Number(ff.attachmentSize)
+ : sum,
+ 0,
+ )
+ : 0
+ const thisAttachmentSize = Number(field.attachmentSize)
+ if (thisAttachmentSize > availableAttachmentSize) {
+ toast({
+ useMarkdown: true,
+ description: `The field "${field.title}" could not be duplicated. The attachment size of **${thisAttachmentSize} MB** exceeds the form's remaining available attachment size of **${availableAttachmentSize} MB**.`,
+ })
+ return
+ }
+ }
+ duplicateFieldMutation.mutate(field._id)
+ }, [form, fieldBuilderState, field, duplicateFieldMutation, toast])
+
+ const handleDeleteClick = useCallback(() => {
+ if (fieldBuilderState === FieldBuilderState.CreatingField) {
+ setToInactive()
+ } else if (fieldBuilderState === FieldBuilderState.EditingField) {
+ onDeleteModalOpen()
+ }
+ }, [setToInactive, fieldBuilderState, onDeleteModalOpen])
+
+ return (
+
+
+ {isMobile ? (
+ }
+ onClick={handleEditFieldClick}
+ />
+ ) : null}
+ {
+ // Fields which are not yet created cannot be duplicated
+ fieldBuilderState !== FieldBuilderState.CreatingField && (
+
+ }
+ />
+
+ )
+ }
+
+ }
+ onClick={handleDeleteClick}
+ isLoading={deleteFieldMutation.isLoading}
+ isDisabled={isAnyMutationLoading}
+ />
+
+
+
+ )
+}
+
type FieldRowProps = {
field: FormFieldDto
colorTheme?: FormColorTheme