From e9f4b554d7f0d52e081cc861952ebcff682fa5e4 Mon Sep 17 00:00:00 2001 From: vasileios Date: Fri, 17 Jan 2025 09:30:51 +0100 Subject: [PATCH] [open-formulieren/open-forms#5006] Updated addressNL component to manually fill in city and streetname Until now the addressNL component would retrieve the city and street name based on the postcode and housenumber. These fields were always read-only but now we want to be able to fill in city and street name if something goes wrong with the API call. --- src/formio/components/AddressNL.jsx | 35 +++++++++--- src/formio/components/AddressNL.stories.js | 63 +++++++++++++++++++++- src/i18n/compiled/en.json | 36 ++++++++----- src/i18n/compiled/nl.json | 36 ++++++++----- src/i18n/messages/en.json | 30 +++++++---- src/i18n/messages/nl.json | 30 +++++++---- 6 files changed, 178 insertions(+), 52 deletions(-) diff --git a/src/formio/components/AddressNL.jsx b/src/formio/components/AddressNL.jsx index e2ab54c93..07170ef6f 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() @@ -340,6 +353,7 @@ const AddressNLForm = ({initialValues, required, deriveAddress, layout, setFormi postcode: true, houseNumber: true, city: true, + streetName: true, }} validationSchema={toFormikValidationSchema( addressNLSchema(required, intl, { @@ -368,6 +382,7 @@ const AddressNLForm = ({initialValues, required, deriveAddress, layout, setFormi const FormikAddress = ({required, setFormioValues, deriveAddress, layout}) => { const {values, isValid, setFieldValue} = useFormikContext(); const {baseUrl} = useContext(ConfigContext); + const [isAddressAutoFilled, setAddressAutoFilled] = useState(true); const useColumns = layout === 'doubleColumn'; useEffect(() => { @@ -394,6 +409,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']); + setAddressAutoFilled(dataRetrieved); + setFieldValue('autoPopulated', dataRetrieved); }; return ( @@ -430,21 +451,23 @@ const FormikAddress = ({required, setFormioValues, deriveAddress, layout}) => { name="streetName" label={ } - disabled + disabled={isAddressAutoFilled} + isRequired={required} /> } - disabled + disabled={isAddressAutoFilled} + isRequired={required} /> )} diff --git a/src/formio/components/AddressNL.stories.js b/src/formio/components/AddressNL.stories.js index 20bd619c8..cc32cf1ff 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,63 @@ export const WithDeriveCityStreetNameWithData = { }, }; +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: { @@ -338,7 +395,7 @@ export const WithDeriveCityStreetNameWithDataIncorrectCity = { }, }; -export const WithDeriveCityStreetNameNoData = { +export const WithDeriveCityStreetNameNoDataAndNotRequired = { render: SingleFormioComponent, parameters: { msw: { @@ -372,7 +429,9 @@ export const WithDeriveCityStreetNameNoData = { await waitFor(() => { expect(city.value).toBe(''); + expect(city).not.toBeDisabled(); expect(streetName.value).toBe(''); + expect(streetName).not.toBeDisabled(); }); }, }; 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.",