diff --git a/src/formio/components/AddressNL.jsx b/src/formio/components/AddressNL.jsx index e2ab54c93..2302795ad 100644 --- a/src/formio/components/AddressNL.jsx +++ b/src/formio/components/AddressNL.jsx @@ -3,7 +3,7 @@ */ import {Formik, useFormikContext} from 'formik'; import debounce from 'lodash/debounce'; -import {useContext, useEffect} from 'react'; +import {useContext, useEffect, useState} from 'react'; import {createRoot} from 'react-dom/client'; import {Formio} from 'react-formio'; import {FormattedMessage, IntlProvider, defineMessages, useIntl} from 'react-intl'; @@ -77,6 +77,7 @@ export default class AddressNL extends Field { city: '', streetName: '', secretStreetCity: '', + autoPopulated: false, }; } @@ -199,6 +200,14 @@ const FIELD_LABELS = defineMessages({ description: 'Label for addressNL houseNumber input', defaultMessage: 'House number', }, + streetName: { + description: 'Label for addressNL streetName input', + defaultMessage: 'Street name', + }, + city: { + description: 'Label for addressNL city input', + defaultMessage: 'City', + }, }); const addressNLSchema = (required, intl, {postcode = {}, city = {}}) => { @@ -216,6 +225,7 @@ const addressNLSchema = (required, intl, {postcode = {}, city = {}}) => { }); let postcodeSchema = z.string().regex(postcodeRegex, {message: postcodeErrorMessage}); + let streetNameSchema = z.string(); const {pattern: cityPattern = '', errorMessage: cityErrorMessage = ''} = city; let citySchema = z.string(); if (cityPattern) { @@ -235,12 +245,15 @@ const addressNLSchema = (required, intl, {postcode = {}, city = {}}) => { if (!required) { postcodeSchema = postcodeSchema.optional(); houseNumberSchema = houseNumberSchema.optional(); + streetNameSchema = streetNameSchema.optional(); + citySchema = citySchema.optional(); } return z .object({ postcode: postcodeSchema, - city: citySchema.optional(), + streetName: streetNameSchema, + city: citySchema, houseNumber: houseNumberSchema, houseLetter: z .string() @@ -300,13 +313,15 @@ const AddressNLForm = ({initialValues, required, deriveAddress, layout, setFormi openForms: {components: nestedComponents}, }, } = useContext(ConfigContext); - const {postcode, city} = nestedComponents || {}; + const {postcode, city, streetName} = nestedComponents || {}; const postcodePattern = postcode?.validate?.pattern; const postcodeError = postcode?.translatedErrors[intl.locale].pattern; const cityPattern = city?.validate?.pattern; const cityError = city?.translatedErrors[intl.locale].pattern; + const streetNameError = streetName?.translatedErrors[intl.locale].pattern; + console.log(streetNameError, cityError); const errorMap = (issue, ctx) => { switch (issue.code) { case z.ZodIssueCode.invalid_type: { @@ -340,6 +355,7 @@ const AddressNLForm = ({initialValues, required, deriveAddress, layout, setFormi postcode: true, houseNumber: true, city: true, + streetName: true, }} validationSchema={toFormikValidationSchema( addressNLSchema(required, intl, { @@ -351,6 +367,9 @@ const AddressNLForm = ({initialValues, required, deriveAddress, layout, setFormi pattern: cityPattern, errorMessage: cityError, }, + streetName: { + errorMessage: streetNameError, + }, }), {errorMap} )} @@ -368,6 +387,7 @@ const AddressNLForm = ({initialValues, required, deriveAddress, layout, setFormi const FormikAddress = ({required, setFormioValues, deriveAddress, layout}) => { const {values, isValid, setFieldValue} = useFormikContext(); const {baseUrl} = useContext(ConfigContext); + const [isAddressDisabled, setAddressDisabled] = useState(true); const useColumns = layout === 'doubleColumn'; useEffect(() => { @@ -394,6 +414,12 @@ const FormikAddress = ({required, setFormioValues, deriveAddress, layout}) => { setFieldValue('city', data['city']); setFieldValue('streetName', data['streetName']); setFieldValue('secretStreetCity', data['secretStreetCity']); + + // mark the auto-filled fields as populated and disabled when they have been both + // retrieved from the API and they do have a value + const dataRetrieved = !!(data['city'] && data['streetName']); + setAddressDisabled(dataRetrieved); + setFieldValue('autoPopulated', dataRetrieved); }; return ( @@ -430,21 +456,23 @@ const FormikAddress = ({required, setFormioValues, deriveAddress, layout}) => { name="streetName" label={ } - disabled + disabled={isAddressDisabled} + isRequired={required} /> } - disabled + disabled={isAddressDisabled} + isRequired={required} /> )} diff --git a/src/formio/components/AddressNL.stories.js b/src/formio/components/AddressNL.stories.js index 20bd619c8..1b1d12276 100644 --- a/src/formio/components/AddressNL.stories.js +++ b/src/formio/components/AddressNL.stories.js @@ -195,7 +195,7 @@ export const WithFailedBRKValidation = { // }, }; -export const WithDeriveCityStreetNameWithData = { +export const WithDeriveCityStreetNameWithDataNotRequired = { render: SingleFormioComponent, parameters: { msw: { @@ -246,6 +246,115 @@ export const WithDeriveCityStreetNameWithData = { }, }; +export const WithDeriveCityStreetNameWithNoDataNotRequired = { + render: SingleFormioComponent, + parameters: { + msw: { + handlers: [mockBAGNoDataGet], + }, + }, + args: { + type: 'addressNL', + key: 'addressNL', + label: 'Address NL', + extraComponentProperties: { + validate: { + required: false, + }, + deriveAddress: true, + openForms: { + components: { + city: { + validate: {pattern: 'Amsterdam'}, + translatedErrors: { + nl: { + pattern: 'De stad moet Amsterdam zijn', + }, + }, + }, + }, + }, + }, + }, + play: async ({canvasElement}) => { + const canvas = within(canvasElement); + + const postcodeInput = await canvas.findByLabelText('Postcode'); + await userEvent.type(postcodeInput, '1234AB'); + + const houseNumberInput = await canvas.findByLabelText('Huisnummer'); + await userEvent.type(houseNumberInput, '1'); + + const city = await canvas.findByLabelText('Stad'); + const streetName = await canvas.findByLabelText('Straatnaam'); + + await userEvent.tab(); + + await waitFor(() => { + expect(city).toHaveValue(''); + expect(city).not.toBeDisabled(); + expect(streetName).toHaveValue(''); + expect(streetName).not.toBeDisabled(); + }); + }, +}; +export const WithDeriveCityStreeNameNoDataAndRequired = { + render: SingleFormioComponent, + parameters: { + msw: { + handlers: [mockBAGNoDataGet], + }, + }, + args: { + type: 'addressNL', + key: 'addressNL', + label: 'Address NL', + extraComponentProperties: { + validate: { + required: true, + }, + deriveAddress: true, + openForms: { + components: { + city: { + validate: {pattern: 'Amsterdam'}, + translatedErrors: { + nl: { + pattern: 'De stad moet Amsterdam zijn', + }, + }, + }, + }, + }, + }, + }, + play: async ({canvasElement}) => { + const canvas = within(canvasElement); + + const postcodeInput = await canvas.findByLabelText('Postcode'); + postcodeInput.focus(); + await userEvent.tab(); + + const city = await canvas.findByLabelText('Stad'); + const streetName = await canvas.findByLabelText('Straatnaam'); + const postcode_error = await canvas.findByText('Postcode is verplicht.'); + const house_number_error = await canvas.findByText('Huisnummer is verplicht.'); + const city_error = await canvas.findByText('Stad is verplicht.'); + const street_name_error = await canvas.findByText('Straatnaam is verplicht.'); + + await waitFor(() => { + expect(city).toHaveValue(''); + expect(city).toBeDisabled(); + expect(streetName).toHaveValue(''); + expect(streetName).toBeDisabled(); + expect(postcode_error).toBeVisible(); + expect(house_number_error).toBeVisible(); + expect(city_error).toBeVisible(); + expect(street_name_error).toBeVisible(); + }); + }, +}; + export const IncorrectPostcode = { render: SingleFormioComponent, args: { diff --git a/src/i18n/compiled/en.json b/src/i18n/compiled/en.json index 69d4d9e5d..0f0fad705 100644 --- a/src/i18n/compiled/en.json +++ b/src/i18n/compiled/en.json @@ -397,6 +397,12 @@ "value": "Check and confirm" } ], + "AKhmW+": [ + { + "type": 0, + "value": "Street name" + } + ], "AM6xqd": [ { "type": 0, @@ -491,12 +497,6 @@ "value": "Remove" } ], - "DEetjI": [ - { - "type": 0, - "value": "Street name" - } - ], "DK2ewv": [ { "type": 0, @@ -1375,6 +1375,12 @@ "value": "Form temporarily unavailable" } ], + "ZNkl8Q": [ + { + "type": 0, + "value": "City" + } + ], "ZVQeut": [ { "type": 1, @@ -1789,6 +1795,12 @@ "value": "Product" } ], + "leZlV+": [ + { + "type": 0, + "value": "Street name" + } + ], "lmWBQT": [ { "type": 0, @@ -1925,12 +1937,6 @@ "value": "." } ], - "osSl3z": [ - { - "type": 0, - "value": "City" - } - ], "ovI+W7": [ { "type": 0, @@ -2069,6 +2075,12 @@ "value": "Send code" } ], + "s4+4p2": [ + { + "type": 0, + "value": "City" + } + ], "sSmY1N": [ { "type": 0, diff --git a/src/i18n/compiled/nl.json b/src/i18n/compiled/nl.json index a57a85100..87bfc6c12 100644 --- a/src/i18n/compiled/nl.json +++ b/src/i18n/compiled/nl.json @@ -397,6 +397,12 @@ "value": "Controleer en bevestig" } ], + "AKhmW+": [ + { + "type": 0, + "value": "Straatnaam" + } + ], "AM6xqd": [ { "type": 0, @@ -491,12 +497,6 @@ "value": "Verwijderen" } ], - "DEetjI": [ - { - "type": 0, - "value": "Straatnaam" - } - ], "DK2ewv": [ { "type": 0, @@ -1375,6 +1375,12 @@ "value": "Formulier tijdelijk onbeschikbaar" } ], + "ZNkl8Q": [ + { + "type": 0, + "value": "Stad" + } + ], "ZVQeut": [ { "type": 0, @@ -1793,6 +1799,12 @@ "value": "Product" } ], + "leZlV+": [ + { + "type": 0, + "value": "Straatnaam" + } + ], "lmWBQT": [ { "type": 0, @@ -1929,12 +1941,6 @@ "value": " zijn." } ], - "osSl3z": [ - { - "type": 0, - "value": "Stad" - } - ], "ovI+W7": [ { "type": 0, @@ -2073,6 +2079,12 @@ "value": "Verstuur code" } ], + "s4+4p2": [ + { + "type": 0, + "value": "Stad" + } + ], "sSmY1N": [ { "type": 0, diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 965f9c360..292c94a44 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -174,6 +174,11 @@ "description": "Check overview and confirm", "originalDefault": "Check and confirm" }, + "AKhmW+": { + "defaultMessage": "Street name", + "description": "Label for addressNL streetName input", + "originalDefault": "Street name" + }, "AM6xqd": { "defaultMessage": "House number must be a number with up to five digits (e.g. 456).", "description": "ZOD error message when AddressNL house number does not match the house number regular expression", @@ -249,11 +254,6 @@ "description": "Appointments: remove product/service button text", "originalDefault": "Remove" }, - "DEetjI": { - "defaultMessage": "Street name", - "description": "Label for addressNL streetName read only result", - "originalDefault": "Street name" - }, "DK2ewv": { "defaultMessage": "Authentication problem", "description": "'Permission denied' error title", @@ -639,6 +639,11 @@ "description": "'Maintenance mode form' error title", "originalDefault": "Form temporarily unavailable" }, + "ZNkl8Q": { + "defaultMessage": "City", + "description": "Label for addressNL city result or entered value", + "originalDefault": "City" + }, "ZVQeut": { "defaultMessage": "{field} is a required field.", "description": "Required field error message", @@ -874,6 +879,11 @@ "description": "Appointments products step page title", "originalDefault": "Product" }, + "leZlV+": { + "defaultMessage": "Street name", + "description": "Label for addressNL streetName result or entered value", + "originalDefault": "Street name" + }, "lmWBQT": { "defaultMessage": "The code is exactly six characters long and consists of only uppercase letters and numbers.", "description": "Email verification: code input field description", @@ -934,11 +944,6 @@ "description": "ZOD 'too_big' error message, for BigInt", "originalDefault": "BigInt must be {exact, select, true {exactly equal to} other {{inclusive, select, true {less than or equal to} other {less than}}} } {maximum}." }, - "osSl3z": { - "defaultMessage": "City", - "description": "Label for addressNL city read only result", - "originalDefault": "City" - }, "ovI+W7": { "defaultMessage": "Use ⌘ + scroll to zoom the map", "description": "Gesturehandeling mac scroll message.", @@ -984,6 +989,11 @@ "description": "Email verification: send code button text", "originalDefault": "Send code" }, + "s4+4p2": { + "defaultMessage": "City", + "description": "Label for addressNL city input", + "originalDefault": "City" + }, "sSmY1N": { "defaultMessage": "Find address", "description": "The leaflet map's input fields placeholder message.", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index 4ef35f536..e9a86dc94 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -176,6 +176,11 @@ "description": "Check overview and confirm", "originalDefault": "Check and confirm" }, + "AKhmW+": { + "defaultMessage": "Straatnaam", + "description": "Label for addressNL streetName input", + "originalDefault": "Street name" + }, "AM6xqd": { "defaultMessage": "Huisnummer moet een nummer zijn met maximaal 5 cijfers (bijv. 456).", "description": "ZOD error message when AddressNL house number does not match the house number regular expression", @@ -252,11 +257,6 @@ "description": "Appointments: remove product/service button text", "originalDefault": "Remove" }, - "DEetjI": { - "defaultMessage": "Straatnaam", - "description": "Label for addressNL streetName read only result", - "originalDefault": "Street name" - }, "DK2ewv": { "defaultMessage": "Inlogprobleem", "description": "'Permission denied' error title", @@ -647,6 +647,11 @@ "description": "'Maintenance mode form' error title", "originalDefault": "Form temporarily unavailable" }, + "ZNkl8Q": { + "defaultMessage": "Stad", + "description": "Label for addressNL city result or entered value", + "originalDefault": "City" + }, "ZVQeut": { "defaultMessage": "Het verplichte veld {field} is niet ingevuld.", "description": "Required field error message", @@ -886,6 +891,11 @@ "isTranslated": true, "originalDefault": "Product" }, + "leZlV+": { + "defaultMessage": "Straatnaam", + "description": "Label for addressNL streetName result or entered value", + "originalDefault": "Street name" + }, "lmWBQT": { "defaultMessage": "De bevestigingscode is precies zes tekens lang en bestaat uit hoofdletters en getallen.", "description": "Email verification: code input field description", @@ -946,11 +956,6 @@ "description": "ZOD 'too_big' error message, for BigInt", "originalDefault": "BigInt must be {exact, select, true {exactly equal to} other {{inclusive, select, true {less than or equal to} other {less than}}} } {maximum}." }, - "osSl3z": { - "defaultMessage": "Stad", - "description": "Label for addressNL city read only result", - "originalDefault": "City" - }, "ovI+W7": { "defaultMessage": "Gebruik ⌘ + scroll om te zoomen in de kaart", "description": "Gesturehandeling mac scroll message.", @@ -996,6 +1001,11 @@ "description": "Email verification: send code button text", "originalDefault": "Send code" }, + "s4+4p2": { + "defaultMessage": "Stad", + "description": "Label for addressNL city input", + "originalDefault": "City" + }, "sSmY1N": { "defaultMessage": "Zoek adres", "description": "The leaflet map's input fields placeholder message.",