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(Forms): add createMinimumAgeValidator to Field.NationalIdentityNumber make a customizable adultValidator #4057

Merged
merged 12 commits into from
Oct 11, 2024
Merged
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ComponentBox from '../../../../../../shared/tags/ComponentBox'
import { createMinimumAgeValidator } from '@dnb/eufemia/src/extensions/forms/Field/NationalIdentityNumber'
import { Field } from '@dnb/eufemia/src/extensions/forms'

export const Empty = () => {
Expand Down Expand Up @@ -188,3 +189,51 @@ export const ValidationExtendValidator = () => {
</ComponentBox>
)
}

export const ValidationExtendValidatorAdult = () => {
return (
<ComponentBox scope={{ createMinimumAgeValidator }}>
{() => {
const adultValidator = createMinimumAgeValidator(18)
const myAdultValidator = (value, { validators }) => {
const { dnrAndFnrValidator } = validators

return [dnrAndFnrValidator, adultValidator]
}

return (
<Field.NationalIdentityNumber
required
value="56052459244"
onBlurValidator={myAdultValidator}
validateInitially
/>
)
}}
</ComponentBox>
)
}

export const ValidationFnrAdult = () => {
return (
<ComponentBox scope={{ createMinimumAgeValidator }}>
{() => {
const adultValidator = createMinimumAgeValidator(18)
const myFnrAdultValidator = (value, { validators }) => {
const { fnrValidator } = validators

return [fnrValidator, adultValidator]
}

return (
<Field.NationalIdentityNumber
required
value="49100651997"
onBlurValidator={myFnrAdultValidator}
validateInitially
/>
)
}}
</ComponentBox>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,28 @@ Below is an example of the error message displayed when there's an invalid Norwe

It validates [D numbers](https://www.skatteetaten.no/en/person/national-registry/identitetsnummer/d-nummer/) using the [fnrvalidator](https://github.com/navikt/fnrvalidator).

Below is an example of the error message displayed when there's an invalid D number:
Below is an example of the error message displayed when there's an invalid D number(a D number has its first number in the identification number increased by 4):

<Examples.ValidationDnr />

### Validation function

You can provide your own validation function.
You can provide your own validation function, either to `validator` or `onBlurValidator`.

<Examples.ValidationFunction />

### Extend validation with custom validation function

You can [extend the existing validations](/uilib/extensions/forms/create-component/useFieldProps/info/#validators)(`dnrValidator`, `fnrValidator`, and `dnrAndFnrValidator`) with your own validation function.
You can [extend the existing validations](/uilib/extensions/forms/create-component/useFieldProps/info/#validators)(`dnrValidator`, `fnrValidator`, `dnrAndFnrValidator`, and make your own age validator by using the `createMinimumAgeValidator` function) with your own validation function.

<Examples.ValidationExtendValidator />

### Extend validation with adult validator

You can [extend the existing validations](/uilib/extensions/forms/create-component/useFieldProps/info/#validators)(`dnrValidator`, `fnrValidator`, and `dnrAndFnrValidator`) with your own age validator, by using the `createMinimumAgeValidator` function.

<Examples.ValidationExtendValidatorAdult />

### Validate only national identity numbers(fnr) above 18 years old

<Examples.ValidationFnrAdult />
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,25 @@ import { NationalIdentityNumberProperties } from '@dnb/eufemia/src/extensions/fo

`Field.NationalIdentityNumber` expose the following validators through its `validator` and `onBlurValidator` property:

- `dnrValidator`: validates a d number.
- `dnrValidator`: validates a D number.
- `fnrValidator`: validates a national identity number (fødselsnummer).
- `dnrAndFnrValidator`:
- validates the identification number as a d number when first digit is 4 or greater (because a d number has its first number increased by 4).
- validates the identification number as a D number when first digit is 4 or greater (because a D number has its first number increased by 4).
- validates the identification number as a national identity number (fødselsnummer) when first digit is 3 or less.

You can create your own age validator by using the `createMinimumAgeValidator` function. It takes an age as a parameter and returns a validator function. The validator function takes a value and returns an error message if the value is not above the given age.
It validates if the identification number has a date of birth that is 18 years or older. It uses only the 7 first digits of the identification number to validate. The first 6 digits representing the birth of date, and the next digit represents the century.
As it only use the 7 first digits, it does not validate the identification number, therefore it's quite common to use this validator together with one of the validators above (`dnrValidator`, `fnrValidator` or `dnrAndFnrValidator`) to validate the identification number as well.

You need to import the `createMinimumAgeValidator` function from the `Field.NationalIdentityNumber` component:

```tsx
import { createMinimumAgeValidator } from '@dnb/eufemia/extensions/forms/Field/NationalIdentityNumber'

// Create a validator that validates if the value is above 18 years old
const above18YearsValidator = createMinimumAgeValidator(18)
```

See the following [example](/uilib/extensions/forms/feature-fields/NationalIdentityNumber/#extend-validation-with-custom-validation-function) on how to extend validation using the exposed validators.

## Translations
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useCallback, useMemo } from 'react'
import StringField, { Props as StringFieldProps } from '../String'
import { dnr, fnr } from '@navikt/fnrvalidator'
import { Validator } from '../../types'
import { FormError, Validator } from '../../types'

import useErrorMessage from '../../hooks/useErrorMessage'
import useTranslation from '../../hooks/useTranslation'
Expand All @@ -14,18 +14,28 @@ export type Props = Omit<StringFieldProps, 'onBlurValidator'> & {

function NationalIdentityNumber(props: Props) {
const translations = useTranslation().NationalIdentityNumber
const { label, errorRequired, errorFnr, errorDnr } = translations
const {
label,
errorRequired,
errorFnr,
errorDnr,
errorMinimumAgeValidator,
} = translations
const errorMessages = useErrorMessage(props.path, props.errorMessages, {
required: errorRequired,
pattern: errorFnr,
errorFnr,
errorDnr,
errorMinimumAgeValidator,
})

const fnrValidator = useCallback(
(value: string) => {
// have to check for undefined as @navikt/fnrvalidator does not support undefined
if (value !== undefined && fnr(value).status === 'invalid') {
if (
value !== undefined &&
(Number.parseInt(value.substring(0, 1)) > 3 ||
fnr(value).status === 'invalid')
) {
return Error(errorFnr)
}
},
Expand All @@ -34,8 +44,11 @@ function NationalIdentityNumber(props: Props) {

const dnrValidator = useCallback(
(value: string) => {
// have to check for undefined as @navikt/fnrvalidator does not support undefined
if (value !== undefined && dnr(value).status === 'invalid') {
if (
value !== undefined &&
(Number.parseInt(value.substring(0, 1)) < 4 ||
dnr(value).status === 'invalid')
) {
return Error(errorDnr)
}
},
Expand Down Expand Up @@ -106,5 +119,64 @@ function NationalIdentityNumber(props: Props) {
return <StringField {...StringFieldProps} />
}

export function getAgeByBirthDate(birthDate: Date): number {
const today = new Date()
const age = today.getFullYear() - birthDate.getFullYear()
const month = today.getMonth() - birthDate.getMonth()
const day = today.getDate() - birthDate.getDate()

if (month < 0 || (month === 0 && day < 0)) {
return age - 1
}

return age
}

export function getBirthDateByFnrOrDnr(value: string) {
if (value === undefined) {
return // stop here
}

const yearPart = value.substring(4, 6)
const centuryNumber = Number.parseInt(value.substring(6, 7))

const isBornIn20XX = centuryNumber >= 5
const year = isBornIn20XX ? `20${yearPart}` : `19${yearPart}`
const month = Number.parseInt(value.substring(2, 4))

const differentiatorValue =
value.length > 0 ? Number.parseInt(value.substring(0, 1)) : undefined
const isDnr = differentiatorValue && differentiatorValue > 3

const day = isDnr
? Number.parseInt(value.substring(0, 2)) - 40
: Number.parseInt(value.substring(0, 2))

return new Date(Number.parseInt(year), month - 1, day)
}

export function createMinimumAgeValidator(age: number) {
return (value: string) => {
if (typeof value !== 'string') {
return // stop here
}

if (value.length > 6) {
const date = getBirthDateByFnrOrDnr(value)
if (getAgeByBirthDate(date) >= age) {
return // stop here
}
}

return new FormError(
'NationalIdentityNumber.errorMinimumAgeValidator',
{
validationRule: 'errorMinimumAgeValidator', // "validationRule" Will be removed in future PR
messageValues: { age: String(age) },
}
)
}
}

NationalIdentityNumber._supportsSpacingProps = true
export default NationalIdentityNumber
Loading
Loading