Skip to content

Commit

Permalink
feat(application-system): Add phone and email fields to tablerepeater (
Browse files Browse the repository at this point in the history
…#17219)

* Initial field and validation

* feat(ExampleForm): Update table repeater field and validation schema

- Renamed 'rentalHousingLandlordInfoTable' to 'tableRepeaterField' in ExampleForm.
- Updated ExampleSchema to include validation for the new 'tableRepeaterField' structure.
- Enhanced NationalIdWithName component to accept phone and email default values.
- Refactored error handling in NationalIdWithName for better validation feedback.
- Cleaned up unused code in TableRepeaterFormField for improved readability.

* fix lint

* cleanup

* Error message cleanup

* cleanup

* Refine dataschema

* PR comment

* Fix field layout

* Cleanup types

* Revert example form changes

* Update libs/application/ui-components/src/components/NationalIdWithName/NationalIdWithName.tsx

Add suggestion from jonni

Co-authored-by: Jónas G. Sigurðsson <[email protected]>

---------

Co-authored-by: Jónas G. Sigurðsson <[email protected]>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 16, 2024
1 parent 8aa0e52 commit 3314c57
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ const careerHistoryCompaniesValidation = (data: any) => {
}
export const ExampleSchema = z.object({
approveExternalData: z.boolean().refine((v) => v),
tableRepeaterField: z.array(
z.object({
nationalIdWithName: z.object({
name: z.string().min(1).max(256),
nationalId: z.string().refine((n) => n && kennitala.isValid(n), {
params: m.dataSchemeNationalId,
}),
phone: z.string().refine(isValidNumber, {
params: m.dataSchemePhoneNumber,
}),
email: z.string().email(),
}),
}),
),
person: z.object({
name: z.string().min(1).max(256),
age: z.string().refine((x) => {
Expand Down
6 changes: 6 additions & 0 deletions libs/application/types/src/lib/Fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ export type RepeaterItem = {
*/
displayInTable?: boolean
label?: StaticText
phoneLabel?: StaticText
emailLabel?: StaticText
placeholder?: StaticText
options?: TableRepeaterOptions
backgroundColor?: 'blue' | 'white'
Expand All @@ -99,6 +101,10 @@ export type RepeaterItem = {
activeField?: Record<string, string>,
) => boolean
dataTestId?: string
showPhoneField?: boolean
phoneRequired?: boolean
showEmailField?: boolean
emailRequired?: boolean
readonly?:
| boolean
| ((
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { FC, useEffect, useState } from 'react'
import { GridRow, GridColumn } from '@island.is/island-ui/core'
import { useLocale } from '@island.is/localization'
import {
coreErrorMessages,
getErrorViaPath,
getValueViaPath,
} from '@island.is/application/core'
import { coreErrorMessages, getValueViaPath } from '@island.is/application/core'
import { Application, StaticText } from '@island.is/application/types'
import { gql, useLazyQuery } from '@apollo/client'
import {
IdentityInput,
Query,
RskCompanyInfoInput,
} from '@island.is/api/schema'
import { InputController } from '@island.is/shared/form-fields'
import {
InputController,
PhoneInputController,
} from '@island.is/shared/form-fields'
import { useFormContext } from 'react-hook-form'
import * as kennitala from 'kennitala'
import debounce from 'lodash/debounce'
Expand All @@ -27,14 +26,23 @@ interface NationalIdWithNameProps {
customId?: string
customNationalIdLabel?: StaticText
customNameLabel?: StaticText
phoneLabel?: StaticText
emailLabel?: StaticText
phoneRequired?: boolean
emailRequired?: boolean
onNationalIdChange?: (s: string) => void
onNameChange?: (s: string) => void
nationalIdDefaultValue?: string
nameDefaultValue?: string
phoneDefaultValue?: string
emailDefaultValue?: string
errorMessage?: string
minAgePerson?: number
searchPersons?: boolean
searchCompanies?: boolean
showPhoneField?: boolean
showEmailField?: boolean
error?: string
}

export const NationalIdWithName: FC<
Expand All @@ -46,19 +54,30 @@ export const NationalIdWithName: FC<
required,
customId = '',
customNationalIdLabel = '',
phoneLabel = '',
emailLabel = '',
phoneRequired = false,
emailRequired = false,
customNameLabel = '',
onNationalIdChange,
onNameChange,
nationalIdDefaultValue,
nameDefaultValue,
phoneDefaultValue,
emailDefaultValue,
errorMessage,
minAgePerson,
searchPersons = true,
searchCompanies = false,
showPhoneField = false,
showEmailField = false,
error,
}) => {
const fieldId = customId.length > 0 ? customId : id
const nameField = `${fieldId}.name`
const nationalIdField = `${fieldId}.nationalId`
const emailField = `${fieldId}.email`
const phoneField = `${fieldId}.phone`

const { formatMessage } = useLocale()
const {
Expand All @@ -69,12 +88,18 @@ export const NationalIdWithName: FC<
const [personName, setPersonName] = useState('')
const [companyName, setCompanyName] = useState('')

// get name validation errors
const nameFieldErrors = errorMessage
? nameDefaultValue?.length === 0
? errorMessage
: undefined
: getErrorViaPath(errors, nameField)
const getFieldErrorString = (
error: unknown,
id: string,
): string | undefined => {
if (!error || typeof error !== 'object') return undefined

const errorList = error as Record<string, unknown>[]
if (!Array.isArray(errorList)) return undefined

const fieldError = errorList[id as any]
return typeof fieldError === 'string' ? fieldError : undefined
}

// get national id validation errors
let nationalIdFieldErrors: string | undefined
Expand All @@ -91,7 +116,7 @@ export const NationalIdWithName: FC<
{ minAge: minAgePerson },
)
} else if (!errorMessage) {
nationalIdFieldErrors = getErrorViaPath(errors, nationalIdField)
nationalIdFieldErrors = getFieldErrorString(error, 'nationalId')
}

// get default values
Expand All @@ -101,6 +126,12 @@ export const NationalIdWithName: FC<
const defaultName = nameDefaultValue
? nameDefaultValue
: getValueViaPath(application.answers, nameField, '')
const defaultPhone = phoneDefaultValue
? phoneDefaultValue
: getValueViaPath<string>(application.answers, phoneField, '')
const defaultEmail = emailDefaultValue
? emailDefaultValue
: getValueViaPath<string>(application.answers, emailField, '')

// query to get name by national id
const [getIdentity, { data, loading: queryLoading, error: queryError }] =
Expand Down Expand Up @@ -180,62 +211,95 @@ export const NationalIdWithName: FC<
}, [personName, companyName, setValue, nameField, application.answers])

return (
<GridRow>
<GridColumn span={['1/1', '1/1', '1/1', '1/2']} paddingTop={2}>
<InputController
id={nationalIdField}
label={
customNationalIdLabel
? formatMessage(customNationalIdLabel)
: formatMessage(coreErrorMessages.nationalRegistryNationalId)
}
defaultValue={defaultNationalId}
format="######-####"
required={required}
backgroundColor="blue"
onChange={debounce((v) => {
setNationalIdInput(v.target.value.replace(/\W/g, ''))
onNationalIdChange &&
onNationalIdChange(v.target.value.replace(/\W/g, ''))
})}
loading={searchPersons ? queryLoading : companyQueryLoading}
error={nationalIdFieldErrors}
disabled={disabled}
/>
</GridColumn>
<GridColumn span={['1/1', '1/1', '1/1', '1/2']} paddingTop={2}>
<InputController
id={nameField}
defaultValue={defaultName}
label={
customNameLabel
? formatMessage(customNameLabel)
: formatMessage(coreErrorMessages.nationalRegistryName)
}
required={required}
error={
searchPersons
? queryError || data?.identity === null
? formatMessage(
coreErrorMessages.nationalRegistryNameNotFoundForNationalId,
)
: nameFieldErrors && !data
? nameFieldErrors
: undefined
: searchCompanies
? companyQueryError ||
companyData?.companyRegistryCompany === null
? formatMessage(
coreErrorMessages.nationalRegistryNameNotFoundForNationalId,
)
: nameFieldErrors && !companyData
? nameFieldErrors
<>
<GridRow>
<GridColumn span={['1/1', '1/1', '1/1', '1/2']} paddingTop={2}>
<InputController
id={nationalIdField}
label={
customNationalIdLabel
? formatMessage(customNationalIdLabel)
: formatMessage(coreErrorMessages.nationalRegistryNationalId)
}
defaultValue={defaultNationalId}
format="######-####"
required={required}
backgroundColor="blue"
onChange={debounce((v) => {
setNationalIdInput(v.target.value.replace(/\W/g, ''))
onNationalIdChange &&
onNationalIdChange(v.target.value.replace(/\W/g, ''))
})}
loading={searchPersons ? queryLoading : companyQueryLoading}
error={nationalIdFieldErrors}
disabled={disabled}
/>
</GridColumn>
<GridColumn span={['1/1', '1/1', '1/1', '1/2']} paddingTop={2}>
<InputController
id={nameField}
defaultValue={defaultName}
label={
customNameLabel
? formatMessage(customNameLabel)
: formatMessage(coreErrorMessages.nationalRegistryName)
}
required={required}
error={
searchPersons
? queryError || data?.identity === null
? formatMessage(
coreErrorMessages.nationalRegistryNameNotFoundForNationalId,
)
: getFieldErrorString(error, 'name') && !data
? getFieldErrorString(error, 'name')
: undefined
: searchCompanies
? companyQueryError ||
companyData?.companyRegistryCompany === null
? formatMessage(
coreErrorMessages.nationalRegistryNameNotFoundForNationalId,
)
: getFieldErrorString(error, 'name') && !companyData
? getFieldErrorString(error, 'name')
: undefined
: undefined
: undefined
}
disabled
/>
</GridColumn>
</GridRow>
}
disabled
/>
</GridColumn>
</GridRow>
{(showPhoneField || showEmailField) && (
<GridRow>
{showPhoneField && (
<GridColumn span={['1/1', '1/1', '1/1', '1/2']} paddingTop={2}>
<PhoneInputController
id={phoneField}
label={formatMessage(phoneLabel)}
defaultValue={defaultPhone}
required={phoneRequired}
backgroundColor="blue"
error={getFieldErrorString(error, 'phone')}
disabled={disabled}
/>
</GridColumn>
)}
{showEmailField && (
<GridColumn span={['1/1', '1/1', '1/1', '1/2']} paddingTop={2}>
<InputController
id={emailField}
label={formatMessage(emailLabel)}
defaultValue={defaultEmail}
type="email"
required={emailRequired}
backgroundColor="blue"
error={getFieldErrorString(error, 'email')}
disabled={disabled}
/>
</GridColumn>
)}
</GridRow>
)}
</>
)
}

0 comments on commit 3314c57

Please sign in to comment.