Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(v2/end-page-builder-2) edit end page from builder #4047

Merged
merged 21 commits into from
Jul 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1d615e9
feat: add end page builder in settings
hanstirtaputra Jun 9, 2022
77cea09
feat: update settings end page validation behaviour
hanstirtaputra Jun 10, 2022
89ab323
fix: remove endPage from settings model
hanstirtaputra Jun 15, 2022
b8ebe42
refactor: create separate AdminFormPage service for endpage updates
hanstirtaputra Jun 15, 2022
fbc423d
fix: broken storybook for end page settings
hanstirtaputra Jun 23, 2022
1818505
fix: use createFormBuilderMocks instead of getAdminForm in storybook
hanstirtaputra Jun 23, 2022
2e49085
feat: add thank you button in builder
hanstirtaputra Jun 13, 2022
cfa5010
feat: add end page builder store
hanstirtaputra Jun 14, 2022
cc6a922
feat: link thank you button to side drawer
hanstirtaputra Jun 14, 2022
cb7ea4e
feat: add edit end page side drawer input
hanstirtaputra Jun 14, 2022
957c521
fix: duplicate EndPageUpdateDto type resulting in compile error
hanstirtaputra Jun 20, 2022
1530dea
feat: add end page builder view
hanstirtaputra Jun 20, 2022
1b8fd6f
refactor: builder drawer to split end page builder and field builder
hanstirtaputra Jun 20, 2022
b78f542
refactor: EditEndPage form controls
hanstirtaputra Jun 24, 2022
94f7821
fix: change MemoFieldDrawerContentProps field to non-optional
hanstirtaputra Jun 24, 2022
2b3c96a
refactor: remove unused FormPage enum base type
hanstirtaputra Jun 24, 2022
6f9c621
refactor: use React's children prop in BuilderContentContainerProps
hanstirtaputra Jun 27, 2022
0231fb1
fix: BuilderDrawerContainer props in EditFieldDrawer
hanstirtaputra Jun 27, 2022
191f7ef
fix: form builder drag and drop fieldId error
hanstirtaputra Jun 27, 2022
e0c6066
fix: render both EndPageView and FormBuilder instead of toggling betw…
hanstirtaputra Jul 6, 2022
4760027
fix: update FormBuilder style for thank you button
hanstirtaputra Jul 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
import { useEffect } from 'react'
import { useCallback, useEffect } from 'react'
import { Droppable } from 'react-beautiful-dnd'
import { Box, Flex } from '@chakra-ui/react'
import { Box, Flex, FlexProps, Image, Stack, Text } from '@chakra-ui/react'
import { format } from 'date-fns'

import { BxsChevronUp } from '~assets/icons/BxsChevronUp'
import Button from '~components/Button'

import { useAdminForm } from '~features/admin-form/common/queries'
import { ThankYouSvgr } from '~features/public-form/components/FormEndPage/components/ThankYouSvgr'

import { useCreatePageSidebar } from '../../common/CreatePageSidebarContext'
import {
endPageDataSelector,
useEndPageBuilderStore,
} from '../BuilderAndDesignDrawer/EditEndPageDrawer/useEndPageBuilderStore'
import { FIELD_LIST_DROP_ID } from '../constants'
import { DndPlaceholderProps } from '../types'
import {
BuildFieldState,
setToEditEndPageSelector,
setToInactiveSelector,
stateDataSelector,
useBuilderAndDesignStore,
} from '../useBuilderAndDesignStore'

Expand All @@ -15,69 +29,165 @@ import BuilderAndDesignPlaceholder from './BuilderAndDesignPlaceholder'
import { BuilderFields } from './BuilderFields'
import { useBuilderFields } from './useBuilderFields'

interface FormBuilderProps extends FlexProps {
placeholderProps: DndPlaceholderProps
}

const FormBuilder = ({
placeholderProps,
...props
}: FormBuilderProps): JSX.Element => {
const { builderFields } = useBuilderFields()
const { handleBuilderClick } = useCreatePageSidebar()
const setEditEndPage = useBuilderAndDesignStore(setToEditEndPageSelector)

return (
<Flex
m={{ base: 0, md: '2rem' }}
mb={0}
flex={1}
bg={{ base: 'secondary.100', md: 'primary.100' }}
p={{ base: '1.5rem', md: '2.5rem' }}
justify="center"
overflow="auto"
{...props}
>
<Flex flexDir="column" w="100%" maxW="57rem" h="fit-content">
<Flex bg="white" p={{ base: 0, md: '2.5rem' }} flexDir="column">
<Droppable droppableId={FIELD_LIST_DROP_ID}>
{(provided, snapshot) =>
builderFields?.length ? (
<Box
pos="relative"
ref={provided.innerRef}
{...provided.droppableProps}
>
<BuilderFields
fields={builderFields}
isDraggingOver={snapshot.isDraggingOver}
/>
{provided.placeholder}
<BuilderAndDesignPlaceholder
placeholderProps={placeholderProps}
isDraggingOver={snapshot.isDraggingOver}
/>
</Box>
) : (
<EmptyFormPlaceholder
ref={provided.innerRef}
{...provided.droppableProps}
isDraggingOver={snapshot.isDraggingOver}
onClick={handleBuilderClick}
/>
)
}
</Droppable>
</Flex>
<Button
py="1.5rem"
mt="1.5rem"
variant="outline"
borderColor="secondary.200"
colorScheme="secondary"
onClick={() => {
setEditEndPage()
handleBuilderClick()
}}
>
<Text textStyle="subhead-2">Customise your Thank you page</Text>
</Button>
</Flex>
</Flex>
)
}

const EndPageView = ({ ...props }: FlexProps): JSX.Element => {
const { data: form } = useAdminForm()
const endPageData = useEndPageBuilderStore(endPageDataSelector)

return (
<Flex
m={{ base: 0, md: '2rem' }}
mb={0}
flex={1}
bg="white"
p={{ base: '1.5rem', md: 0 }}
justify="center"
overflow="auto"
{...props}
>
<Stack w="100%">
<Flex justifyContent="center" pt="1rem" pb="0.5rem">
<Image src={form?.admin?.agency?.logo} h="4rem" />
</Flex>
<Flex backgroundColor="primary.100" justifyContent="center">
<ThankYouSvgr h="100%" pt="2.5rem" />
</Flex>

<Box px="4rem" pt="3rem">
<Flex justifyContent="space-between" alignItems="center">
<Text textStyle="h2" color="secondary.500">
{endPageData.title}
</Text>
<BxsChevronUp color="secondary.500" />
</Flex>

<Text textStyle="subhead-1" color="secondary.500" mt="1rem">
{endPageData.paragraph}
</Text>

<Text textStyle="subhead-1" color="secondary.500" mt="2.25rem">
{form?.title ?? 'Form Title'}
</Text>
<Text textStyle="body-1" color="neutral.500">
{form?._id ?? 'Form Identification Number'}
<br />
{format(new Date(), 'dd MMM yyyy, h:m aa')}
</Text>

<Flex pt="1.75rem" gap="2rem">
<Button>Save this response</Button>
<Button variant="clear">{endPageData.buttonText}</Button>
</Flex>
</Box>
</Stack>
</Flex>
)
}
interface BuilderAndDesignContentProps {
placeholderProps: DndPlaceholderProps
}

export const BuilderAndDesignContent = ({
placeholderProps,
}: BuilderAndDesignContentProps): JSX.Element => {
const setFieldsToInactive = useBuilderAndDesignStore(setToInactiveSelector)
const { builderFields } = useBuilderFields()
const { handleBuilderClick } = useCreatePageSidebar()
const { stateData, setToInactive: setFieldsToInactive } =
useBuilderAndDesignStore(
useCallback(
(state) => ({
stateData: stateDataSelector(state),
setToInactive: setToInactiveSelector(state),
}),
[],
),
)

useEffect(() => setFieldsToInactive, [setFieldsToInactive])

return (
<>
<Flex flex={1} bg="neutral.200" overflow="auto">
<Flex
m={{ base: 0, md: '2rem' }}
mb={0}
flex={1}
bg={{ base: 'secondary.100', md: 'primary.100' }}
p={{ base: '1.5rem', md: '2.5rem' }}
justify="center"
overflow="auto"
>
<Flex
h="fit-content"
bg="white"
p={{ base: 0, md: '2.5rem' }}
maxW="57rem"
w="100%"
flexDir="column"
>
<Droppable droppableId={FIELD_LIST_DROP_ID}>
{(provided, snapshot) =>
builderFields?.length ? (
<Box
pos="relative"
ref={provided.innerRef}
{...provided.droppableProps}
>
<BuilderFields
fields={builderFields}
isDraggingOver={snapshot.isDraggingOver}
/>
{provided.placeholder}
<BuilderAndDesignPlaceholder
placeholderProps={placeholderProps}
isDraggingOver={snapshot.isDraggingOver}
/>
</Box>
) : (
<EmptyFormPlaceholder
ref={provided.innerRef}
{...provided.droppableProps}
isDraggingOver={snapshot.isDraggingOver}
onClick={handleBuilderClick}
/>
)
}
</Droppable>
</Flex>
</Flex>
<EndPageView
display={
stateData.state === BuildFieldState.EditingEndPage ? 'flex' : 'none'
}
/>
<FormBuilder
placeholderProps={placeholderProps}
display={
stateData.state === BuildFieldState.EditingEndPage ? 'none' : 'flex'
}
/>
</Flex>
</>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
useBuilderAndDesignStore,
} from '../useBuilderAndDesignStore'

import { EditEndPageDrawer } from './EditEndPageDrawer/EditEndPageDrawer'
import { EditFieldDrawer } from './EditFieldDrawer'
import { FieldListDrawer } from './FieldListDrawer'

Expand Down Expand Up @@ -52,6 +53,8 @@ export const BuilderAndDesignDrawer = (): JSX.Element | null => {
createOrEditData.state === BuildFieldState.CreatingField
) {
return <EditFieldDrawer />
} else if (createOrEditData.state === BuildFieldState.EditingEndPage) {
return <EditEndPageDrawer />
}
return <FieldListDrawer />
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { useCallback, useMemo } from 'react'
import { UnpackNestedValue, useForm, useWatch } from 'react-hook-form'
import { useDebounce } from 'react-use'
import { FormControl, Stack } from '@chakra-ui/react'
import { cloneDeep } from 'lodash'

import { FormEndPage } from '~shared/types'

import { REQUIRED_ERROR } from '~constants/validation'
import { useIsMobile } from '~hooks/useIsMobile'
import Button from '~components/Button'
import FormErrorMessage from '~components/FormControl/FormErrorMessage'
import FormLabel from '~components/FormControl/FormLabel'
import Input from '~components/Input'
import Textarea from '~components/Textarea'

import { useMutateFormPage } from '~features/admin-form/common/mutations'
import { useAdminForm } from '~features/admin-form/common/queries'
import { buttonLinkRules } from '~features/admin-form/settings/components/EndPageSettingsSection/EndPageSettingsInput'

import {
setToInactiveSelector,
useBuilderAndDesignStore,
} from '../../useBuilderAndDesignStore'
import { DrawerContentContainer } from '../EditFieldDrawer/edit-fieldtype/common/DrawerContentContainer'

import {
setStateSelector,
useEndPageBuilderStore,
} from './useEndPageBuilderStore'

interface EndPageBuilderInputProps {
endPage: FormEndPage
}

export const EndPageBuilderInput = ({
endPage,
}: EndPageBuilderInputProps): JSX.Element => {
const isMobile = useIsMobile()
const { mutateFormEndPage } = useMutateFormPage()

const closeBuilderDrawer = useBuilderAndDesignStore(setToInactiveSelector)
const setEndPageBuilderState = useEndPageBuilderStore(setStateSelector)

const {
register,
formState: { errors },
control,
handleSubmit,
} = useForm<FormEndPage>({
mode: 'onBlur',
defaultValues: endPage,
})

const handleEndPageBuilderChanges = useCallback(
(endPageInputs) => {
setEndPageBuilderState({ ...(endPageInputs as FormEndPage) })
},
[setEndPageBuilderState],
)

const watchedInputs = useWatch({
control: control,
}) as UnpackNestedValue<FormEndPage>

const clonedWatchedInputs = useMemo(
() => cloneDeep(watchedInputs),
[watchedInputs],
)

useDebounce(() => handleEndPageBuilderChanges(clonedWatchedInputs), 300, [
Object.values(clonedWatchedInputs),
])

const handleUpdateEndPage = handleSubmit((endPage) =>
mutateFormEndPage.mutate(endPage),
)

return (
<DrawerContentContainer>
<Stack gap="2rem">
<FormControl isInvalid={!!errors.title}>
<FormLabel isRequired>Title</FormLabel>
<Input {...register('title', { required: REQUIRED_ERROR })} />
<FormErrorMessage>{errors.title?.message}</FormErrorMessage>
</FormControl>
<FormControl isInvalid={!!errors.paragraph}>
<FormLabel isRequired>Follow-up instructions</FormLabel>
<Textarea {...register('paragraph')} />
<FormErrorMessage>{errors.paragraph?.message}</FormErrorMessage>
</FormControl>
<Stack direction={['column', 'row']} gap={['2rem', '1rem']}>
<FormControl isInvalid={!!errors.buttonText}>
<FormLabel isRequired>Button text</FormLabel>
<Input
placeholder="Submit another form"
{...register('buttonText')}
/>
<FormErrorMessage>{errors.buttonText?.message}</FormErrorMessage>
</FormControl>
<FormControl isInvalid={!!errors.buttonLink}>
<FormLabel isRequired>Button redirect link</FormLabel>
<Input
placeholder="Default form link"
{...register('buttonLink', buttonLinkRules)}
/>
<FormErrorMessage>{errors.buttonLink?.message}</FormErrorMessage>
</FormControl>
</Stack>
</Stack>

<Stack
direction={{ base: 'column', md: 'row-reverse' }}
justifyContent="end"
spacing="1rem"
>
<Button isFullWidth={isMobile} onClick={handleUpdateEndPage}>
Save field
</Button>
<Button
isFullWidth={isMobile}
variant="clear"
colorScheme="secondary"
onClick={closeBuilderDrawer}
>
Cancel
</Button>
</Stack>
</DrawerContentContainer>
)
}

export const EditEndPage = (): JSX.Element => {
const { data: form } = useAdminForm()

return <>{form ? <EndPageBuilderInput endPage={form.endPage} /> : null}</>
}
Loading