Skip to content

Commit

Permalink
feat: add feedbackform on create wizard
Browse files Browse the repository at this point in the history
  • Loading branch information
KenLSM committed Dec 19, 2024
1 parent 66661be commit 041081b
Show file tree
Hide file tree
Showing 19 changed files with 400 additions and 71 deletions.
5 changes: 4 additions & 1 deletion .template-env
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,7 @@ FORMSG_SDK_MODE=
# AZURE_OPENAI_API_KEY=
# AZURE_OPENAI_ENDPOINT=
# AZURE_OPENAI_DEPLOYMENT_NAME=
# AZURE_OPENAI_API_VERSION=
# AZURE_OPENAI_API_VERSION=

## Kill email mode configs, provide a valid storage form id
# KILL_EMAIL_MODE_FEEDBACK_FORMID=
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ services:
- POSTMAN_BASE_URL=https://test.postman.gov.sg/api/v2
- DOWNLOAD_FORM_WHITELIST_RATE_LIMIT
- UPLOAD_FORM_WHITELIST_RATE_LIMIT
- KILL_EMAIL_MODE_FEEDBACK_FORMID=67642a79b207c811692cad51

mockpass:
build: https://github.com/opengovsg/mockpass.git#v4.3.1
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/features/public-form/PublicFormService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,20 @@ export const getPublicFormView = async (
.then(transformAllIsoStringsToDate)
}

/**
* TODO: (Kill Email Mode) Remove this route after kill email mode is fully implemented.
* Gets the BE defined feedback form for admins to answer why they are using email mode
* @returns Public view of form for admin email feedback form
*/
export const getAdminUseEmailModeFeedbackFormView =
async (): Promise<PublicFormViewDto> => {
return ApiService.get<PublicFormViewDto>(
`${PUBLIC_FORMS_ENDPOINT}/admin-use-email-feedback`,
)
.then(({ data }) => data)
.then(transformAllIsoStringsToDate)
}

/**
* Gets the redirect url for public form login
* @param formId form id of form to log in.
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/features/public-form/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ApiError } from '~typings/core'
import { MONGODB_ID_REGEX } from '~constants/routes'

import {
getAdminUseEmailModeFeedbackFormView,
getMultirespondentSubmissionById,
getPublicFormView,
} from './PublicFormService'
Expand Down Expand Up @@ -62,3 +63,19 @@ export const useEncryptedSubmission = (
},
)
}

/**
* TODO: (Kill Email Mode) Remove this after kill email mode is fully implemented.
* Queries the BE defined feedback form for admins to answer why they are using email mode
* @returns
*/
export const useAdminUseEmailModeFormView = (): UseQueryResult<
PublicFormViewDto,
ApiError
> => {
return useQuery<PublicFormViewDto, ApiError>(
publicFormKeys.id('useAdminUseEmailModeFormView'),
() => getAdminUseEmailModeFeedbackFormView(),
{},
)
}
78 changes: 77 additions & 1 deletion frontend/src/features/workspace/WorkspaceService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { AdminFeedbackDto, AdminFeedbackRating } from '~shared/types'
import {
AdminFeedbackDto,
AdminFeedbackRating,
AdminUseEmailModeFeedbackDto,
BasicField,
ErrorDto,
} from '~shared/types'
import {
AdminDashboardFormMetaDto,
CreateEmailFormBodyDto,
Expand All @@ -7,10 +13,15 @@ import {
DuplicateFormBodyDto,
FormDto,
FormId,
PublicFormViewDto,
} from '~shared/types/form/form'
import { WorkspaceDto } from '~shared/types/workspace'
import { removeAt } from '~shared/utils/immutable-array-fns'

import { ApiService } from '~services/ApiService'
import { CHECKBOX_OTHERS_INPUT_VALUE } from '~templates/Field/Checkbox/constants'

import { PUBLIC_FORMS_ENDPOINT } from '~features/public-form/PublicFormService'

export const ADMIN_FORM_ENDPOINT = '/admin/forms'
const ADMIN_WORKSPACES_ENDPOINT = '/admin/workspaces'
Expand Down Expand Up @@ -112,6 +123,71 @@ export const createMultirespondentModeForm = async (
)
}

const createFeedbackResponses = (
formInputs: AdminUseEmailModeFeedbackDto,
feedbackForm: PublicFormViewDto,
) => {
// const feedbackFormFieldsStructure: [string, number][] = [['reason', 0]]

const { fieldType, title, _id } = feedbackForm.form.form_fields[0]

const reasonCheckboxAnswerArray = formInputs['reason']
let answerArray: string[] = []
if (
reasonCheckboxAnswerArray !== undefined &&
reasonCheckboxAnswerArray.value
) {
const othersIndex = reasonCheckboxAnswerArray.value.findIndex(
(v) => v === CHECKBOX_OTHERS_INPUT_VALUE,
)
// Others is checked, so we need to add the input at othersInput to the answer array
if (othersIndex !== -1) {
answerArray = removeAt(reasonCheckboxAnswerArray.value, othersIndex)
answerArray.push(`Others: ${reasonCheckboxAnswerArray.othersInput}`)
} else {
answerArray = reasonCheckboxAnswerArray.value
}
}

const responses = [
{
_id,
question: title,
answerArray,
fieldType,
},
]
return responses
}

const createAdminEmailModeUseFeedback = (
formInputs: AdminUseEmailModeFeedbackDto,
feedbackForm: PublicFormViewDto,
) => {
const responses = createFeedbackResponses(formInputs, feedbackForm)
// convert content to FormData object
const formData = new FormData()
formData.append('body', JSON.stringify({ responses, version: 2.1 }))

return formData
}

// TODO: (Kill Email Mode) Remove this route after kill email mode is fully implemented.
export const submitUseEmailFormFeedback = async ({
body,
feedbackForm,
}: {
body: AdminUseEmailModeFeedbackDto
feedbackForm: PublicFormViewDto
}): Promise<boolean | ErrorDto> => {
if (!feedbackForm) return new Error('feedback form not provided')
const formData = createAdminEmailModeUseFeedback(body, feedbackForm)
return ApiService.post<boolean>(
`${PUBLIC_FORMS_ENDPOINT}/submissions/storage/email-mode-feedback?captchaResponse=null`,
formData,
).then(({ data }) => data)
}

export const dupeEmailModeForm = async (
formId: string,
body: DuplicateFormBodyDto,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const CreateFormDetailsScreen = (): JSX.Element => {
const {
formMethods,
handleDetailsSubmit,
handleEmailFeedbackSubmit,
isLoading,
isFetching,
modalHeader,
Expand All @@ -65,6 +66,9 @@ export const CreateFormDetailsScreen = (): JSX.Element => {

const titleInputValue = watch('title')
const responseModeValue = watch('responseMode')
const handleEmailButtonPress = () => {
handleEmailFeedbackSubmit()
}

return (
<>
Expand Down Expand Up @@ -98,7 +102,11 @@ export const CreateFormDetailsScreen = (): JSX.Element => {
control={control}
render={({ field }) => (
<WorkspaceRowsProvider>
<FormResponseOptions {...field} isSingpass={isSingpass} />
<FormResponseOptions
{...field}
isSingpass={isSingpass}
handleEmailButtonPress={handleEmailButtonPress}
/>
</WorkspaceRowsProvider>
)}
rules={{ required: 'Please select a form response mode' }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from '../CreateFormWizardContext'

import { CreateFormDetailsScreen } from './CreateFormDetailsScreen'
import { EmailModeFeedbackAndCreateScreen } from './EmailModeFeedbackAndCreateScreen'
import { SaveSecretKeyScreen } from './SaveSecretKeyScreen'

/**
Expand All @@ -21,6 +22,10 @@ export const CreateFormModalContent = () => {
<CreateFormDetailsScreen />
)}
{currentStep === CreateFormFlowStates.Landing && <SaveSecretKeyScreen />}
{/* TODO: (Kill Email Mode) Remove this route after kill email mode is fully implemented. */}
{currentStep === CreateFormFlowStates.EmailFeedback && (
<EmailModeFeedbackAndCreateScreen />
)}
</XMotionBox>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { FormProvider } from 'react-hook-form'
import { BiRightArrowAlt } from 'react-icons/bi'
import {
Container,
FormControl,
FormErrorMessage,
Input,
ModalBody,
Text,
} from '@chakra-ui/react'

import { BasicField } from '~shared/types'

import { GUIDE_PREVENT_EMAIL_BOUNCE } from '~constants/links'
import { FORM_TITLE_VALIDATION_RULES } from '~utils/formValidation'
import Button from '~components/Button'
import FormLabel from '~components/FormControl/FormLabel'
import { CheckboxField, CheckboxFieldSchema } from '~templates/Field'

import { useAdminUseEmailModeFormView } from '~features/public-form/queries'

import { useCreateFormWizard } from '../CreateFormWizardContext'

import { EmailFormRecipientsInput } from './EmailFormRecipientsInput'

const CHECKBOX_FIELD_SCHEMA: CheckboxFieldSchema = {
_id: 'reason',
fieldOptions: [
'I need to collect Sensitive High data',
'I need to receive attachments via email',
'I use the JSON in Email mode responses for automations',
],
othersRadioButton: true,
ValidationOptions: { customMax: null, customMin: null },
validateByValue: false,
fieldType: BasicField.Checkbox,
title: 'Why are you creating an Email mode form?',
description: '',
required: true,
disabled: false,
}

// TODO: (Kill Email Mode) Remove this route after kill email mode is fully implemented.
export const EmailModeFeedbackAndCreateScreen = (): JSX.Element => {
const {
formMethods,

handleCreateEmailModeForm,
isLoading,
isFetching,
} = useCreateFormWizard()
const {
register,
formState: { errors },
} = formMethods

const { data: feedbackForm } = useAdminUseEmailModeFormView()
if (!feedbackForm) return <></>

return (
<ModalBody whiteSpace="pre-wrap">
<FormProvider {...formMethods}>
<Container maxW="42.5rem" p={0}>
<FormControl isRequired isInvalid={!!errors.title} mb="2.25rem">
<FormLabel useMarkdownForDescription>Form name</FormLabel>

<Input
autoFocus
{...register('title', FORM_TITLE_VALIDATION_RULES)}
/>
<FormErrorMessage>{errors.title?.message}</FormErrorMessage>
</FormControl>

<FormControl isRequired isInvalid={!!errors.emails} mb="2.25rem">
<FormLabel
useMarkdownForDescription
description={`All email addresses below will be notified. Learn more on [how to guard against email bounces](${GUIDE_PREVENT_EMAIL_BOUNCE}).`}
>
Notifications for new responses
</FormLabel>
<EmailFormRecipientsInput />
</FormControl>

<FormControl isRequired isInvalid={!!errors.emails} mb="2.25rem">
<CheckboxField schema={CHECKBOX_FIELD_SCHEMA} />
</FormControl>
<Button
rightIcon={<BiRightArrowAlt fontSize="1.5rem" />}
type="submit"
isLoading={isLoading}
isDisabled={isFetching}
onClick={handleCreateEmailModeForm(feedbackForm)}
>
<Text lineHeight="1.5rem">Next step</Text>
</Button>
</Container>
</FormProvider>
</ModalBody>
)
}
Loading

0 comments on commit 041081b

Please sign in to comment.