@defra/forms-engine-plugin 4.23.0 → 5.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.public/assets-manifest.json +3 -5
- package/.public/javascripts/im-core.min.js +2 -0
- package/.public/javascripts/im-core.min.js.map +1 -0
- package/.public/javascripts/shared.min.js +1 -1
- package/.public/javascripts/shared.min.js.map +1 -1
- package/.public/javascripts/vendor/@defra.min.js +1 -1
- package/.public/javascripts/vendor/@defra.min.js.map +1 -1
- package/.public/stylesheets/application.min.css +1 -1
- package/.public/stylesheets/application.min.css.map +1 -1
- package/.server/client/javascripts/geospatial-map.js +8 -6
- package/.server/client/javascripts/geospatial-map.js.map +1 -1
- package/.server/client/javascripts/location-map.js +8 -1
- package/.server/client/javascripts/location-map.js.map +1 -1
- package/.server/client/javascripts/map.d.ts +6 -0
- package/.server/client/javascripts/map.js +10 -0
- package/.server/client/javascripts/map.js.map +1 -1
- package/.server/client/javascripts/sssi-dataset.d.ts +34 -0
- package/.server/client/javascripts/sssi-dataset.js +101 -0
- package/.server/client/javascripts/sssi-dataset.js.map +1 -0
- package/.server/client/stylesheets/shared.scss +0 -2
- package/.server/server/forms/register-as-a-unicorn-breeder.yaml +0 -14
- package/.server/server/forms/simple-form.yaml +106 -3
- package/.server/server/plugins/engine/components/AutocompleteField.d.ts +11 -11
- package/.server/server/plugins/engine/components/AutocompleteField.js +2 -2
- package/.server/server/plugins/engine/components/AutocompleteField.js.map +1 -1
- package/.server/server/plugins/engine/components/CheckboxesField.d.ts +3 -2
- package/.server/server/plugins/engine/components/CheckboxesField.js +4 -9
- package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
- package/.server/server/plugins/engine/components/ComponentCollection.d.ts +8 -7
- package/.server/server/plugins/engine/components/ComponentCollection.js +106 -51
- package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
- package/.server/server/plugins/engine/components/DatePartsField.d.ts +8 -6
- package/.server/server/plugins/engine/components/DatePartsField.js +27 -9
- package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -1
- package/.server/server/plugins/engine/components/DeclarationField.d.ts +13 -11
- package/.server/server/plugins/engine/components/DeclarationField.js +31 -9
- package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -1
- package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +8 -5
- package/.server/server/plugins/engine/components/EastingNorthingField.js +42 -22
- package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
- package/.server/server/plugins/engine/components/EmailAddressField.d.ts +11 -14
- package/.server/server/plugins/engine/components/EmailAddressField.js +2 -2
- package/.server/server/plugins/engine/components/EmailAddressField.js.map +1 -1
- package/.server/server/plugins/engine/components/FileUploadField.d.ts +14 -12
- package/.server/server/plugins/engine/components/FileUploadField.js +22 -12
- package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
- package/.server/server/plugins/engine/components/FormComponent.d.ts +21 -12
- package/.server/server/plugins/engine/components/FormComponent.js +33 -14
- package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
- package/.server/server/plugins/engine/components/GeospatialField.d.ts +15 -12
- package/.server/server/plugins/engine/components/GeospatialField.js +21 -13
- package/.server/server/plugins/engine/components/GeospatialField.js.map +1 -1
- package/.server/server/plugins/engine/components/LatLongField.d.ts +8 -5
- package/.server/server/plugins/engine/components/LatLongField.js +32 -20
- package/.server/server/plugins/engine/components/LatLongField.js.map +1 -1
- package/.server/server/plugins/engine/components/ListFormComponent.d.ts +7 -6
- package/.server/server/plugins/engine/components/ListFormComponent.js +19 -7
- package/.server/server/plugins/engine/components/ListFormComponent.js.map +1 -1
- package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +17 -16
- package/.server/server/plugins/engine/components/LocationFieldBase.js +2 -2
- package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
- package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +8 -3
- package/.server/server/plugins/engine/components/LocationFieldHelpers.js +14 -3
- package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
- package/.server/server/plugins/engine/components/Markdown.d.ts +8 -7
- package/.server/server/plugins/engine/components/Markdown.js +5 -2
- package/.server/server/plugins/engine/components/Markdown.js.map +1 -1
- package/.server/server/plugins/engine/components/MonthYearField.d.ts +9 -7
- package/.server/server/plugins/engine/components/MonthYearField.js +26 -8
- package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -1
- package/.server/server/plugins/engine/components/MultilineTextField.d.ts +10 -9
- package/.server/server/plugins/engine/components/MultilineTextField.js +2 -2
- package/.server/server/plugins/engine/components/MultilineTextField.js.map +1 -1
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +2 -1
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +14 -6
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -1
- package/.server/server/plugins/engine/components/NumberField.d.ts +10 -9
- package/.server/server/plugins/engine/components/NumberField.js +2 -2
- package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
- package/.server/server/plugins/engine/components/OsGridRefField.d.ts +2 -1
- package/.server/server/plugins/engine/components/OsGridRefField.js +14 -6
- package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -1
- package/.server/server/plugins/engine/components/PaymentField.d.ts +12 -10
- package/.server/server/plugins/engine/components/PaymentField.js +21 -8
- package/.server/server/plugins/engine/components/PaymentField.js.map +1 -1
- package/.server/server/plugins/engine/components/SelectField.d.ts +11 -11
- package/.server/server/plugins/engine/components/SelectField.js +2 -2
- package/.server/server/plugins/engine/components/SelectField.js.map +1 -1
- package/.server/server/plugins/engine/components/SelectionControlField.d.ts +4 -4
- package/.server/server/plugins/engine/components/SelectionControlField.js +2 -2
- package/.server/server/plugins/engine/components/SelectionControlField.js.map +1 -1
- package/.server/server/plugins/engine/components/TelephoneNumberField.d.ts +10 -9
- package/.server/server/plugins/engine/components/TelephoneNumberField.js +2 -2
- package/.server/server/plugins/engine/components/TelephoneNumberField.js.map +1 -1
- package/.server/server/plugins/engine/components/UkAddressField.d.ts +12 -10
- package/.server/server/plugins/engine/components/UkAddressField.js +29 -17
- package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
- package/.server/server/plugins/engine/components/YesNoField.d.ts +3 -0
- package/.server/server/plugins/engine/components/YesNoField.js +9 -0
- package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
- package/.server/server/plugins/engine/components/helpers/components.d.ts +3 -2
- package/.server/server/plugins/engine/components/helpers/components.js +11 -8
- package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
- package/.server/server/plugins/engine/components/types.d.ts +8 -1
- package/.server/server/plugins/engine/components/types.js.map +1 -1
- package/.server/server/plugins/engine/configureEnginePlugin.js +8 -1
- package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
- package/.server/server/plugins/engine/form-context.d.ts +1 -0
- package/.server/server/plugins/engine/form-context.js +4 -2
- package/.server/server/plugins/engine/form-context.js.map +1 -1
- package/.server/server/plugins/engine/helpers.d.ts +1 -1
- package/.server/server/plugins/engine/helpers.js +8 -13
- package/.server/server/plugins/engine/helpers.js.map +1 -1
- package/.server/server/plugins/engine/i18n/buildValidationMessages.d.ts +39 -0
- package/.server/server/plugins/engine/i18n/buildValidationMessages.js +50 -0
- package/.server/server/plugins/engine/i18n/buildValidationMessages.js.map +1 -0
- package/.server/server/plugins/engine/i18n/createFormTranslator.d.ts +5 -0
- package/.server/server/plugins/engine/i18n/createFormTranslator.js +18 -0
- package/.server/server/plugins/engine/i18n/createFormTranslator.js.map +1 -0
- package/.server/server/plugins/engine/i18n/createTranslator.d.ts +9 -0
- package/.server/server/plugins/engine/i18n/createTranslator.js +51 -0
- package/.server/server/plugins/engine/i18n/createTranslator.js.map +1 -0
- package/.server/server/plugins/engine/i18n/extractBaseTranslations.d.ts +5 -0
- package/.server/server/plugins/engine/i18n/extractBaseTranslations.js +57 -0
- package/.server/server/plugins/engine/i18n/extractBaseTranslations.js.map +1 -0
- package/.server/server/plugins/engine/i18n/index.d.ts +18 -0
- package/.server/server/plugins/engine/i18n/index.js +71 -0
- package/.server/server/plugins/engine/i18n/index.js.map +1 -0
- package/.server/server/plugins/engine/i18n/translations/cy.json +271 -0
- package/.server/server/plugins/engine/i18n/translations/en-GB.json +271 -0
- package/.server/server/plugins/engine/i18n/types.d.ts +41 -0
- package/.server/server/plugins/engine/i18n/types.js +2 -0
- package/.server/server/plugins/engine/i18n/types.js.map +1 -0
- package/.server/server/plugins/engine/index.js +3 -2
- package/.server/server/plugins/engine/index.js.map +1 -1
- package/.server/server/plugins/engine/models/FormModel.d.ts +9 -1
- package/.server/server/plugins/engine/models/FormModel.js +44 -18
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +5 -2
- package/.server/server/plugins/engine/models/SummaryViewModel.js +44 -18
- package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
- package/.server/server/plugins/engine/options.js +2 -1
- package/.server/server/plugins/engine/options.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +4 -0
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js +2 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v1.js +7 -6
- package/.server/server/plugins/engine/outputFormatters/machine/v1.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +2 -1
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +6 -3
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/PageController.d.ts +3 -1
- package/.server/server/plugins/engine/pageControllers/PageController.js +11 -1
- package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +4 -2
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +52 -21
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/RepeatPageController.d.ts +3 -2
- package/.server/server/plugins/engine/pageControllers/RepeatPageController.js +63 -26
- package/.server/server/plugins/engine/pageControllers/RepeatPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/StartPageController.d.ts +3 -1
- package/.server/server/plugins/engine/pageControllers/StartPageController.js +2 -2
- package/.server/server/plugins/engine/pageControllers/StartPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/StatusPageController.js +12 -2
- package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +4 -3
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +44 -28
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/__stubs__/translator.d.ts +2 -0
- package/.server/server/plugins/engine/pageControllers/__stubs__/translator.js +24 -0
- package/.server/server/plugins/engine/pageControllers/__stubs__/translator.js.map +1 -0
- package/.server/server/plugins/engine/pageControllers/errors.d.ts +1 -1
- package/.server/server/plugins/engine/pageControllers/errors.js +3 -2
- package/.server/server/plugins/engine/pageControllers/errors.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/helpers/submission.d.ts +3 -2
- package/.server/server/plugins/engine/pageControllers/helpers/submission.js +4 -4
- package/.server/server/plugins/engine/pageControllers/helpers/submission.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/validationOptions.d.ts +7 -3
- package/.server/server/plugins/engine/pageControllers/validationOptions.js +34 -33
- package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
- package/.server/server/plugins/engine/plugin.js +2 -0
- package/.server/server/plugins/engine/plugin.js.map +1 -1
- package/.server/server/plugins/engine/routes/index.js +4 -1
- package/.server/server/plugins/engine/routes/index.js.map +1 -1
- package/.server/server/plugins/engine/routes/questions.js +5 -2
- package/.server/server/plugins/engine/routes/questions.js.map +1 -1
- package/.server/server/plugins/engine/types/index.d.ts +1 -0
- package/.server/server/plugins/engine/types/index.js.map +1 -1
- package/.server/server/plugins/engine/types/schema.js +1 -0
- package/.server/server/plugins/engine/types/schema.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +12 -1
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/views/components/_location-field-base.html +4 -2
- package/.server/server/plugins/engine/views/components/fileuploadfield.html +3 -3
- package/.server/server/plugins/engine/views/components/geospatialfield.html +4 -1
- package/.server/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +1 -1
- package/.server/server/plugins/engine/views/components/osgridreffield.html +1 -1
- package/.server/server/plugins/engine/views/components/paymentfield.html +8 -8
- package/.server/server/plugins/engine/views/components/ukaddressfield.html +4 -4
- package/.server/server/plugins/engine/views/confirmation.html +4 -4
- package/.server/server/plugins/engine/views/exit.html +4 -4
- package/.server/server/plugins/engine/views/file-upload.html +3 -3
- package/.server/server/plugins/engine/views/index.html +3 -3
- package/.server/server/plugins/engine/views/item-delete.html +1 -1
- package/.server/server/plugins/engine/views/partials/form.html +3 -3
- package/.server/server/plugins/engine/views/partials/preview-banner.html +4 -4
- package/.server/server/plugins/engine/views/partials/warn-missing-notification-email.html +4 -5
- package/.server/server/plugins/engine/views/repeat-list-summary.html +4 -4
- package/.server/server/plugins/engine/views/summary.html +14 -7
- package/.server/server/plugins/map/routes/index.js +1 -1
- package/.server/server/plugins/map/routes/index.js.map +1 -1
- package/.server/server/plugins/map/types.d.ts +1 -25
- package/.server/server/plugins/map/types.js +1 -16
- package/.server/server/plugins/map/types.js.map +1 -1
- package/.server/server/plugins/nunjucks/enviroment.test.js +10 -0
- package/.server/server/plugins/nunjucks/enviroment.test.js.map +1 -1
- package/.server/server/plugins/nunjucks/environment.d.ts +8 -0
- package/.server/server/plugins/nunjucks/environment.js +13 -0
- package/.server/server/plugins/nunjucks/environment.js.map +1 -1
- package/.server/server/plugins/nunjucks/filters/answer.js +9 -1
- package/.server/server/plugins/nunjucks/filters/answer.js.map +1 -1
- package/.server/server/plugins/nunjucks/filters/answer.test.js +13 -2
- package/.server/server/plugins/nunjucks/filters/answer.test.js.map +1 -1
- package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
- package/.server/server/plugins/postcode-lookup/models/index.d.ts +24 -19
- package/.server/server/plugins/postcode-lookup/models/index.js +83 -76
- package/.server/server/plugins/postcode-lookup/models/index.js.map +1 -1
- package/.server/server/plugins/postcode-lookup/routes/index.js +7 -13
- package/.server/server/plugins/postcode-lookup/routes/index.js.map +1 -1
- package/.server/server/plugins/postcode-lookup/types.d.ts +1 -0
- package/.server/server/plugins/postcode-lookup/types.js +1 -0
- package/.server/server/plugins/postcode-lookup/types.js.map +1 -1
- package/.server/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
- package/.server/server/types.d.ts +2 -1
- package/.server/server/types.js.map +1 -1
- package/.server/server/utils/utils.d.ts +5 -0
- package/.server/server/utils/utils.js +16 -0
- package/.server/server/utils/utils.js.map +1 -1
- package/.server/typings/hapi/index.d.js.map +1 -1
- package/package.json +5 -4
- package/src/client/javascripts/geospatial-map.js +10 -6
- package/src/client/javascripts/location-map.js +11 -0
- package/src/client/javascripts/map.js +10 -0
- package/src/client/javascripts/sssi-dataset.js +106 -0
- package/src/client/stylesheets/shared.scss +0 -2
- package/src/server/forms/register-as-a-unicorn-breeder.yaml +0 -14
- package/src/server/forms/simple-form.yaml +106 -3
- package/src/server/plugins/engine/components/AutocompleteField.ts +3 -6
- package/src/server/plugins/engine/components/CheckboxesField.ts +12 -11
- package/src/server/plugins/engine/components/ComponentCollection.ts +143 -61
- package/src/server/plugins/engine/components/DatePartsField.ts +33 -11
- package/src/server/plugins/engine/components/DeclarationField.ts +37 -14
- package/src/server/plugins/engine/components/EastingNorthingField.ts +83 -22
- package/src/server/plugins/engine/components/EmailAddressField.ts +4 -7
- package/src/server/plugins/engine/components/FileUploadField.ts +28 -22
- package/src/server/plugins/engine/components/FormComponent.ts +49 -12
- package/src/server/plugins/engine/components/GeospatialField.ts +38 -21
- package/src/server/plugins/engine/components/LatLongField.ts +52 -21
- package/src/server/plugins/engine/components/ListFormComponent.ts +26 -10
- package/src/server/plugins/engine/components/LocationFieldBase.ts +5 -8
- package/src/server/plugins/engine/components/LocationFieldHelpers.ts +24 -4
- package/src/server/plugins/engine/components/Markdown.ts +7 -3
- package/src/server/plugins/engine/components/MonthYearField.ts +32 -10
- package/src/server/plugins/engine/components/MultilineTextField.ts +5 -8
- package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +25 -11
- package/src/server/plugins/engine/components/NumberField.ts +3 -4
- package/src/server/plugins/engine/components/OsGridRefField.ts +25 -9
- package/src/server/plugins/engine/components/PaymentField.ts +22 -12
- package/src/server/plugins/engine/components/SelectField.ts +3 -6
- package/src/server/plugins/engine/components/SelectionControlField.ts +7 -8
- package/src/server/plugins/engine/components/TelephoneNumberField.ts +4 -7
- package/src/server/plugins/engine/components/UkAddressField.ts +33 -18
- package/src/server/plugins/engine/components/YesNoField.ts +10 -0
- package/src/server/plugins/engine/components/helpers/components.ts +13 -6
- package/src/server/plugins/engine/components/types.ts +9 -0
- package/src/server/plugins/engine/configureEnginePlugin.ts +12 -1
- package/src/server/plugins/engine/form-context.ts +8 -2
- package/src/server/plugins/engine/helpers.ts +10 -18
- package/src/server/plugins/engine/i18n/buildValidationMessages.ts +69 -0
- package/src/server/plugins/engine/i18n/createFormTranslator.ts +33 -0
- package/src/server/plugins/engine/i18n/createTranslator.ts +61 -0
- package/src/server/plugins/engine/i18n/extractBaseTranslations.ts +61 -0
- package/src/server/plugins/engine/i18n/index.ts +80 -0
- package/src/server/plugins/engine/i18n/translations/cy.json +271 -0
- package/src/server/plugins/engine/i18n/translations/en-GB.json +271 -0
- package/src/server/plugins/engine/i18n/types.ts +62 -0
- package/src/server/plugins/engine/index.ts +4 -2
- package/src/server/plugins/engine/models/FormModel.ts +59 -23
- package/src/server/plugins/engine/models/SummaryViewModel.ts +68 -25
- package/src/server/plugins/engine/options.js +2 -1
- package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +5 -0
- package/src/server/plugins/engine/outputFormatters/human/v1.ts +2 -3
- package/src/server/plugins/engine/outputFormatters/machine/v1.ts +9 -8
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +7 -3
- package/src/server/plugins/engine/pageControllers/PageController.ts +12 -1
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +53 -21
- package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +66 -29
- package/src/server/plugins/engine/pageControllers/StartPageController.ts +7 -2
- package/src/server/plugins/engine/pageControllers/StatusPageController.ts +10 -2
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +48 -22
- package/src/server/plugins/engine/pageControllers/__stubs__/translator.ts +28 -0
- package/src/server/plugins/engine/pageControllers/errors.ts +4 -2
- package/src/server/plugins/engine/pageControllers/helpers/submission.ts +11 -4
- package/src/server/plugins/engine/pageControllers/validationOptions.ts +43 -53
- package/src/server/plugins/engine/plugin.ts +2 -0
- package/src/server/plugins/engine/routes/index.ts +9 -1
- package/src/server/plugins/engine/routes/questions.ts +5 -1
- package/src/server/plugins/engine/types/index.ts +5 -0
- package/src/server/plugins/engine/types/schema.ts +1 -0
- package/src/server/plugins/engine/types.ts +10 -0
- package/src/server/plugins/engine/views/components/_location-field-base.html +4 -2
- package/src/server/plugins/engine/views/components/fileuploadfield.html +3 -3
- package/src/server/plugins/engine/views/components/geospatialfield.html +4 -1
- package/src/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +1 -1
- package/src/server/plugins/engine/views/components/osgridreffield.html +1 -1
- package/src/server/plugins/engine/views/components/paymentfield.html +8 -8
- package/src/server/plugins/engine/views/components/ukaddressfield.html +4 -4
- package/src/server/plugins/engine/views/confirmation.html +4 -4
- package/src/server/plugins/engine/views/exit.html +4 -4
- package/src/server/plugins/engine/views/file-upload.html +3 -3
- package/src/server/plugins/engine/views/index.html +3 -3
- package/src/server/plugins/engine/views/item-delete.html +1 -1
- package/src/server/plugins/engine/views/partials/form.html +3 -3
- package/src/server/plugins/engine/views/partials/preview-banner.html +4 -4
- package/src/server/plugins/engine/views/partials/warn-missing-notification-email.html +4 -5
- package/src/server/plugins/engine/views/repeat-list-summary.html +4 -4
- package/src/server/plugins/engine/views/summary.html +14 -7
- package/src/server/plugins/map/routes/index.js +1 -1
- package/src/server/plugins/map/types.js +1 -16
- package/src/server/plugins/nunjucks/environment.js +14 -0
- package/src/server/plugins/nunjucks/filters/answer.js +12 -1
- package/src/server/plugins/postcode-lookup/models/index.js +144 -87
- package/src/server/plugins/postcode-lookup/routes/index.js +13 -16
- package/src/server/plugins/postcode-lookup/types.js +1 -0
- package/src/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
- package/src/server/types.ts +2 -0
- package/src/server/utils/utils.js +14 -0
- package/src/typings/hapi/index.d.ts +1 -0
- package/.public/javascripts/im-search-plugin.min.js +0 -2
- package/.public/javascripts/im-search-plugin.min.js.map +0 -1
- package/.public/javascripts/vendor/preact.min.js +0 -2
- package/.public/javascripts/vendor/preact.min.js.map +0 -1
- package/.server/server/plugins/engine/components/constants.d.ts +0 -1
- package/.server/server/plugins/engine/components/constants.js +0 -2
- package/.server/server/plugins/engine/components/constants.js.map +0 -1
- package/src/server/plugins/engine/components/constants.ts +0 -1
|
@@ -3,8 +3,9 @@ import { getAnswer } from "../../components/helpers/components.js";
|
|
|
3
3
|
import { FileUploadField } from "../../components/index.js";
|
|
4
4
|
const designerUrl = config.get('designerUrl');
|
|
5
5
|
export function format(context, items, model, _submitResponse, _formStatus, _formMetadata) {
|
|
6
|
+
const translator = model.createTranslator();
|
|
6
7
|
const now = new Date();
|
|
7
|
-
const categorisedData = categoriseData(items);
|
|
8
|
+
const categorisedData = categoriseData(items, translator);
|
|
8
9
|
const data = {
|
|
9
10
|
meta: {
|
|
10
11
|
schemaVersion: '1',
|
|
@@ -42,7 +43,7 @@ export function format(context, items, model, _submitResponse, _formStatus, _for
|
|
|
42
43
|
* }
|
|
43
44
|
* }
|
|
44
45
|
*/
|
|
45
|
-
function categoriseData(items) {
|
|
46
|
+
function categoriseData(items, translator) {
|
|
46
47
|
const output = {
|
|
47
48
|
main: {},
|
|
48
49
|
repeaters: {},
|
|
@@ -50,11 +51,11 @@ function categoriseData(items) {
|
|
|
50
51
|
};
|
|
51
52
|
items.forEach(item => {
|
|
52
53
|
if ('subItems' in item) {
|
|
53
|
-
output.repeaters[item.name] = extractRepeaters(item);
|
|
54
|
+
output.repeaters[item.name] = extractRepeaters(item, translator);
|
|
54
55
|
} else if (isFileUploadFieldItem(item)) {
|
|
55
56
|
output.files[item.name] = extractFileUploads(item);
|
|
56
57
|
} else {
|
|
57
|
-
output.main[item.name] = getAnswer(item.field, item.state, {
|
|
58
|
+
output.main[item.name] = getAnswer(item.field, item.state, translator, {
|
|
58
59
|
format: 'data'
|
|
59
60
|
});
|
|
60
61
|
}
|
|
@@ -67,12 +68,12 @@ function categoriseData(items) {
|
|
|
67
68
|
* @param item - the repeater item
|
|
68
69
|
* @returns the repeater item
|
|
69
70
|
*/
|
|
70
|
-
function extractRepeaters(item) {
|
|
71
|
+
function extractRepeaters(item, translator) {
|
|
71
72
|
const repeaters = [];
|
|
72
73
|
item.subItems.forEach(inputRepeaterItem => {
|
|
73
74
|
const outputRepeaterItem = {};
|
|
74
75
|
inputRepeaterItem.forEach(repeaterComponent => {
|
|
75
|
-
outputRepeaterItem[repeaterComponent.name] = getAnswer(repeaterComponent.field, repeaterComponent.state, {
|
|
76
|
+
outputRepeaterItem[repeaterComponent.name] = getAnswer(repeaterComponent.field, repeaterComponent.state, translator, {
|
|
76
77
|
format: 'data'
|
|
77
78
|
});
|
|
78
79
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v1.js","names":["config","getAnswer","FileUploadField","designerUrl","get","format","context","items","model","_submitResponse","_formStatus","_formMetadata","now","Date","categorisedData","categoriseData","data","meta","schemaVersion","timestamp","toISOString","referenceNumber","definition","def","body","JSON","stringify","output","main","repeaters","files","forEach","item","name","extractRepeaters","isFileUploadFieldItem","extractFileUploads","field","state","subItems","inputRepeaterItem","outputRepeaterItem","repeaterComponent","push","fileUploadState","getContextValueFromState","map","fileId","userDownloadLink"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/machine/v1.ts"],"sourcesContent":["import {\n type FormMetadata,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { config } from '~/src/config/index.js'\nimport { getAnswer } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport { type FormContext } from '~/src/server/plugins/engine/types.js'\n\nconst designerUrl = config.get('designerUrl')\n\nexport function format(\n context: FormContext,\n items: DetailItem[],\n model: FormModel,\n _submitResponse: SubmitResponsePayload,\n _formStatus: ReturnType<typeof checkFormStatus>,\n _formMetadata?: FormMetadata\n) {\n const now = new Date()\n\n const categorisedData = categoriseData(items)\n\n const data = {\n meta: {\n schemaVersion: '1',\n timestamp: now.toISOString(),\n referenceNumber: context.referenceNumber,\n definition: model.def\n },\n data: categorisedData\n }\n\n const body = JSON.stringify(data)\n\n return body\n}\n\n/**\n * Categories the form submission data into the \"main\" body and \"repeaters\".\n *\n * {\n * main: {\n * componentName: 'componentValue',\n * },\n * repeaters: {\n * repeaterName: [\n * {\n * componentName: 'componentValue'\n * }\n * ]\n * },\n * files: {\n * fileComponentName: [\n * {\n * fileId: '123-456-789',\n * link: 'https://forms-designer/file-download/123-456-789'\n * }\n * ]\n * }\n * }\n */\nfunction categoriseData(items: DetailItem[]) {\n const output: {\n main: Record<string, string>\n repeaters: Record<string, Record<string, string>[]>\n files: Record<string, Record<string, string>[]>\n } = { main: {}, repeaters: {}, files: {} }\n\n items.forEach((item) => {\n if ('subItems' in item) {\n output.repeaters[item.name] = extractRepeaters(item)\n } else if (isFileUploadFieldItem(item)) {\n output.files[item.name] = extractFileUploads(item)\n } else {\n output.main[item.name] = getAnswer(item.field, item.state, {\n format: 'data'\n })\n }\n })\n\n return output\n}\n\n/**\n * Returns the \"repeaters\" section of the response body\n * @param item - the repeater item\n * @returns the repeater item\n */\nfunction extractRepeaters(item: DetailItemRepeat) {\n const repeaters: Record<string, string>[] = []\n\n item.subItems.forEach((inputRepeaterItem) => {\n const outputRepeaterItem: Record<string, string> = {}\n\n inputRepeaterItem.forEach((repeaterComponent) => {\n outputRepeaterItem[repeaterComponent.name] = getAnswer(\n repeaterComponent.field,\n repeaterComponent.state,\n {
|
|
1
|
+
{"version":3,"file":"v1.js","names":["config","getAnswer","FileUploadField","designerUrl","get","format","context","items","model","_submitResponse","_formStatus","_formMetadata","translator","createTranslator","now","Date","categorisedData","categoriseData","data","meta","schemaVersion","timestamp","toISOString","referenceNumber","definition","def","body","JSON","stringify","output","main","repeaters","files","forEach","item","name","extractRepeaters","isFileUploadFieldItem","extractFileUploads","field","state","subItems","inputRepeaterItem","outputRepeaterItem","repeaterComponent","push","fileUploadState","getContextValueFromState","map","fileId","userDownloadLink"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/machine/v1.ts"],"sourcesContent":["import {\n type FormMetadata,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { config } from '~/src/config/index.js'\nimport { getAnswer } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport { type FormContext } from '~/src/server/plugins/engine/types.js'\n\nconst designerUrl = config.get('designerUrl')\n\nexport function format(\n context: FormContext,\n items: DetailItem[],\n model: FormModel,\n _submitResponse: SubmitResponsePayload,\n _formStatus: ReturnType<typeof checkFormStatus>,\n _formMetadata?: FormMetadata\n) {\n const translator = model.createTranslator()\n const now = new Date()\n\n const categorisedData = categoriseData(items, translator)\n\n const data = {\n meta: {\n schemaVersion: '1',\n timestamp: now.toISOString(),\n referenceNumber: context.referenceNumber,\n definition: model.def\n },\n data: categorisedData\n }\n\n const body = JSON.stringify(data)\n\n return body\n}\n\n/**\n * Categories the form submission data into the \"main\" body and \"repeaters\".\n *\n * {\n * main: {\n * componentName: 'componentValue',\n * },\n * repeaters: {\n * repeaterName: [\n * {\n * componentName: 'componentValue'\n * }\n * ]\n * },\n * files: {\n * fileComponentName: [\n * {\n * fileId: '123-456-789',\n * link: 'https://forms-designer/file-download/123-456-789'\n * }\n * ]\n * }\n * }\n */\nfunction categoriseData(items: DetailItem[], translator: Translator) {\n const output: {\n main: Record<string, string>\n repeaters: Record<string, Record<string, string>[]>\n files: Record<string, Record<string, string>[]>\n } = { main: {}, repeaters: {}, files: {} }\n\n items.forEach((item) => {\n if ('subItems' in item) {\n output.repeaters[item.name] = extractRepeaters(item, translator)\n } else if (isFileUploadFieldItem(item)) {\n output.files[item.name] = extractFileUploads(item)\n } else {\n output.main[item.name] = getAnswer(item.field, item.state, translator, {\n format: 'data'\n })\n }\n })\n\n return output\n}\n\n/**\n * Returns the \"repeaters\" section of the response body\n * @param item - the repeater item\n * @returns the repeater item\n */\nfunction extractRepeaters(item: DetailItemRepeat, translator: Translator) {\n const repeaters: Record<string, string>[] = []\n\n item.subItems.forEach((inputRepeaterItem) => {\n const outputRepeaterItem: Record<string, string> = {}\n\n inputRepeaterItem.forEach((repeaterComponent) => {\n outputRepeaterItem[repeaterComponent.name] = getAnswer(\n repeaterComponent.field,\n repeaterComponent.state,\n translator,\n { format: 'data' }\n )\n })\n\n repeaters.push(outputRepeaterItem)\n })\n\n return repeaters\n}\n\n/**\n * Returns the \"files\" section of the response body\n * @param item - the file upload item in the form\n * @returns the file upload data\n */\nfunction extractFileUploads(item: FileUploadFieldDetailitem) {\n const fileUploadState = item.field.getContextValueFromState(item.state) ?? []\n\n return fileUploadState.map((fileId) => {\n return {\n fileId,\n userDownloadLink: `${designerUrl}/file-download/${fileId}`\n }\n })\n}\n\nfunction isFileUploadFieldItem(\n item: DetailItemField\n): item is FileUploadFieldDetailitem {\n return item.field instanceof FileUploadField\n}\n\n/**\n * A detail item specifically for files\n */\ntype FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {\n field: FileUploadField\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -2,6 +2,7 @@ import { type PageFileUpload } from '@defra/forms-model';
|
|
|
2
2
|
import { type ValidationErrorItem } from 'joi';
|
|
3
3
|
import { FileUploadField } from '../../../../server/plugins/engine/components/FileUploadField.js';
|
|
4
4
|
import { type FormComponent } from '../../../../server/plugins/engine/components/FormComponent.js';
|
|
5
|
+
import { type Translator } from '../../../../server/plugins/engine/i18n/types.js';
|
|
5
6
|
import { type FormModel } from '../../../../server/plugins/engine/models/index.js';
|
|
6
7
|
import { QuestionPageController } from '../../../../server/plugins/engine/pageControllers/QuestionPageController.js';
|
|
7
8
|
import { type AnyFormRequest, type FeaturedFormPageViewModel, type FormContext, type FormContextRequest, type FormSubmissionError, type FormSubmissionState, type UploadInitiateResponse, type UploadStatusFileResponse } from '../../../../server/plugins/engine/types.js';
|
|
@@ -33,7 +34,7 @@ export declare class FileUploadPageController extends QuestionPageController {
|
|
|
33
34
|
makeGetItemDeleteRouteHandler(): (request: FormRequest, context: FormContext, h: FormResponseToolkit) => import("@hapi/hapi").ResponseObject;
|
|
34
35
|
makePostItemDeleteRouteHandler(): (request: FormRequestPayload, context: FormContext, h: FormResponseToolkit) => Promise<import("@hapi/hapi").ResponseObject>;
|
|
35
36
|
getErrors(details?: ValidationErrorItem[]): FormSubmissionError[] | undefined;
|
|
36
|
-
getViewModel(request: FormContextRequest, context: FormContext): FeaturedFormPageViewModel;
|
|
37
|
+
getViewModel(request: FormContextRequest, context: FormContext, translator: Translator): FeaturedFormPageViewModel;
|
|
37
38
|
/**
|
|
38
39
|
* Refreshes the CDP upload and files in the
|
|
39
40
|
* state and checks for any removed files.
|
|
@@ -133,10 +133,13 @@ export class FileUploadPageController extends QuestionPageController {
|
|
|
133
133
|
const {
|
|
134
134
|
filename
|
|
135
135
|
} = fileToRemove.status.form.file;
|
|
136
|
+
const {
|
|
137
|
+
t
|
|
138
|
+
} = this.getTranslator(request);
|
|
136
139
|
return h.view(this.fileDeleteViewName, {
|
|
137
140
|
...viewModel,
|
|
138
141
|
context,
|
|
139
|
-
backLink: this.getBackLink(request, context),
|
|
142
|
+
backLink: this.getBackLink(request, context, t),
|
|
140
143
|
pageTitle: `Are you sure you want to remove this file?`,
|
|
141
144
|
itemTitle: filename,
|
|
142
145
|
confirmation: {
|
|
@@ -209,7 +212,7 @@ export class FileUploadPageController extends QuestionPageController {
|
|
|
209
212
|
return errors;
|
|
210
213
|
}
|
|
211
214
|
}
|
|
212
|
-
getViewModel(request, context) {
|
|
215
|
+
getViewModel(request, context, translator) {
|
|
213
216
|
const {
|
|
214
217
|
fileUpload
|
|
215
218
|
} = this;
|
|
@@ -217,7 +220,7 @@ export class FileUploadPageController extends QuestionPageController {
|
|
|
217
220
|
state
|
|
218
221
|
} = context;
|
|
219
222
|
const upload = this.getUploadFromState(state);
|
|
220
|
-
const viewModel = super.getViewModel(request, context);
|
|
223
|
+
const viewModel = super.getViewModel(request, context, translator);
|
|
221
224
|
const {
|
|
222
225
|
components
|
|
223
226
|
} = viewModel;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileUploadPageController.js","names":["ComponentType","Boom","wait","StatusCodes","FileUploadField","tempItemSchema","getCacheService","getError","getExponentialBackoffDelay","QuestionPageController","getProxyUrlForLocalDevelopment","getUploadStatus","initiateUpload","FileStatus","UploadStatus","MAX_UPLOADS","CDP_UPLOAD_TIMEOUT_MS","prepareStatus","status","file","form","isPending","fileStatus","pending","errorMessage","prepareFileState","fileState","FileUploadPageController","fileUpload","fileDeleteViewName","constructor","model","pageDef","collection","fileUploads","fields","filter","field","type","at","length","badImplementation","path","indexOf","name","viewName","getStateKeys","component","pagePath","page","getFormDataFromState","request","state","payload","files","getFilesFromState","undefined","getState","refreshUpload","uploadState","upload","getUploadFromState","makeGetItemDeleteRouteHandler","context","h","viewModel","params","fileToRemove","find","fileId","itemId","notFound","filename","view","backLink","getBackLink","pageTitle","itemTitle","confirmation","text","buttonConfirm","buttonCancel","makePostItemDeleteRouteHandler","confirm","getFormParams","checkRemovedFiles","proceed","getErrors","details","errors","forEach","error","isUploadError","isUploadRootError","push","value","href","getViewModel","components","formComponent","id","index","proxyUrl","uploadUrl","formAction","uploadId","componentsBefore","slice","checkUploadStatus","depth","initiateAndStoreNewUpload","statusResponse","err","isBoom","output","statusCode","NOT_FOUND","valueOf","badRequest","uploadStatus","initiated","Error","logger","gatewayTimeout","toFixed","delay","info","validationResult","validate","stripUnknown","processUploadedFiles","validatedItem","validatedStatus","rawFile","uploadedFiles","Array","isArray","allErrors","complete","perFileState","unshift","cacheService","server","setFlash","some","f","mergeState","filesUpdated","options","schema","getFormMetadata","services","formsService","max","Math","min","formMetadata","slug","notificationEmail","newUpload","accept"],"sources":["../../../../../src/server/plugins/engine/pageControllers/FileUploadPageController.ts"],"sourcesContent":["import { ComponentType, type PageFileUpload } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { wait } from '@hapi/hoek'\nimport { StatusCodes } from 'http-status-codes'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n FileUploadField,\n tempItemSchema\n} from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport { type FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n getCacheService,\n getError,\n getExponentialBackoffDelay\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { getProxyUrlForLocalDevelopment } from '~/src/server/plugins/engine/pageControllers/helpers/index.js'\nimport {\n getUploadStatus,\n initiateUpload\n} from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n FileStatus,\n UploadStatus,\n type AnyFormRequest,\n type FeaturedFormPageViewModel,\n type FileState,\n type FileUpload,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError,\n type FormSubmissionState,\n type ItemDeletePageViewModel,\n type UploadInitiateResponse,\n type UploadStatusFileResponse\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nconst MAX_UPLOADS = 25\nconst CDP_UPLOAD_TIMEOUT_MS = 60000 // 1 minute\n\nexport function prepareStatus(status: UploadStatusFileResponse) {\n const file = status.form.file\n const isPending = file.fileStatus === FileStatus.pending\n\n if (!file.errorMessage && isPending) {\n file.errorMessage = 'The selected file has not fully uploaded'\n }\n\n return status\n}\n\nfunction prepareFileState(fileState: FileState) {\n prepareStatus(fileState.status)\n\n return fileState\n}\n\nexport class FileUploadPageController extends QuestionPageController {\n declare pageDef: PageFileUpload\n\n fileUpload: FileUploadField\n fileDeleteViewName = 'item-delete'\n\n constructor(model: FormModel, pageDef: PageFileUpload) {\n super(model, pageDef)\n\n const { collection } = this\n\n // Get the file upload fields from the collection\n const fileUploads = collection.fields.filter(\n (field): field is FileUploadField =>\n field.type === ComponentType.FileUploadField\n )\n\n const fileUpload = fileUploads.at(0)\n\n // Assert we have exactly 1 file upload component\n if (!fileUpload || fileUploads.length > 1) {\n throw Boom.badImplementation(\n `Expected 1 FileUploadFieldComponent in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assert the file upload component is the first form component\n if (collection.fields.indexOf(fileUpload) !== 0) {\n throw Boom.badImplementation(\n `Expected '${fileUpload.name}' to be the first form component in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assign the file upload component to the controller\n this.fileUpload = fileUpload\n this.viewName = 'file-upload'\n }\n\n /**\n * Get supplementary state keys for clearing file upload state.\n * Returns the nested upload path for FileUploadField components only.\n * @param component - The component to get supplementary state keys for\n * @returns Array containing the nested upload path, e.g., [\"upload['/page-path']\"]\n * or ['upload'] if no page path is available. Returns empty array for non-FileUploadField components.\n */\n getStateKeys(component: FormComponent): string[] {\n // Only return upload keys for FileUploadField components\n if (!(component instanceof FileUploadField)) {\n return []\n }\n\n const pagePath = component.page?.path\n return pagePath ? [`upload['${pagePath}']`] : ['upload']\n }\n\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ) {\n const { fileUpload } = this\n\n const payload = super.getFormDataFromState(request, state)\n const files = this.getFilesFromState(state)\n\n // Append the files to the payload\n payload[fileUpload.name] = files.length ? files : undefined\n\n return payload\n }\n\n async getState(request: AnyFormRequest) {\n const { fileUpload } = this\n\n // Get the actual state\n const state = await super.getState(request)\n const files = this.getFilesFromState(state)\n\n // Overwrite the files with those in the upload state\n state[fileUpload.name] = files\n\n return this.refreshUpload(request, state)\n }\n\n /**\n * Get the uploaded files from state.\n */\n getFilesFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.files ?? []\n }\n\n /**\n * Get the initiated upload from state.\n */\n getUploadFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.upload\n }\n\n makeGetItemDeleteRouteHandler() {\n return (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewModel } = this\n const { params } = request\n const { state } = context\n\n const files = this.getFilesFromState(state)\n\n const fileToRemove = files.find(\n ({ status }) => status.form.file.fileId === params.itemId\n )\n\n if (!fileToRemove) {\n throw Boom.notFound('File to delete not found')\n }\n\n const { filename } = fileToRemove.status.form.file\n\n return h.view(this.fileDeleteViewName, {\n ...viewModel,\n context,\n backLink: this.getBackLink(request, context),\n pageTitle: `Are you sure you want to remove this file?`,\n itemTitle: filename,\n confirmation: { text: 'You cannot recover removed files.' },\n buttonConfirm: { text: 'Remove file' },\n buttonCancel: { text: 'Cancel' }\n } satisfies ItemDeletePageViewModel)\n }\n }\n\n makePostItemDeleteRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { path } = this\n const { state } = context\n\n const { confirm } = this.getFormParams(request)\n\n // Check for any removed files in the POST payload\n if (confirm) {\n await this.checkRemovedFiles(request, state)\n return this.proceed(request, h, path)\n }\n\n return this.proceed(request, h)\n }\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n const { fileUpload } = this\n\n if (details) {\n const errors: FormSubmissionError[] = []\n\n details.forEach((error) => {\n const isUploadError = error.path[0] === fileUpload.name\n const isUploadRootError = isUploadError && error.path.length === 1\n\n if (!isUploadError || isUploadRootError) {\n // The error is for the root of the upload or another\n // field on the page so defer to the getError helper\n errors.push(getError(error))\n } else {\n const { context, path, type } = error\n\n if (type === 'object.unknown' && path.at(-1) === 'errorMessage') {\n const value = context?.value as string | undefined\n\n if (value) {\n const name = fileUpload.name\n const text = typeof value === 'string' ? value : 'Unknown error'\n const href = `#${name}`\n\n errors.push({ path, href, name, text })\n }\n }\n }\n })\n\n return errors\n }\n }\n\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FeaturedFormPageViewModel {\n const { fileUpload } = this\n const { state } = context\n\n const upload = this.getUploadFromState(state)\n\n const viewModel = super.getViewModel(request, context)\n const { components } = viewModel\n\n // Featured form component\n const [formComponent] = components.filter(\n ({ model }) => model.id === fileUpload.name\n )\n\n const index = components.indexOf(formComponent)\n\n const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl)\n\n return {\n ...viewModel,\n formAction: upload?.uploadUrl,\n uploadId: upload?.uploadId,\n formComponent,\n\n // Split out components before/after\n componentsBefore: components.slice(0, index),\n components: components.slice(index),\n proxyUrl\n }\n }\n\n /**\n * Refreshes the CDP upload and files in the\n * state and checks for any removed files.\n *\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise we initiate a new one.\n * @param request - the hapi request\n * @param state - the form state\n */\n private async refreshUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n state = await this.checkUploadStatus(request, state)\n\n return state\n }\n\n /**\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise a new one is initiated.\n * @param request - the hapi request\n * @param state - the form state\n * @param depth - the number of retries so far\n */\n private async checkUploadStatus(\n request: AnyFormRequest,\n state: FormSubmissionState,\n depth = 1\n ): Promise<FormSubmissionState> {\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n // If no upload exists, initiate a new one.\n if (!upload?.uploadId) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const uploadId = upload.uploadId\n\n let statusResponse\n\n try {\n statusResponse = await getUploadStatus(uploadId)\n } catch (err) {\n // if the user loads a file upload page and queries the cached upload, after the upload has\n // expired in CDP, we will get a 404 from the getUploadStatus endpoint.\n // In this case we want to initiate a new upload and return that state, so the form\n // doesn't blow up for the end user.\n if (\n Boom.isBoom(err) &&\n err.output.statusCode === StatusCodes.NOT_FOUND.valueOf()\n ) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n throw err\n }\n\n if (!statusResponse) {\n throw Boom.badRequest(\n `Unexpected empty response from getUploadStatus for ${uploadId}`\n )\n }\n\n // Re-use the upload if it is still in the \"initiated\" state.\n if (statusResponse.uploadStatus === UploadStatus.initiated) {\n return state\n }\n\n if (statusResponse.uploadStatus === UploadStatus.pending) {\n // Using exponential backoff delays:\n // Depth 1: 2000ms, Depth 2: 4000ms, Depth 3: 8000ms, Depth 4: 16000ms, Depth 5+: 30000ms (capped)\n // A depth of 5 (or more) implies cumulative delays roughly reaching 55 seconds.\n if (depth >= 5) {\n const err = new Error(\n `Exceeded cumulative retry delay for ${uploadId} (depth: ${depth}). Re-initiating a new upload.`\n )\n request.logger.error(\n err,\n `[uploadTimeout] Exceeded cumulative retry delay for uploadId: ${uploadId} at depth: ${depth} - re-initiating new upload`\n )\n await this.initiateAndStoreNewUpload(request, state)\n throw Boom.gatewayTimeout(\n `Timed out waiting for ${uploadId} after cumulative retries exceeding ${((CDP_UPLOAD_TIMEOUT_MS - 5000) / 1000).toFixed(0)} seconds`\n )\n }\n const delay = getExponentialBackoffDelay(depth)\n request.logger.info(\n `[uploadRetry] Waiting ${delay / 1000} seconds for uploadId: ${uploadId} to complete (retry depth: ${depth})`\n )\n await wait(delay)\n return this.checkUploadStatus(request, state, depth + 1)\n }\n\n // Only add to files state if the file validates.\n // This secures against html tampering of the file input\n // (e.g. changing it to a simple text field or similar).\n const validationResult = tempItemSchema.validate(\n { uploadId, status: statusResponse },\n { stripUnknown: true }\n )\n const error = validationResult.error\n\n if (error) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n // CDP returns form.file as a single object for one file,\n // or an array for multiple files. The Joi schema normalises\n // both to an array via .single().\n await this.processUploadedFiles(\n request,\n state,\n validationResult.value,\n files,\n upload\n )\n\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n /**\n * Processes the uploaded files from a CDP status response.\n * Complete files are added to state, rejected/pending files\n * have their error messages flashed.\n * @param request - the hapi request\n * @param state - the form state\n * @param validatedItem - the Joi-validated upload item\n * @param files - the current files array from state\n * @param upload - the current upload initiation response\n */\n private async processUploadedFiles(\n request: AnyFormRequest,\n state: FormSubmissionState,\n validatedItem: FileState,\n files: FileState[],\n upload: UploadInitiateResponse | undefined\n ) {\n const { uploadId } = validatedItem\n const validatedStatus = validatedItem.status\n const rawFile = validatedStatus.form.file as unknown as\n | FileUpload\n | FileUpload[]\n const uploadedFiles = Array.isArray(rawFile) ? rawFile : [rawFile]\n\n const allErrors: FormSubmissionError[] = []\n\n for (const file of uploadedFiles) {\n if (file.fileStatus === FileStatus.complete) {\n const perFileState: FileState = {\n uploadId,\n status: {\n ...validatedStatus,\n form: { file }\n } as FileState['status']\n }\n files.unshift(prepareFileState(perFileState))\n } else {\n // Collect the error for rejected/pending files.\n const { fileUpload } = this\n const name = fileUpload.name\n const text = file.errorMessage ?? 'Unknown error'\n allErrors.push({ path: [name], href: `#${name}`, name, text })\n }\n }\n\n if (allErrors.length) {\n const cacheService = getCacheService(request.server)\n cacheService.setFlash(request, { errors: allErrors })\n }\n\n if (uploadedFiles.some((f) => f.fileStatus === FileStatus.complete)) {\n await this.mergeState(request, state, {\n upload: { [this.path]: { files, upload } }\n })\n }\n }\n\n /**\n * Checks the payload for a file getting removed\n * and removes it from the upload files if found\n * @param request - the hapi request\n * @param state - the form state\n * @returns updated state if any files have been removed\n */\n private async checkRemovedFiles(\n request: FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { path } = this\n const { params } = request\n\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n const filesUpdated = files.filter(\n ({ status }) => status.form.file.fileId !== params.itemId\n )\n\n if (filesUpdated.length === files.length) {\n return\n }\n\n await this.mergeState(request, state, {\n upload: { [path]: { files: filesUpdated, upload } }\n })\n }\n\n /**\n * Initiates a CDP file upload and stores in the upload state\n * @param request - the hapi request\n * @param state - the form state\n */\n private async initiateAndStoreNewUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n const { fileUpload, href, path } = this\n const { options, schema } = fileUpload\n const { getFormMetadata } = this.model.services.formsService\n\n const files = this.getFilesFromState(state)\n\n // Reset the upload in state\n let upload: UploadInitiateResponse | undefined\n\n // Don't initiate anymore after minimum of `schema.max` or MAX_UPLOADS\n const max = Math.min(schema.max ?? MAX_UPLOADS, MAX_UPLOADS)\n\n if (files.length < max) {\n const formMetadata = await getFormMetadata(request.params.slug)\n const notificationEmail =\n formMetadata.notificationEmail ?? 'defraforms@defra.gov.uk'\n\n const newUpload = await initiateUpload(\n href,\n notificationEmail,\n options.accept\n )\n\n if (newUpload === undefined) {\n throw Boom.badRequest('Unexpected empty response from initiateUpload')\n }\n\n upload = newUpload\n }\n\n return this.mergeState(request, state, {\n upload: { [path]: { files, upload } }\n })\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"FileUploadPageController.js","names":["ComponentType","Boom","wait","StatusCodes","FileUploadField","tempItemSchema","getCacheService","getError","getExponentialBackoffDelay","QuestionPageController","getProxyUrlForLocalDevelopment","getUploadStatus","initiateUpload","FileStatus","UploadStatus","MAX_UPLOADS","CDP_UPLOAD_TIMEOUT_MS","prepareStatus","status","file","form","isPending","fileStatus","pending","errorMessage","prepareFileState","fileState","FileUploadPageController","fileUpload","fileDeleteViewName","constructor","model","pageDef","collection","fileUploads","fields","filter","field","type","at","length","badImplementation","path","indexOf","name","viewName","getStateKeys","component","pagePath","page","getFormDataFromState","request","state","payload","files","getFilesFromState","undefined","getState","refreshUpload","uploadState","upload","getUploadFromState","makeGetItemDeleteRouteHandler","context","h","viewModel","params","fileToRemove","find","fileId","itemId","notFound","filename","t","getTranslator","view","backLink","getBackLink","pageTitle","itemTitle","confirmation","text","buttonConfirm","buttonCancel","makePostItemDeleteRouteHandler","confirm","getFormParams","checkRemovedFiles","proceed","getErrors","details","errors","forEach","error","isUploadError","isUploadRootError","push","value","href","getViewModel","translator","components","formComponent","id","index","proxyUrl","uploadUrl","formAction","uploadId","componentsBefore","slice","checkUploadStatus","depth","initiateAndStoreNewUpload","statusResponse","err","isBoom","output","statusCode","NOT_FOUND","valueOf","badRequest","uploadStatus","initiated","Error","logger","gatewayTimeout","toFixed","delay","info","validationResult","validate","stripUnknown","processUploadedFiles","validatedItem","validatedStatus","rawFile","uploadedFiles","Array","isArray","allErrors","complete","perFileState","unshift","cacheService","server","setFlash","some","f","mergeState","filesUpdated","options","schema","getFormMetadata","services","formsService","max","Math","min","formMetadata","slug","notificationEmail","newUpload","accept"],"sources":["../../../../../src/server/plugins/engine/pageControllers/FileUploadPageController.ts"],"sourcesContent":["import { ComponentType, type PageFileUpload } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { wait } from '@hapi/hoek'\nimport { StatusCodes } from 'http-status-codes'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n FileUploadField,\n tempItemSchema\n} from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport { type FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n getCacheService,\n getError,\n getExponentialBackoffDelay\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { getProxyUrlForLocalDevelopment } from '~/src/server/plugins/engine/pageControllers/helpers/index.js'\nimport {\n getUploadStatus,\n initiateUpload\n} from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n FileStatus,\n UploadStatus,\n type AnyFormRequest,\n type FeaturedFormPageViewModel,\n type FileState,\n type FileUpload,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError,\n type FormSubmissionState,\n type ItemDeletePageViewModel,\n type UploadInitiateResponse,\n type UploadStatusFileResponse\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nconst MAX_UPLOADS = 25\nconst CDP_UPLOAD_TIMEOUT_MS = 60000 // 1 minute\n\nexport function prepareStatus(status: UploadStatusFileResponse) {\n const file = status.form.file\n const isPending = file.fileStatus === FileStatus.pending\n\n if (!file.errorMessage && isPending) {\n file.errorMessage = 'The selected file has not fully uploaded'\n }\n\n return status\n}\n\nfunction prepareFileState(fileState: FileState) {\n prepareStatus(fileState.status)\n\n return fileState\n}\n\nexport class FileUploadPageController extends QuestionPageController {\n declare pageDef: PageFileUpload\n\n fileUpload: FileUploadField\n fileDeleteViewName = 'item-delete'\n\n constructor(model: FormModel, pageDef: PageFileUpload) {\n super(model, pageDef)\n\n const { collection } = this\n\n // Get the file upload fields from the collection\n const fileUploads = collection.fields.filter(\n (field): field is FileUploadField =>\n field.type === ComponentType.FileUploadField\n )\n\n const fileUpload = fileUploads.at(0)\n\n // Assert we have exactly 1 file upload component\n if (!fileUpload || fileUploads.length > 1) {\n throw Boom.badImplementation(\n `Expected 1 FileUploadFieldComponent in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assert the file upload component is the first form component\n if (collection.fields.indexOf(fileUpload) !== 0) {\n throw Boom.badImplementation(\n `Expected '${fileUpload.name}' to be the first form component in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assign the file upload component to the controller\n this.fileUpload = fileUpload\n this.viewName = 'file-upload'\n }\n\n /**\n * Get supplementary state keys for clearing file upload state.\n * Returns the nested upload path for FileUploadField components only.\n * @param component - The component to get supplementary state keys for\n * @returns Array containing the nested upload path, e.g., [\"upload['/page-path']\"]\n * or ['upload'] if no page path is available. Returns empty array for non-FileUploadField components.\n */\n getStateKeys(component: FormComponent): string[] {\n // Only return upload keys for FileUploadField components\n if (!(component instanceof FileUploadField)) {\n return []\n }\n\n const pagePath = component.page?.path\n return pagePath ? [`upload['${pagePath}']`] : ['upload']\n }\n\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ) {\n const { fileUpload } = this\n\n const payload = super.getFormDataFromState(request, state)\n const files = this.getFilesFromState(state)\n\n // Append the files to the payload\n payload[fileUpload.name] = files.length ? files : undefined\n\n return payload\n }\n\n async getState(request: AnyFormRequest) {\n const { fileUpload } = this\n\n // Get the actual state\n const state = await super.getState(request)\n const files = this.getFilesFromState(state)\n\n // Overwrite the files with those in the upload state\n state[fileUpload.name] = files\n\n return this.refreshUpload(request, state)\n }\n\n /**\n * Get the uploaded files from state.\n */\n getFilesFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.files ?? []\n }\n\n /**\n * Get the initiated upload from state.\n */\n getUploadFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.upload\n }\n\n makeGetItemDeleteRouteHandler() {\n return (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewModel } = this\n const { params } = request\n const { state } = context\n\n const files = this.getFilesFromState(state)\n\n const fileToRemove = files.find(\n ({ status }) => status.form.file.fileId === params.itemId\n )\n\n if (!fileToRemove) {\n throw Boom.notFound('File to delete not found')\n }\n\n const { filename } = fileToRemove.status.form.file\n\n const { t } = this.getTranslator(request)\n\n return h.view(this.fileDeleteViewName, {\n ...viewModel,\n context,\n backLink: this.getBackLink(request, context, t),\n pageTitle: `Are you sure you want to remove this file?`,\n itemTitle: filename,\n confirmation: { text: 'You cannot recover removed files.' },\n buttonConfirm: { text: 'Remove file' },\n buttonCancel: { text: 'Cancel' }\n } satisfies ItemDeletePageViewModel)\n }\n }\n\n makePostItemDeleteRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { path } = this\n const { state } = context\n\n const { confirm } = this.getFormParams(request)\n\n // Check for any removed files in the POST payload\n if (confirm) {\n await this.checkRemovedFiles(request, state)\n return this.proceed(request, h, path)\n }\n\n return this.proceed(request, h)\n }\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n const { fileUpload } = this\n\n if (details) {\n const errors: FormSubmissionError[] = []\n\n details.forEach((error) => {\n const isUploadError = error.path[0] === fileUpload.name\n const isUploadRootError = isUploadError && error.path.length === 1\n\n if (!isUploadError || isUploadRootError) {\n // The error is for the root of the upload or another\n // field on the page so defer to the getError helper\n errors.push(getError(error))\n } else {\n const { context, path, type } = error\n\n if (type === 'object.unknown' && path.at(-1) === 'errorMessage') {\n const value = context?.value as string | undefined\n\n if (value) {\n const name = fileUpload.name\n const text = typeof value === 'string' ? value : 'Unknown error'\n const href = `#${name}`\n\n errors.push({ path, href, name, text })\n }\n }\n }\n })\n\n return errors\n }\n }\n\n getViewModel(\n request: FormContextRequest,\n context: FormContext,\n translator: Translator\n ): FeaturedFormPageViewModel {\n const { fileUpload } = this\n const { state } = context\n\n const upload = this.getUploadFromState(state)\n\n const viewModel = super.getViewModel(request, context, translator)\n const { components } = viewModel\n\n // Featured form component\n const [formComponent] = components.filter(\n ({ model }) => model.id === fileUpload.name\n )\n\n const index = components.indexOf(formComponent)\n\n const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl)\n\n return {\n ...viewModel,\n formAction: upload?.uploadUrl,\n uploadId: upload?.uploadId,\n formComponent,\n\n // Split out components before/after\n componentsBefore: components.slice(0, index),\n components: components.slice(index),\n proxyUrl\n }\n }\n\n /**\n * Refreshes the CDP upload and files in the\n * state and checks for any removed files.\n *\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise we initiate a new one.\n * @param request - the hapi request\n * @param state - the form state\n */\n private async refreshUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n state = await this.checkUploadStatus(request, state)\n\n return state\n }\n\n /**\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise a new one is initiated.\n * @param request - the hapi request\n * @param state - the form state\n * @param depth - the number of retries so far\n */\n private async checkUploadStatus(\n request: AnyFormRequest,\n state: FormSubmissionState,\n depth = 1\n ): Promise<FormSubmissionState> {\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n // If no upload exists, initiate a new one.\n if (!upload?.uploadId) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const uploadId = upload.uploadId\n\n let statusResponse\n\n try {\n statusResponse = await getUploadStatus(uploadId)\n } catch (err) {\n // if the user loads a file upload page and queries the cached upload, after the upload has\n // expired in CDP, we will get a 404 from the getUploadStatus endpoint.\n // In this case we want to initiate a new upload and return that state, so the form\n // doesn't blow up for the end user.\n if (\n Boom.isBoom(err) &&\n err.output.statusCode === StatusCodes.NOT_FOUND.valueOf()\n ) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n throw err\n }\n\n if (!statusResponse) {\n throw Boom.badRequest(\n `Unexpected empty response from getUploadStatus for ${uploadId}`\n )\n }\n\n // Re-use the upload if it is still in the \"initiated\" state.\n if (statusResponse.uploadStatus === UploadStatus.initiated) {\n return state\n }\n\n if (statusResponse.uploadStatus === UploadStatus.pending) {\n // Using exponential backoff delays:\n // Depth 1: 2000ms, Depth 2: 4000ms, Depth 3: 8000ms, Depth 4: 16000ms, Depth 5+: 30000ms (capped)\n // A depth of 5 (or more) implies cumulative delays roughly reaching 55 seconds.\n if (depth >= 5) {\n const err = new Error(\n `Exceeded cumulative retry delay for ${uploadId} (depth: ${depth}). Re-initiating a new upload.`\n )\n request.logger.error(\n err,\n `[uploadTimeout] Exceeded cumulative retry delay for uploadId: ${uploadId} at depth: ${depth} - re-initiating new upload`\n )\n await this.initiateAndStoreNewUpload(request, state)\n throw Boom.gatewayTimeout(\n `Timed out waiting for ${uploadId} after cumulative retries exceeding ${((CDP_UPLOAD_TIMEOUT_MS - 5000) / 1000).toFixed(0)} seconds`\n )\n }\n const delay = getExponentialBackoffDelay(depth)\n request.logger.info(\n `[uploadRetry] Waiting ${delay / 1000} seconds for uploadId: ${uploadId} to complete (retry depth: ${depth})`\n )\n await wait(delay)\n return this.checkUploadStatus(request, state, depth + 1)\n }\n\n // Only add to files state if the file validates.\n // This secures against html tampering of the file input\n // (e.g. changing it to a simple text field or similar).\n const validationResult = tempItemSchema.validate(\n { uploadId, status: statusResponse },\n { stripUnknown: true }\n )\n const error = validationResult.error\n\n if (error) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n // CDP returns form.file as a single object for one file,\n // or an array for multiple files. The Joi schema normalises\n // both to an array via .single().\n await this.processUploadedFiles(\n request,\n state,\n validationResult.value,\n files,\n upload\n )\n\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n /**\n * Processes the uploaded files from a CDP status response.\n * Complete files are added to state, rejected/pending files\n * have their error messages flashed.\n * @param request - the hapi request\n * @param state - the form state\n * @param validatedItem - the Joi-validated upload item\n * @param files - the current files array from state\n * @param upload - the current upload initiation response\n */\n private async processUploadedFiles(\n request: AnyFormRequest,\n state: FormSubmissionState,\n validatedItem: FileState,\n files: FileState[],\n upload: UploadInitiateResponse | undefined\n ) {\n const { uploadId } = validatedItem\n const validatedStatus = validatedItem.status\n const rawFile = validatedStatus.form.file as unknown as\n | FileUpload\n | FileUpload[]\n const uploadedFiles = Array.isArray(rawFile) ? rawFile : [rawFile]\n\n const allErrors: FormSubmissionError[] = []\n\n for (const file of uploadedFiles) {\n if (file.fileStatus === FileStatus.complete) {\n const perFileState: FileState = {\n uploadId,\n status: {\n ...validatedStatus,\n form: { file }\n } as FileState['status']\n }\n files.unshift(prepareFileState(perFileState))\n } else {\n // Collect the error for rejected/pending files.\n const { fileUpload } = this\n const name = fileUpload.name\n const text = file.errorMessage ?? 'Unknown error'\n allErrors.push({ path: [name], href: `#${name}`, name, text })\n }\n }\n\n if (allErrors.length) {\n const cacheService = getCacheService(request.server)\n cacheService.setFlash(request, { errors: allErrors })\n }\n\n if (uploadedFiles.some((f) => f.fileStatus === FileStatus.complete)) {\n await this.mergeState(request, state, {\n upload: { [this.path]: { files, upload } }\n })\n }\n }\n\n /**\n * Checks the payload for a file getting removed\n * and removes it from the upload files if found\n * @param request - the hapi request\n * @param state - the form state\n * @returns updated state if any files have been removed\n */\n private async checkRemovedFiles(\n request: FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { path } = this\n const { params } = request\n\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n const filesUpdated = files.filter(\n ({ status }) => status.form.file.fileId !== params.itemId\n )\n\n if (filesUpdated.length === files.length) {\n return\n }\n\n await this.mergeState(request, state, {\n upload: { [path]: { files: filesUpdated, upload } }\n })\n }\n\n /**\n * Initiates a CDP file upload and stores in the upload state\n * @param request - the hapi request\n * @param state - the form state\n */\n private async initiateAndStoreNewUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n const { fileUpload, href, path } = this\n const { options, schema } = fileUpload\n const { getFormMetadata } = this.model.services.formsService\n\n const files = this.getFilesFromState(state)\n\n // Reset the upload in state\n let upload: UploadInitiateResponse | undefined\n\n // Don't initiate anymore after minimum of `schema.max` or MAX_UPLOADS\n const max = Math.min(schema.max ?? MAX_UPLOADS, MAX_UPLOADS)\n\n if (files.length < max) {\n const formMetadata = await getFormMetadata(request.params.slug)\n const notificationEmail =\n formMetadata.notificationEmail ?? 'defraforms@defra.gov.uk'\n\n const newUpload = await initiateUpload(\n href,\n notificationEmail,\n options.accept\n )\n\n if (newUpload === undefined) {\n throw Boom.badRequest('Unexpected empty response from initiateUpload')\n }\n\n upload = newUpload\n }\n\n return this.mergeState(request, state, {\n upload: { [path]: { files, upload } }\n })\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -2,9 +2,10 @@ import { type Events, type FormDefinition, type Page, type Section } from '@defr
|
|
|
2
2
|
import { type Lifecycle, type RouteOptions, type Server } from '@hapi/hapi';
|
|
3
3
|
import { type ComponentCollection } from '../../../../server/plugins/engine/components/ComponentCollection.js';
|
|
4
4
|
import { type FormComponent } from '../../../../server/plugins/engine/components/FormComponent.js';
|
|
5
|
+
import { type Translator } from '../../../../server/plugins/engine/i18n/types.js';
|
|
5
6
|
import { type FormModel } from '../../../../server/plugins/engine/models/index.js';
|
|
6
7
|
import { type ExecutableCondition } from '../../../../server/plugins/engine/models/types.js';
|
|
7
|
-
import { type FormContext, type PageViewModelBase } from '../../../../server/plugins/engine/types.js';
|
|
8
|
+
import { type AnyFormRequest, type FormContext, type PageViewModelBase } from '../../../../server/plugins/engine/types.js';
|
|
8
9
|
import { type FormRequest, type FormRequestPayload, type FormRequestPayloadRefs, type FormRequestRefs, type FormResponseToolkit } from '../../../../server/routes/types.js';
|
|
9
10
|
export declare class PageController {
|
|
10
11
|
/**
|
|
@@ -41,6 +42,7 @@ export declare class PageController {
|
|
|
41
42
|
getStartPath(): string;
|
|
42
43
|
getSummaryPath(): string;
|
|
43
44
|
getStatusPath(): string;
|
|
45
|
+
protected getTranslator(request: AnyFormRequest): Translator;
|
|
44
46
|
makeGetRouteHandler(): (request: FormRequest, context: FormContext, h: FormResponseToolkit) => ReturnType<Lifecycle.Method<FormRequestRefs>>;
|
|
45
47
|
makePostRouteHandler(): (request: FormRequestPayload, context: FormContext, h: FormResponseToolkit) => ReturnType<Lifecycle.Method<FormRequestPayloadRefs>>;
|
|
46
48
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ControllerPath } from '@defra/forms-model';
|
|
2
2
|
import Boom from '@hapi/boom';
|
|
3
3
|
import { getSaveAndExitHelpers, getStartPath, normalisePath } from "../helpers.js";
|
|
4
|
+
import { resolveLanguage } from "../../../utils/utils.js";
|
|
4
5
|
export class PageController {
|
|
5
6
|
/**
|
|
6
7
|
* The base class for all page controllers. Page controllers are responsible for generating the get and post route handlers when a user navigates to `/{id}/{path*}`.
|
|
@@ -124,13 +125,22 @@ export class PageController {
|
|
|
124
125
|
getStatusPath() {
|
|
125
126
|
return ControllerPath.Status.valueOf();
|
|
126
127
|
}
|
|
128
|
+
getTranslator(request) {
|
|
129
|
+
return this.model.createTranslator(resolveLanguage(request));
|
|
130
|
+
}
|
|
127
131
|
makeGetRouteHandler() {
|
|
128
132
|
return (request, context, h) => {
|
|
129
133
|
const {
|
|
130
134
|
viewModel,
|
|
131
135
|
viewName
|
|
132
136
|
} = this;
|
|
133
|
-
|
|
137
|
+
const {
|
|
138
|
+
t
|
|
139
|
+
} = this.getTranslator(request);
|
|
140
|
+
return h.view(viewName, {
|
|
141
|
+
...viewModel,
|
|
142
|
+
t
|
|
143
|
+
});
|
|
134
144
|
};
|
|
135
145
|
}
|
|
136
146
|
makePostRouteHandler() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageController.js","names":["ControllerPath","Boom","getSaveAndExitHelpers","getStartPath","normalisePath","PageController","def","name","model","pageDef","id","title","section","condition","events","collection","viewName","allowSaveAndExit","constructor","getSection","conditions","view","path","href","getHref","keys","getRouteOptions","postRouteOptions","viewModel","showTitle","pageTitle","sectionTitle","hideTitle","page","isStartPage","serviceUrl","feedbackLink","phaseTag","options","disableUserFeedback","undefined","formId","phaseBanner","phase","basePath","relativeTargetPath","startsWith","substring","finalPath","replace","getSummaryPath","Summary","valueOf","getStatusPath","Status","makeGetRouteHandler","request","context","h","makePostRouteHandler","badRequest","getStateKeys","_component","shouldShowSaveAndExit","server"],"sources":["../../../../../src/server/plugins/engine/pageControllers/PageController.ts"],"sourcesContent":["import {\n ControllerPath,\n type Events,\n type FormDefinition,\n type Page,\n type Section\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type Lifecycle, type RouteOptions, type Server } from '@hapi/hapi'\n\nimport { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { type FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n getSaveAndExitHelpers,\n getStartPath,\n normalisePath\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport {\n type FormContext,\n type PageViewModelBase\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport class PageController {\n /**\n * The base class for all page controllers. Page controllers are responsible for generating the get and post route handlers when a user navigates to `/{id}/{path*}`.\n */\n def: FormDefinition\n name?: string\n model: FormModel\n pageDef: Page\n id?: string\n title: string\n section?: Section\n condition?: ExecutableCondition\n events?: Events\n collection?: ComponentCollection\n viewName = 'index'\n allowSaveAndExit = false\n\n constructor(model: FormModel, pageDef: Page) {\n const { def } = model\n\n this.def = def\n this.name = def.name\n this.model = model\n this.pageDef = pageDef\n this.id = pageDef.id\n this.title = pageDef.title\n this.events = pageDef.events\n\n // Resolve section\n if (pageDef.section) {\n this.section = model.getSection(pageDef.section)\n }\n\n // Resolve condition\n if (pageDef.condition) {\n this.condition = model.conditions[pageDef.condition]\n }\n\n // Override view name\n if (pageDef.view) {\n this.viewName = pageDef.view\n }\n }\n\n get path() {\n return this.pageDef.path\n }\n\n get href() {\n const { path } = this\n return this.getHref(`/${normalisePath(path)}`)\n }\n\n get keys() {\n return this.collection?.keys ?? []\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {}\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {}\n }\n\n get viewModel(): PageViewModelBase {\n const { name, section, title } = this\n\n const showTitle = true\n const pageTitle = title\n const sectionTitle = section?.hideTitle !== true ? section?.title : ''\n\n return {\n name,\n page: this,\n pageTitle,\n sectionTitle,\n showTitle,\n isStartPage: false,\n serviceUrl: this.getHref('/'),\n feedbackLink: this.feedbackLink,\n phaseTag: this.phaseTag\n }\n }\n\n get feedbackLink() {\n return this.def.options?.disableUserFeedback\n ? undefined\n : `/form/feedback?formId=${this.model.formId}`\n }\n\n get phaseTag() {\n const { def } = this\n return def.phaseBanner?.phase\n }\n\n getHref(path: string): string {\n const basePath = this.model.basePath\n\n if (path === '/') {\n return `/${basePath}`\n }\n\n // if ever the path is not prefixed with a slash, add it\n const relativeTargetPath = path.startsWith('/') ? path.substring(1) : path\n let finalPath = `/${basePath}`\n if (relativeTargetPath) {\n finalPath += `/${relativeTargetPath}`\n }\n finalPath = finalPath.replace(/\\/{2,}/g, '/')\n\n return finalPath\n }\n\n getStartPath() {\n return getStartPath(this.model)\n }\n\n getSummaryPath() {\n return ControllerPath.Summary.valueOf()\n }\n\n getStatusPath() {\n return ControllerPath.Status.valueOf()\n }\n\n makeGetRouteHandler(): (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => ReturnType<Lifecycle.Method<FormRequestRefs>> {\n return (request, context, h) => {\n const { viewModel, viewName } = this\n return h.view(viewName, viewModel)\n }\n }\n\n makePostRouteHandler(): (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => ReturnType<Lifecycle.Method<FormRequestPayloadRefs>> {\n throw Boom.badRequest('Unsupported POST route handler for this page')\n }\n\n /**\n * Get supplementary state keys for clearing component state.\n *\n * This method returns page controller-level state keys only. The core component's\n * state key (the component's name) is managed separately by the framework and should\n * NOT be included in the returned array.\n *\n * Returns an empty array by default. Override in subclasses to provide\n * page-specific supplementary state keys (e.g., upload state, cached data).\n * @param _component - The component to get supplementary state keys for (optional)\n * @returns Array of supplementary state keys to clear (excluding the component name itself)\n */\n getStateKeys(_component?: FormComponent): string[] {\n return []\n }\n\n shouldShowSaveAndExit(server: Server): boolean {\n return getSaveAndExitHelpers(server) !== undefined && this.allowSaveAndExit\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"PageController.js","names":["ControllerPath","Boom","getSaveAndExitHelpers","getStartPath","normalisePath","resolveLanguage","PageController","def","name","model","pageDef","id","title","section","condition","events","collection","viewName","allowSaveAndExit","constructor","getSection","conditions","view","path","href","getHref","keys","getRouteOptions","postRouteOptions","viewModel","showTitle","pageTitle","sectionTitle","hideTitle","page","isStartPage","serviceUrl","feedbackLink","phaseTag","options","disableUserFeedback","undefined","formId","phaseBanner","phase","basePath","relativeTargetPath","startsWith","substring","finalPath","replace","getSummaryPath","Summary","valueOf","getStatusPath","Status","getTranslator","request","createTranslator","makeGetRouteHandler","context","h","t","makePostRouteHandler","badRequest","getStateKeys","_component","shouldShowSaveAndExit","server"],"sources":["../../../../../src/server/plugins/engine/pageControllers/PageController.ts"],"sourcesContent":["import {\n ControllerPath,\n type Events,\n type FormDefinition,\n type Page,\n type Section\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type Lifecycle, type RouteOptions, type Server } from '@hapi/hapi'\n\nimport { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { type FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n getSaveAndExitHelpers,\n getStartPath,\n normalisePath\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport {\n type AnyFormRequest,\n type FormContext,\n type PageViewModelBase\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\nimport { resolveLanguage } from '~/src/server/utils/utils.js'\n\nexport class PageController {\n /**\n * The base class for all page controllers. Page controllers are responsible for generating the get and post route handlers when a user navigates to `/{id}/{path*}`.\n */\n def: FormDefinition\n name?: string\n model: FormModel\n pageDef: Page\n id?: string\n title: string\n section?: Section\n condition?: ExecutableCondition\n events?: Events\n collection?: ComponentCollection\n viewName = 'index'\n allowSaveAndExit = false\n\n constructor(model: FormModel, pageDef: Page) {\n const { def } = model\n\n this.def = def\n this.name = def.name\n this.model = model\n this.pageDef = pageDef\n this.id = pageDef.id\n this.title = pageDef.title\n this.events = pageDef.events\n\n // Resolve section\n if (pageDef.section) {\n this.section = model.getSection(pageDef.section)\n }\n\n // Resolve condition\n if (pageDef.condition) {\n this.condition = model.conditions[pageDef.condition]\n }\n\n // Override view name\n if (pageDef.view) {\n this.viewName = pageDef.view\n }\n }\n\n get path() {\n return this.pageDef.path\n }\n\n get href() {\n const { path } = this\n return this.getHref(`/${normalisePath(path)}`)\n }\n\n get keys() {\n return this.collection?.keys ?? []\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {}\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {}\n }\n\n get viewModel(): PageViewModelBase {\n const { name, section, title } = this\n\n const showTitle = true\n const pageTitle = title\n const sectionTitle = section?.hideTitle !== true ? section?.title : ''\n\n return {\n name,\n page: this,\n pageTitle,\n sectionTitle,\n showTitle,\n isStartPage: false,\n serviceUrl: this.getHref('/'),\n feedbackLink: this.feedbackLink,\n phaseTag: this.phaseTag\n }\n }\n\n get feedbackLink() {\n return this.def.options?.disableUserFeedback\n ? undefined\n : `/form/feedback?formId=${this.model.formId}`\n }\n\n get phaseTag() {\n const { def } = this\n return def.phaseBanner?.phase\n }\n\n getHref(path: string): string {\n const basePath = this.model.basePath\n\n if (path === '/') {\n return `/${basePath}`\n }\n\n // if ever the path is not prefixed with a slash, add it\n const relativeTargetPath = path.startsWith('/') ? path.substring(1) : path\n let finalPath = `/${basePath}`\n if (relativeTargetPath) {\n finalPath += `/${relativeTargetPath}`\n }\n finalPath = finalPath.replace(/\\/{2,}/g, '/')\n\n return finalPath\n }\n\n getStartPath() {\n return getStartPath(this.model)\n }\n\n getSummaryPath() {\n return ControllerPath.Summary.valueOf()\n }\n\n getStatusPath() {\n return ControllerPath.Status.valueOf()\n }\n\n protected getTranslator(request: AnyFormRequest): Translator {\n return this.model.createTranslator(resolveLanguage(request))\n }\n\n makeGetRouteHandler(): (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => ReturnType<Lifecycle.Method<FormRequestRefs>> {\n return (request, context, h) => {\n const { viewModel, viewName } = this\n const { t } = this.getTranslator(request)\n return h.view(viewName, {\n ...viewModel,\n t\n })\n }\n }\n\n makePostRouteHandler(): (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => ReturnType<Lifecycle.Method<FormRequestPayloadRefs>> {\n throw Boom.badRequest('Unsupported POST route handler for this page')\n }\n\n /**\n * Get supplementary state keys for clearing component state.\n *\n * This method returns page controller-level state keys only. The core component's\n * state key (the component's name) is managed separately by the framework and should\n * NOT be included in the returned array.\n *\n * Returns an empty array by default. Override in subclasses to provide\n * page-specific supplementary state keys (e.g., upload state, cached data).\n * @param _component - The component to get supplementary state keys for (optional)\n * @returns Array of supplementary state keys to clear (excluding the component name itself)\n */\n getStateKeys(_component?: FormComponent): string[] {\n return []\n }\n\n shouldShowSaveAndExit(server: Server): boolean {\n return getSaveAndExitHelpers(server) !== undefined && this.allowSaveAndExit\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -3,6 +3,7 @@ import { type RouteOptions } from '@hapi/hapi';
|
|
|
3
3
|
import { type ValidationErrorItem } from 'joi';
|
|
4
4
|
import { ComponentCollection } from '../../../../server/plugins/engine/components/ComponentCollection.js';
|
|
5
5
|
import { type BackLink } from '../../../../server/plugins/engine/components/types.js';
|
|
6
|
+
import { type Translator } from '../../../../server/plugins/engine/i18n/types.js';
|
|
6
7
|
import { type FormModel } from '../../../../server/plugins/engine/models/index.js';
|
|
7
8
|
import { PageController } from '../../../../server/plugins/engine/pageControllers/PageController.js';
|
|
8
9
|
import { type AnyFormRequest, type FormContext, type FormContextRequest, type FormPageViewModel, type FormPayload, type FormPayloadParams, type FormState, type FormStateValue, type FormSubmissionState } from '../../../../server/plugins/engine/types.js';
|
|
@@ -19,8 +20,9 @@ export declare class QuestionPageController extends PageController {
|
|
|
19
20
|
* Used for mapping form payloads and errors to govuk-frontend's template api, so a page can be rendered
|
|
20
21
|
* @param request - the hapi request
|
|
21
22
|
* @param context - the form context
|
|
23
|
+
* @param translator - optional per-request translator pair
|
|
22
24
|
*/
|
|
23
|
-
getViewModel(request: FormContextRequest, context: FormContext): FormPageViewModel;
|
|
25
|
+
getViewModel(request: FormContextRequest, context: FormContext, translator: Translator): FormPageViewModel;
|
|
24
26
|
getRelevantPath(request: AnyFormRequest, context: FormContext): string;
|
|
25
27
|
/**
|
|
26
28
|
* Apply conditions to evaluation state to determine next page path
|
|
@@ -45,7 +47,7 @@ export declare class QuestionPageController extends PageController {
|
|
|
45
47
|
/**
|
|
46
48
|
* Get the back link for a given progress.
|
|
47
49
|
*/
|
|
48
|
-
protected getBackLink(request: FormContextRequest, context: FormContext): BackLink | undefined;
|
|
50
|
+
protected getBackLink(request: FormContextRequest, context: FormContext, t: (key: string, opts?: Record<string, unknown>) => string): BackLink | undefined;
|
|
49
51
|
makePostRouteHandler(): (request: FormRequestPayload, context: FormContext, h: FormResponseToolkit) => Promise<import("@hapi/hapi").ResponseObject>;
|
|
50
52
|
private dispatchExternal;
|
|
51
53
|
proceed(request: FormContextRequest, h: FormResponseToolkit, nextPath?: string): import("@hapi/hapi").ResponseObject;
|
|
@@ -3,7 +3,6 @@ import Boom from '@hapi/boom';
|
|
|
3
3
|
import { COMPONENT_STATE_ERROR, EXTERNAL_STATE_APPENDAGE, EXTERNAL_STATE_PAYLOAD, PAYMENT_EXPIRED_NOTIFICATION } from "../../../constants.js";
|
|
4
4
|
import { ComponentCollection } from "../components/ComponentCollection.js";
|
|
5
5
|
import { PaymentField } from "../components/PaymentField.js";
|
|
6
|
-
import { optionalText } from "../components/constants.js";
|
|
7
6
|
import { checkFormStatus, getCacheService, getErrors, getSaveAndExitHelpers, normalisePath, proceed } from "../helpers.js";
|
|
8
7
|
import { PageController } from "./PageController.js";
|
|
9
8
|
import { prefillStateFromQueryParameters } from "./helpers/state.js";
|
|
@@ -66,24 +65,35 @@ export class QuestionPageController extends PageController {
|
|
|
66
65
|
* Used for mapping form payloads and errors to govuk-frontend's template api, so a page can be rendered
|
|
67
66
|
* @param request - the hapi request
|
|
68
67
|
* @param context - the form context
|
|
68
|
+
* @param translator - optional per-request translator pair
|
|
69
69
|
*/
|
|
70
|
-
getViewModel(request, context) {
|
|
70
|
+
getViewModel(request, context, translator) {
|
|
71
71
|
const {
|
|
72
72
|
collection,
|
|
73
73
|
viewModel
|
|
74
74
|
} = this;
|
|
75
|
-
const {
|
|
76
|
-
query
|
|
77
|
-
} = request;
|
|
78
75
|
const {
|
|
79
76
|
payload,
|
|
80
77
|
errors
|
|
81
78
|
} = context;
|
|
79
|
+
const {
|
|
80
|
+
t,
|
|
81
|
+
tPage,
|
|
82
|
+
tSection
|
|
83
|
+
} = translator;
|
|
82
84
|
let {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
showTitle,
|
|
86
|
+
sectionTitle
|
|
85
87
|
} = viewModel;
|
|
86
|
-
|
|
88
|
+
let pageTitle = this.pageDef.title ? tPage(this.pageDef, 'title') : '';
|
|
89
|
+
if (this.section) {
|
|
90
|
+
sectionTitle = this.section.hideTitle !== true ? tSection(this.section, 'title') : '';
|
|
91
|
+
}
|
|
92
|
+
const components = collection.getViewModel({
|
|
93
|
+
payload,
|
|
94
|
+
errors,
|
|
95
|
+
translator
|
|
96
|
+
});
|
|
87
97
|
const formComponents = components.filter(({
|
|
88
98
|
isFormComponent
|
|
89
99
|
}) => isFormComponent);
|
|
@@ -113,10 +123,15 @@ export class QuestionPageController extends PageController {
|
|
|
113
123
|
|
|
114
124
|
// Check for optional in label
|
|
115
125
|
const isOptional = this.collection.fields.at(0)?.options.required === false;
|
|
116
|
-
if (pageTitle) {
|
|
117
|
-
labelOrLegend.text
|
|
126
|
+
if (!pageTitle) {
|
|
127
|
+
pageTitle = labelOrLegend.text;
|
|
118
128
|
}
|
|
119
|
-
|
|
129
|
+
labelOrLegend.text = isOptional ? `${pageTitle} ${t('common.optional')}` : pageTitle;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Fallback if still no pageTitle set
|
|
133
|
+
if (!pageTitle) {
|
|
134
|
+
pageTitle = labelOrLegend.text;
|
|
120
135
|
}
|
|
121
136
|
}
|
|
122
137
|
showTitle = !isPageHeading;
|
|
@@ -158,8 +173,10 @@ export class QuestionPageController extends PageController {
|
|
|
158
173
|
});
|
|
159
174
|
return {
|
|
160
175
|
...viewModel,
|
|
161
|
-
backLink: this.getBackLink(request, context),
|
|
176
|
+
backLink: this.getBackLink(request, context, t),
|
|
162
177
|
context,
|
|
178
|
+
pageTitle,
|
|
179
|
+
sectionTitle,
|
|
163
180
|
showTitle,
|
|
164
181
|
components,
|
|
165
182
|
errors,
|
|
@@ -355,14 +372,18 @@ export class QuestionPageController extends PageController {
|
|
|
355
372
|
const {
|
|
356
373
|
evaluationState
|
|
357
374
|
} = context;
|
|
375
|
+
const translator = this.getTranslator(request);
|
|
376
|
+
const {
|
|
377
|
+
t
|
|
378
|
+
} = translator;
|
|
358
379
|
|
|
359
380
|
// Copy any URL params into the form state (if not already done so)
|
|
360
381
|
if (await prefillStateFromQueryParameters(request, this)) {
|
|
361
382
|
// Forward to same page without query string
|
|
362
383
|
return h.redirect(request.url.pathname);
|
|
363
384
|
}
|
|
364
|
-
const viewModel = this.getViewModel(request, context);
|
|
365
|
-
viewModel.errors = collection.getViewErrors(viewModel.errors);
|
|
385
|
+
const viewModel = this.getViewModel(request, context, translator);
|
|
386
|
+
viewModel.errors = collection.getViewErrors(translator, viewModel.errors);
|
|
366
387
|
const flashedError = request.yar.flash(COMPONENT_STATE_ERROR);
|
|
367
388
|
const flashedErrors = !Array.isArray(flashedError) ? [flashedError] : [];
|
|
368
389
|
viewModel.errors = (viewModel.errors ?? []).concat(flashedErrors);
|
|
@@ -376,7 +397,10 @@ export class QuestionPageController extends PageController {
|
|
|
376
397
|
// Filter our components based on their conditions using our evaluated state
|
|
377
398
|
viewModel.components = this.filterConditionalComponents(viewModel, model, evaluationState);
|
|
378
399
|
viewModel.hasMissingNotificationEmail = await this.hasMissingNotificationEmail(request, context);
|
|
379
|
-
return h.view(viewName,
|
|
400
|
+
return h.view(viewName, {
|
|
401
|
+
...viewModel,
|
|
402
|
+
t
|
|
403
|
+
});
|
|
380
404
|
};
|
|
381
405
|
}
|
|
382
406
|
async hasMissingNotificationEmail(request, context) {
|
|
@@ -411,7 +435,7 @@ export class QuestionPageController extends PageController {
|
|
|
411
435
|
/**
|
|
412
436
|
* Get the back link for a given progress.
|
|
413
437
|
*/
|
|
414
|
-
getBackLink(request, context) {
|
|
438
|
+
getBackLink(request, context, t) {
|
|
415
439
|
const {
|
|
416
440
|
pageDef
|
|
417
441
|
} = this;
|
|
@@ -430,7 +454,7 @@ export class QuestionPageController extends PageController {
|
|
|
430
454
|
// Check answers back link
|
|
431
455
|
if (returnUrl) {
|
|
432
456
|
return {
|
|
433
|
-
text: hasRepeater(pageDef) && itemId ? '
|
|
457
|
+
text: hasRepeater(pageDef) && itemId ? t('pages.question.backToAddAnother') : t('pages.question.backToCheckAnswers'),
|
|
434
458
|
href: returnUrl
|
|
435
459
|
};
|
|
436
460
|
}
|
|
@@ -446,7 +470,7 @@ export class QuestionPageController extends PageController {
|
|
|
446
470
|
|
|
447
471
|
// Default back link
|
|
448
472
|
return {
|
|
449
|
-
text: '
|
|
473
|
+
text: t('common.back'),
|
|
450
474
|
href: this.getHref(backPath)
|
|
451
475
|
};
|
|
452
476
|
}
|
|
@@ -463,6 +487,10 @@ export class QuestionPageController extends PageController {
|
|
|
463
487
|
evaluationState
|
|
464
488
|
} = context;
|
|
465
489
|
const action = request.payload.action;
|
|
490
|
+
const translator = this.getTranslator(request);
|
|
491
|
+
const {
|
|
492
|
+
t
|
|
493
|
+
} = translator;
|
|
466
494
|
if (action?.startsWith(FormAction.External)) {
|
|
467
495
|
return await this.dispatchExternal(request, h, context);
|
|
468
496
|
}
|
|
@@ -472,12 +500,15 @@ export class QuestionPageController extends PageController {
|
|
|
472
500
|
* @todo Refactor to match POST REDIRECT GET pattern
|
|
473
501
|
*/
|
|
474
502
|
if (context.errors || isForceAccess) {
|
|
475
|
-
const viewModel = this.getViewModel(request, context);
|
|
476
|
-
viewModel.errors = collection.getViewErrors(viewModel.errors);
|
|
503
|
+
const viewModel = this.getViewModel(request, context, translator);
|
|
504
|
+
viewModel.errors = collection.getViewErrors(translator, viewModel.errors);
|
|
477
505
|
|
|
478
506
|
// Filter our components based on their conditions using our evaluated state
|
|
479
507
|
viewModel.components = this.filterConditionalComponents(viewModel, model, evaluationState);
|
|
480
|
-
return h.view(viewName,
|
|
508
|
+
return h.view(viewName, {
|
|
509
|
+
...viewModel,
|
|
510
|
+
t
|
|
511
|
+
});
|
|
481
512
|
}
|
|
482
513
|
|
|
483
514
|
// Save state
|