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: address field #7966

Draft
wants to merge 38 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
33f3694
draft changes to address-field
scottheng96 Nov 28, 2024
7fb2dc3
Merge branch 'develop' into feat/address-field-fe
scottheng96 Nov 28, 2024
2668417
draft for local-address-field-fe
scottheng96 Dec 2, 2024
09fecb6
Merge branch 'develop' into feat/address-field-fe
scottheng96 Dec 2, 2024
81bf1d3
added chromatic stories
scottheng96 Dec 2, 2024
54484ef
removed unfinished be
scottheng96 Dec 3, 2024
841445e
resolved npm run build issues
scottheng96 Dec 3, 2024
9721840
added fe unit tests
scottheng96 Dec 3, 2024
eed538c
done api service call & custom error handling
scottheng96 Dec 4, 2024
2a88fc0
updated FE with chromatic review changes
scottheng96 Dec 5, 2024
8f18700
final changes to FE
scottheng96 Dec 5, 2024
eda5332
updated chromatic changes ui
scottheng96 Dec 6, 2024
edfa1ea
completed FE stories
scottheng96 Dec 6, 2024
dab4d38
BE work start chunk
scottheng96 Dec 9, 2024
2ccf5a9
Merge branch 'develop' into feat/address-field-fe
scottheng96 Dec 9, 2024
3ccb9e9
updated BE v3 response validations
scottheng96 Dec 9, 2024
dd32a67
checkpoint for BE validation
scottheng96 Dec 11, 2024
78a5871
consolidated fe and aligned be - failing on submission still
scottheng96 Dec 17, 2024
815573a
fixed cicd tests
scottheng96 Dec 17, 2024
9469d3d
fix imports
scottheng96 Dec 17, 2024
ca2d0ce
added splitting of address fields into single responses
scottheng96 Dec 18, 2024
ce31aad
submission successful but still 1 string
scottheng96 Dec 19, 2024
b12632e
createAnswersForAddres to split multi-response into single-responses
scottheng96 Dec 26, 2024
9b74598
fixed MRF issues for address field
scottheng96 Jan 5, 2025
b1af63d
updated code to support hypens in response page
scottheng96 Jan 9, 2025
774968f
merged with latest develop
scottheng96 Jan 9, 2025
fb51849
resolved PR comments
scottheng96 Jan 9, 2025
e849411
fixed csv for MRF (string[])
scottheng96 Jan 10, 2025
c4129d2
included unit number both or non validation
scottheng96 Jan 12, 2025
893c55f
checkpoint on getAnswersForAddress splitting into SingleResponses
scottheng96 Jan 13, 2025
bc07b21
fixed MRF, email and storage mode address field; csv downloadable, re…
scottheng96 Jan 14, 2025
132fbd7
resolved error messages for postal code and level number
scottheng96 Jan 14, 2025
3dccf35
fixed fe tests and lint errors
scottheng96 Jan 14, 2025
4cabf87
updated level unit error message & display only on submission
scottheng96 Jan 15, 2025
aa3b7bf
merged with latest develop
scottheng96 Jan 15, 2025
948808a
chore: update formsg-sdk to 0.13.0
scottheng96 Jan 15, 2025
e78096b
added onemap api url to CSP
scottheng96 Jan 15, 2025
86c4608
fixed error message to show only either level or unit
scottheng96 Jan 16, 2025
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
11 changes: 7 additions & 4 deletions __tests__/unit/backend/helpers/generate-form-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,12 +279,15 @@ export const generateNewSingleAnswerResponse = (
customParams?: Partial<ProcessedSingleAnswerResponse>,
): ProcessedSingleAnswerResponse => {
if (
[BasicField.Attachment, BasicField.Table, BasicField.Checkbox].includes(
fieldType,
)
[
BasicField.Attachment,
BasicField.Table,
BasicField.Checkbox,
BasicField.Address,
].includes(fieldType)
) {
throw new Error(
'Call the custom response generator functions for attachment, table and checkbox.',
'Call the custom response generator functions for attachment, address, table and checkbox.',
)
}
return {
Expand Down
18 changes: 9 additions & 9 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"@growthbook/growthbook-react": "^0.17.0",
"@hello-pangea/dnd": "^16.6.0",
"@loadable/component": "^5.16.4",
"@opengovsg/formsg-sdk": "^0.12.0-alpha.1",
"@opengovsg/formsg-sdk": "^0.13.0",
"@stablelib/base64": "^1.0.1",
"@stripe/react-stripe-js": "^1.15.0",
"@stripe/stripe-js": "^1.44.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useToast } from '~hooks/useToast'
import IconButton from '~components/IconButton'
import Tooltip from '~components/Tooltip'
import {
AddressCompoundField,
AttachmentField,
CheckboxField,
ChildrenCompoundField,
Expand Down Expand Up @@ -512,5 +513,7 @@ const FieldRow = ({ field, ...rest }: FieldRowProps) => {
return <TableField schema={field} {...rest} />
case BasicField.Children:
return <ChildrenCompoundField schema={field} {...rest} />
case BasicField.Address:
return <AddressCompoundField schema={field} {...rest} />
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
EditMyInfoChildren,
} from './edit-fieldtype/EditMyInfoChildren'
import {
EditAddress,
EditAttachment,
EditCheckbox,
EditCountryRegion,
Expand Down Expand Up @@ -167,6 +168,8 @@ export const MemoFieldDrawerContent = memo<MemoFieldDrawerContentProps>(
return <EditParagraph {...props} field={field} />
case BasicField.Image:
return <EditImage {...props} field={field} />
case BasicField.Address:
return <EditAddress {...props} field={field} />
default:
return <div>TODO: Insert field options here</div>
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Meta, StoryFn } from '@storybook/react'

import { AddressCompoundFieldBase, BasicField } from '~shared/types'

import { createFormBuilderMocks } from '~/mocks/msw/handlers/admin-form'

import { EditFieldDrawerDecorator, StoryRouter } from '~utils/storybook'

import { EditAddress, EditAddressProps } from './EditAddress'

const DEFAULT_ADDRESS_FIELD: AddressCompoundFieldBase = {
title: 'Local address',
description: '',
required: true,
disabled: false,
fieldType: BasicField.Address,
globalId: 'unused',
}

export default {
title: 'Features/AdminForm/EditFieldDrawer/EditAddress',
component: EditAddress,
decorators: [
StoryRouter({
initialEntries: ['/61540ece3d4a6e50ac0cc6ff'],
path: '/:formId',
}),
EditFieldDrawerDecorator,
],
parameters: {
// Required so skeleton "animation" does not hide content.
chromatic: { pauseAnimationAtEnd: true },
msw: createFormBuilderMocks({}, 0),
},
args: {
field: DEFAULT_ADDRESS_FIELD,
},
} as Meta<EditAddressProps>

interface StoryArgs {
field: AddressCompoundFieldBase
}

const Template: StoryFn<StoryArgs> = ({ field }) => {
return <EditAddress field={field} />
}

export const Default = Template.bind({})

export const WithValues = Template.bind({})
WithValues.args = {
field: {
...DEFAULT_ADDRESS_FIELD,
title: 'Address Field Title',
description: 'Address Field Description',
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useMemo } from 'react'
import { FormControl } from '@chakra-ui/react'
import { extend, pick } from 'lodash'

import { AddressCompoundFieldBase } from '~shared/types/field'

import { createBaseValidationRules } from '~utils/fieldValidation'
import FormErrorMessage from '~components/FormControl/FormErrorMessage'
import FormLabel from '~components/FormControl/FormLabel'
import Input from '~components/Input'
import Textarea from '~components/Textarea'
import Toggle from '~components/Toggle'

import { CreatePageDrawerContentContainer } from '~features/admin-form/create/common'

import { FormFieldDrawerActions } from '../common/FormFieldDrawerActions'
import { EditFieldProps } from '../common/types'
import { useEditFieldForm } from '../common/useEditFieldForm'

export type EditAddressProps = EditFieldProps<AddressCompoundFieldBase>

type EditAddressInputs = Pick<
AddressCompoundFieldBase,
'title' | 'description' | 'required'
>

export const EditAddress = ({ field }: EditAddressProps): JSX.Element => {
const {
register,
formState: { errors },
getValues,
buttonText,
handleUpdateField,
watch,
control,
clearErrors,
isLoading,
handleCancel,
setValue,
} = useEditFieldForm<EditAddressInputs, AddressCompoundFieldBase>({
field,
transform: {
input: (inputField) =>
pick(inputField, ['title', 'description', 'required']),
output: (formOutput, originalField) =>
extend({}, originalField, formOutput),
},
})

const requiredValidationRule = useMemo(
() => createBaseValidationRules({ required: true }),
[],
)
return (
<CreatePageDrawerContentContainer>
<FormControl isRequired isReadOnly={isLoading} isInvalid={!!errors.title}>
<FormLabel>Field Name</FormLabel>
<Input autoFocus {...register('title', requiredValidationRule)} />
<FormErrorMessage>{errors?.title?.message}</FormErrorMessage>
</FormControl>
<FormControl isReadOnly={isLoading} isInvalid={!!errors.description}>
<FormLabel>Description</FormLabel>
<Textarea {...register('description')} />
<FormErrorMessage>{errors?.description?.message}</FormErrorMessage>
</FormControl>
<FormControl isReadOnly={isLoading}>
<Toggle {...register('required')} label="Required" />
</FormControl>
<FormFieldDrawerActions
isLoading={isLoading}
buttonText={buttonText}
handleClick={handleUpdateField}
handleCancel={handleCancel}
/>
</CreatePageDrawerContentContainer>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { EditAddress } from './EditAddress'
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './EditAddress'
export * from './EditAttachment'
export * from './EditCheckbox'
export * from './EditCountryRegion'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const BASIC_FIELDS_ORDERED = [
BasicField.Email,
BasicField.Mobile,
BasicField.HomeNo,
BasicField.Address,
BasicField.Date,
BasicField.Image,
BasicField.Table,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ export const getFieldCreationMeta = (fieldType: BasicField): FieldCreateDto => {
...baseMeta,
}
}
case BasicField.Address: {
return {
fieldType,
...baseMeta,
}
}
}
}

Expand Down
19 changes: 19 additions & 0 deletions frontend/src/features/admin-form/create/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
BiUser,
BiUserVoice,
} from 'react-icons/bi'
import { SlLocationPin } from 'react-icons/sl'
import { As } from '@chakra-ui/react'

import { BasicField, MyInfoAttribute } from '~shared/types/field'
Expand Down Expand Up @@ -266,6 +267,24 @@ export const BASICFIELD_TO_DRAWER_META: {
icon: BiGroup,
isSubmitted: true,
},

[BasicField.Address]: {
label: 'Local address',
icon: SlLocationPin,
isSubmitted: true,
scottheng96 marked this conversation as resolved.
Show resolved Hide resolved
searchAliases: [
'address',
'location',
'place',
'destination',
'directions',
'postal code',
'street',
'building',
'road',
'venue',
],
},
}

const BiDummyIcon = BiCalendar // random icon that is not actually shown in app
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import Spinner from '~components/Spinner'

import { AugmentedDecryptedResponse } from '../ResponsesPage/storage/utils/augmentDecryptedResponses'

import { useMutateDownloadAttachments } from './mutations'
import {
handleAddressResponseDisplay,
useMutateDownloadAttachments,
} from './mutations'

export interface DecryptedRowBaseProps {
row: AugmentedDecryptedResponse
Expand Down Expand Up @@ -104,6 +107,18 @@ const DecryptedAttachmentRow = ({
)
}

const DecryptedAddressRow = ({ row }: DecryptedRowBaseProps): JSX.Element => {
const transformedAddress = handleAddressResponseDisplay(
row.answerArray as string[],
).join(', ')
return (
<Stack>
<DecryptedQuestionLabel row={row} />
<Text textStyle="body-1">{transformedAddress}</Text>
</Stack>
)
}

export const DecryptedRow = memo(
({ row, attachmentDecryptionKey }: DecryptedRowProps): JSX.Element => {
switch (row.fieldType) {
Expand All @@ -118,6 +133,8 @@ export const DecryptedRow = memo(
)
case BasicField.Table:
return <DecryptedTableRow row={row} />
case BasicField.Address:
return <DecryptedAddressRow row={row} />
default:
return (
<Stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,29 @@ export const useMutateDownloadAttachments = () => {

return { downloadAttachmentMutation, downloadAttachmentsAsZipMutation }
}

/**
* Converts address field info as Single Responses into single-line string for response page display
* String manipulation done on frontend in DecryptRow
* since backend requires inputs to be single responses for info to be separate csv columns
*
* responses format:
* ["blockNumber_161","streetName_BUKIT BATOK STREET 11","buildingName_","levelNumber_","unitNumber_","postalCode_650161"]
*/
export const handleAddressResponseDisplay = (responses: string[]) => {
const ans = responses
if (Array.isArray(ans)) {
let arr: string[] = []
if (ans.every((item) => typeof item === 'string')) {
arr = ans.map((item) => (item as string).split('_')[1])
}
if (arr && arr[arr.length - 1])
arr[arr.length - 1] = 'SINGAPORE ' + arr[arr.length - 1]
if (arr && arr[arr.length - 2] && arr[arr.length - 3]) {
const combinedUnit = '#' + arr[arr.length - 2] + '-' + arr[arr.length - 3]
arr.splice(arr.length - 3, 2, combinedUnit)
}
return arr
}
return responses
}
Loading
Loading