Skip to content

Commit

Permalink
feat(Forms): add createMinimumAgeValidator to Field.NationalIdentit…
Browse files Browse the repository at this point in the history
…yNumber make a customizable `adultValidator` (#4057)

PR adds `createMinimumAgeValidator`.

---------

Co-authored-by: -l <[email protected]>
  • Loading branch information
tujoworker and langz authored Oct 11, 2024
1 parent 1f59f10 commit 6c20ba2
Show file tree
Hide file tree
Showing 8 changed files with 1,572 additions and 16 deletions.
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

0 comments on commit 6c20ba2

Please sign in to comment.