@defra/forms-engine-plugin 5.0.0-alpha.8 → 5.0.0-alpha.9

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.
Files changed (162) hide show
  1. package/.server/server/constants.d.ts +1 -0
  2. package/.server/server/constants.js +2 -1
  3. package/.server/server/constants.js.map +1 -1
  4. package/.server/server/plugins/engine/components/ComponentCollection.js +10 -6
  5. package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
  6. package/.server/server/plugins/engine/components/DatePartsField.d.ts +4 -2
  7. package/.server/server/plugins/engine/components/DatePartsField.js +14 -11
  8. package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -1
  9. package/.server/server/plugins/engine/components/DeclarationField.d.ts +4 -2
  10. package/.server/server/plugins/engine/components/DeclarationField.js +5 -3
  11. package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -1
  12. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +24 -2
  13. package/.server/server/plugins/engine/components/EastingNorthingField.js +93 -52
  14. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
  15. package/.server/server/plugins/engine/components/FormComponent.d.ts +4 -1
  16. package/.server/server/plugins/engine/components/FormComponent.js +3 -0
  17. package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
  18. package/.server/server/plugins/engine/components/LatLongField.d.ts +24 -2
  19. package/.server/server/plugins/engine/components/LatLongField.js +85 -46
  20. package/.server/server/plugins/engine/components/LatLongField.js.map +1 -1
  21. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +3 -2
  22. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +10 -11
  23. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
  24. package/.server/server/plugins/engine/components/MonthYearField.d.ts +4 -2
  25. package/.server/server/plugins/engine/components/MonthYearField.js +16 -13
  26. package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -1
  27. package/.server/server/plugins/engine/components/PaymentField.d.ts +1 -0
  28. package/.server/server/plugins/engine/components/PaymentField.js +9 -5
  29. package/.server/server/plugins/engine/components/PaymentField.js.map +1 -1
  30. package/.server/server/plugins/engine/components/UkAddressField.js +22 -7
  31. package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
  32. package/.server/server/plugins/engine/components/YesNoField.d.ts +3 -2
  33. package/.server/server/plugins/engine/components/YesNoField.js +5 -3
  34. package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
  35. package/.server/server/plugins/engine/components/helpers/components.js +1 -2
  36. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  37. package/.server/server/plugins/engine/components/helpers/index.d.ts +7 -0
  38. package/.server/server/plugins/engine/components/helpers/index.js +13 -1
  39. package/.server/server/plugins/engine/components/helpers/index.js.map +1 -1
  40. package/.server/server/plugins/engine/helpers.d.ts +21 -2
  41. package/.server/server/plugins/engine/helpers.js +43 -5
  42. package/.server/server/plugins/engine/helpers.js.map +1 -1
  43. package/.server/server/plugins/engine/i18n/createFormTranslator.d.ts +4 -4
  44. package/.server/server/plugins/engine/i18n/createFormTranslator.js +27 -5
  45. package/.server/server/plugins/engine/i18n/createFormTranslator.js.map +1 -1
  46. package/.server/server/plugins/engine/i18n/extractBaseTranslations.d.ts +1 -1
  47. package/.server/server/plugins/engine/i18n/extractBaseTranslations.js +9 -0
  48. package/.server/server/plugins/engine/i18n/extractBaseTranslations.js.map +1 -1
  49. package/.server/server/plugins/engine/i18n/form.d.ts +13 -0
  50. package/.server/server/plugins/engine/i18n/form.js +35 -0
  51. package/.server/server/plugins/engine/i18n/form.js.map +1 -0
  52. package/.server/server/plugins/engine/i18n/languages.d.ts +10 -0
  53. package/.server/server/plugins/engine/i18n/languages.js +19 -0
  54. package/.server/server/plugins/engine/i18n/languages.js.map +1 -0
  55. package/.server/server/plugins/engine/i18n/translations/cy.json +3 -1
  56. package/.server/server/plugins/engine/i18n/translations/en-GB.json +3 -1
  57. package/.server/server/plugins/engine/index.d.ts +3 -0
  58. package/.server/server/plugins/engine/index.js +3 -0
  59. package/.server/server/plugins/engine/index.js.map +1 -1
  60. package/.server/server/plugins/engine/models/FormModel.d.ts +0 -4
  61. package/.server/server/plugins/engine/models/FormModel.js +9 -15
  62. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  63. package/.server/server/plugins/engine/models/SummaryViewModel.js +2 -1
  64. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  65. package/.server/server/plugins/engine/outputFormatters/human/v1.js +2 -1
  66. package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
  67. package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +2 -0
  68. package/.server/server/plugins/engine/outputFormatters/machine/v2.js +2 -0
  69. package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
  70. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +1 -1
  71. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +2 -2
  72. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  73. package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +1 -1
  74. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +2 -2
  75. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  76. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +6 -3
  77. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  78. package/.server/server/plugins/engine/pageControllers/helpers/submission.d.ts +1 -1
  79. package/.server/server/plugins/engine/pageControllers/helpers/submission.js +3 -3
  80. package/.server/server/plugins/engine/pageControllers/helpers/submission.js.map +1 -1
  81. package/.server/server/plugins/engine/types/schema.js +1 -0
  82. package/.server/server/plugins/engine/types/schema.js.map +1 -1
  83. package/.server/server/plugins/engine/types.d.ts +1 -0
  84. package/.server/server/plugins/engine/types.js.map +1 -1
  85. package/.server/server/plugins/engine/views/components/ukaddressfield.html +1 -1
  86. package/.server/server/plugins/nunjucks/filters/applyUrlParam.d.ts +9 -0
  87. package/.server/server/plugins/nunjucks/filters/applyUrlParam.js +17 -0
  88. package/.server/server/plugins/nunjucks/filters/applyUrlParam.js.map +1 -0
  89. package/.server/server/plugins/nunjucks/filters/applyUrlParam.test.js +17 -0
  90. package/.server/server/plugins/nunjucks/filters/applyUrlParam.test.js.map +1 -0
  91. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  92. package/.server/server/plugins/nunjucks/filters/index.d.ts +1 -0
  93. package/.server/server/plugins/nunjucks/filters/index.js +1 -0
  94. package/.server/server/plugins/nunjucks/filters/index.js.map +1 -1
  95. package/.server/server/plugins/payment/helper.d.ts +11 -1
  96. package/.server/server/plugins/payment/helper.js +18 -2
  97. package/.server/server/plugins/payment/helper.js.map +1 -1
  98. package/.server/server/plugins/payment/helper.test.js +2 -1
  99. package/.server/server/plugins/payment/helper.test.js.map +1 -1
  100. package/.server/server/plugins/payment/service.d.ts +2 -1
  101. package/.server/server/plugins/payment/service.js +5 -2
  102. package/.server/server/plugins/payment/service.js.map +1 -1
  103. package/.server/server/plugins/payment/service.test.js +7 -3
  104. package/.server/server/plugins/payment/service.test.js.map +1 -1
  105. package/.server/server/plugins/payment/types.d.ts +4 -0
  106. package/.server/server/plugins/payment/types.js +1 -0
  107. package/.server/server/plugins/payment/types.js.map +1 -1
  108. package/.server/server/plugins/postcode-lookup/models/index.d.ts +247 -3
  109. package/.server/server/plugins/postcode-lookup/models/index.js +86 -33
  110. package/.server/server/plugins/postcode-lookup/models/index.js.map +1 -1
  111. package/.server/server/plugins/postcode-lookup/routes/index.d.ts +15 -1
  112. package/.server/server/plugins/postcode-lookup/routes/index.js +47 -15
  113. package/.server/server/plugins/postcode-lookup/routes/index.js.map +1 -1
  114. package/.server/server/plugins/postcode-lookup/routes/index.test.js +84 -0
  115. package/.server/server/plugins/postcode-lookup/routes/index.test.js.map +1 -0
  116. package/.server/server/plugins/postcode-lookup/types.d.ts +12 -0
  117. package/.server/server/plugins/postcode-lookup/types.js +6 -1
  118. package/.server/server/plugins/postcode-lookup/types.js.map +1 -1
  119. package/.server/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
  120. package/package.json +2 -1
  121. package/src/server/constants.js +2 -1
  122. package/src/server/plugins/engine/components/ComponentCollection.ts +15 -9
  123. package/src/server/plugins/engine/components/DatePartsField.ts +16 -12
  124. package/src/server/plugins/engine/components/DeclarationField.ts +6 -4
  125. package/src/server/plugins/engine/components/EastingNorthingField.ts +156 -88
  126. package/src/server/plugins/engine/components/FormComponent.ts +4 -1
  127. package/src/server/plugins/engine/components/LatLongField.ts +129 -57
  128. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +14 -11
  129. package/src/server/plugins/engine/components/MonthYearField.ts +16 -11
  130. package/src/server/plugins/engine/components/PaymentField.ts +9 -4
  131. package/src/server/plugins/engine/components/UkAddressField.ts +29 -6
  132. package/src/server/plugins/engine/components/YesNoField.ts +6 -5
  133. package/src/server/plugins/engine/components/helpers/components.ts +1 -2
  134. package/src/server/plugins/engine/components/helpers/index.ts +19 -0
  135. package/src/server/plugins/engine/helpers.ts +51 -5
  136. package/src/server/plugins/engine/i18n/createFormTranslator.ts +43 -12
  137. package/src/server/plugins/engine/i18n/extractBaseTranslations.ts +7 -1
  138. package/src/server/plugins/engine/i18n/form.ts +54 -0
  139. package/src/server/plugins/engine/i18n/languages.ts +19 -0
  140. package/src/server/plugins/engine/i18n/translations/cy.json +3 -1
  141. package/src/server/plugins/engine/i18n/translations/en-GB.json +3 -1
  142. package/src/server/plugins/engine/index.ts +6 -0
  143. package/src/server/plugins/engine/models/FormModel.ts +9 -15
  144. package/src/server/plugins/engine/models/SummaryViewModel.ts +2 -1
  145. package/src/server/plugins/engine/outputFormatters/human/v1.ts +2 -1
  146. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +3 -0
  147. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +2 -2
  148. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +2 -2
  149. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +9 -3
  150. package/src/server/plugins/engine/pageControllers/helpers/submission.ts +6 -3
  151. package/src/server/plugins/engine/types/schema.ts +1 -0
  152. package/src/server/plugins/engine/types.ts +1 -0
  153. package/src/server/plugins/engine/views/components/ukaddressfield.html +1 -1
  154. package/src/server/plugins/nunjucks/filters/applyUrlParam.js +17 -0
  155. package/src/server/plugins/nunjucks/filters/index.js +1 -0
  156. package/src/server/plugins/payment/helper.js +22 -2
  157. package/src/server/plugins/payment/service.js +6 -2
  158. package/src/server/plugins/payment/types.js +1 -0
  159. package/src/server/plugins/postcode-lookup/models/index.js +75 -43
  160. package/src/server/plugins/postcode-lookup/routes/index.js +73 -16
  161. package/src/server/plugins/postcode-lookup/types.js +6 -1
  162. package/src/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"LatLongField.js","names":["ComponentType","lowerFirst","ComponentCollection","FormComponent","isFormState","deduplicateErrorsByHref","getLocationFieldViewModel","NumberField","createLowerFirstExpression","t","tPlugin","messageTemplate","convertToLanguageMessages","DECIMAL_PRECISION","LatLongField","constructor","def","props","name","options","schema","isRequired","required","latitudeMin","latitude","min","latitudeMax","max","longitudeMin","longitude","longitudeMax","fieldLabel","label","customValidationMessages","latitudeRangeMessage","longitudeRangeMessage","latitudeMessages","longitudeMessages","collection","type","title","precision","optionalText","classes","suffix","parent","peers","formSchema","stateSchema","getFormValueFromState","state","value","isLatLong","undefined","getDisplayStringFromFormValue","_translator","getDisplayStringFromState","translator","getContextValueFromFormValue","getContextValueFromState","getViewModel","context","viewModel","getViewErrors","errors","allErrors","getErrors","isState","getAllPossibleErrors","baseErrors","template","advancedSettingsErrors","isNumber"],"sources":["../../../../../src/server/plugins/engine/components/LatLongField.ts"],"sourcesContent":["import { ComponentType, type LatLongFieldComponent } from '@defra/forms-model'\nimport { type LanguageMessages, type ObjectSchema } from 'joi'\nimport lowerFirst from 'lodash/lowerFirst.js'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n deduplicateErrorsByHref,\n getLocationFieldViewModel\n} from '~/src/server/plugins/engine/components/LocationFieldHelpers.js'\nimport { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'\nimport { createLowerFirstExpression } from '~/src/server/plugins/engine/components/helpers/index.js'\nimport {\n type LatLongState,\n type RenderContext\n} from '~/src/server/plugins/engine/components/types.js'\nimport { t as tPlugin } from '~/src/server/plugins/engine/i18n/index.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type ErrorMessageTemplateList,\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'\n\n// Precision constants\n// UK latitude/longitude requires high precision for accurate location (within ~11mm)\nconst DECIMAL_PRECISION = 7 // 7 decimal places\n\nexport class LatLongField extends FormComponent {\n declare options: LatLongFieldComponent['options']\n declare formSchema: ObjectSchema<FormPayload>\n declare stateSchema: ObjectSchema<FormState>\n declare collection: ComponentCollection\n\n constructor(\n def: LatLongFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { name, options, schema } = def\n\n const isRequired = options.required !== false\n\n // Read schema values from def.schema with fallback defaults\n const latitudeMin = schema?.latitude?.min ?? 49.85\n const latitudeMax = schema?.latitude?.max ?? 60.859\n const longitudeMin = schema?.longitude?.min ?? -13.687\n const longitudeMax = schema?.longitude?.max ?? 1.767\n\n const fieldLabel = lowerFirst(this.label)\n\n const customValidationMessages: LanguageMessages =\n convertToLanguageMessages({\n 'number.precision': tPlugin(\n 'components.latLongField.precision',\n 'en-GB'\n ),\n 'number.unsafe': tPlugin('components.latLongField.notANumber', 'en-GB')\n })\n\n const latitudeRangeMessage = tPlugin(\n 'components.latLongField.latitudeRange',\n 'en-GB',\n { fieldLabel, min: latitudeMin, max: latitudeMax }\n )\n const longitudeRangeMessage = tPlugin(\n 'components.latLongField.longitudeRange',\n 'en-GB',\n { fieldLabel, min: longitudeMin, max: longitudeMax }\n )\n\n const latitudeMessages: LanguageMessages = convertToLanguageMessages({\n ...customValidationMessages,\n 'any.required': tPlugin(\n 'components.latLongField.latitudeRequired',\n 'en-GB'\n ),\n 'number.base': tPlugin('components.latLongField.latitudeBase', 'en-GB', {\n fieldLabel\n }),\n 'number.min': latitudeRangeMessage,\n 'number.max': latitudeRangeMessage\n })\n\n const longitudeMessages: LanguageMessages = convertToLanguageMessages({\n ...customValidationMessages,\n 'any.required': tPlugin(\n 'components.latLongField.longitudeRequired',\n 'en-GB'\n ),\n 'number.base': tPlugin('components.latLongField.longitudeBase', 'en-GB', {\n fieldLabel\n }),\n 'number.min': longitudeRangeMessage,\n 'number.max': longitudeRangeMessage\n })\n\n this.collection = new ComponentCollection(\n [\n {\n type: ComponentType.NumberField,\n name: `${name}__latitude`,\n title: 'components.latLongField.latitude',\n schema: {\n min: latitudeMin,\n max: latitudeMax,\n precision: DECIMAL_PRECISION\n },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n suffix: '°',\n customValidationMessages: latitudeMessages\n }\n },\n {\n type: ComponentType.NumberField,\n name: `${name}__longitude`,\n title: 'components.latLongField.longitude',\n schema: {\n min: longitudeMin,\n max: longitudeMax,\n precision: DECIMAL_PRECISION\n },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n suffix: '°',\n customValidationMessages: longitudeMessages\n }\n }\n ],\n { ...props, parent: this },\n {\n peers: [`${name}__latitude`, `${name}__longitude`]\n }\n )\n\n this.options = options\n this.formSchema = this.collection.formSchema\n this.stateSchema = this.collection.stateSchema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const value = super.getFormValueFromState(state)\n return LatLongField.isLatLong(value) ? value : undefined\n }\n\n getDisplayStringFromFormValue(\n value: LatLongState | undefined,\n _translator: Translator\n ): string {\n if (!value) {\n return ''\n }\n\n // CYA page format: <<latvalue, langvalue>>\n return `${value.latitude}, ${value.longitude}`\n }\n\n getDisplayStringFromState(\n state: FormSubmissionState,\n translator: Translator\n ) {\n const value = this.getFormValueFromState(state)\n\n return this.getDisplayStringFromFormValue(value, translator)\n }\n\n getContextValueFromFormValue(value: LatLongState | undefined): string | null {\n if (!value) {\n return null\n }\n\n // Output format: Latitude: <<entry>>\\nLongitude: <<entry>>\n return `Latitude: ${value.latitude}\\nLongitude: ${value.longitude}`\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getContextValueFromFormValue(value)\n }\n\n getViewModel(context: RenderContext) {\n const viewModel = super.getViewModel(context)\n return getLocationFieldViewModel(this, viewModel, context)\n }\n\n getViewErrors(\n translator: Translator,\n errors?: FormSubmissionError[]\n ): FormSubmissionError[] | undefined {\n const allErrors = this.getErrors(translator, errors)\n return deduplicateErrorsByHref(allErrors)\n }\n\n isState(value?: FormStateValue | FormState) {\n return LatLongField.isLatLong(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return LatLongField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n { type: 'required', template: messageTemplate.required },\n {\n type: 'latitudeFormat',\n template: createLowerFirstExpression(\n 'Enter a valid latitude for {{lowerFirst(#title)}} like 51.519450'\n )\n },\n {\n type: 'longitudeFormat',\n template: createLowerFirstExpression(\n 'Enter a valid longitude for {{lowerFirst(#title)}} like -0.127758'\n )\n }\n ],\n advancedSettingsErrors: [\n {\n type: 'latitudeMin',\n template: createLowerFirstExpression(\n 'Latitude for {{lowerFirst(#title)}} must be between 49 and 60'\n )\n },\n {\n type: 'latitudeMax',\n template: createLowerFirstExpression(\n 'Latitude for {{lowerFirst(#title)}} must be between 49 and 60'\n )\n },\n {\n type: 'longitudeMin',\n template: createLowerFirstExpression(\n 'Longitude for {{lowerFirst(#title)}} must be between -9 and 2'\n )\n },\n {\n type: 'longitudeMax',\n template: createLowerFirstExpression(\n 'Longitude for {{lowerFirst(#title)}} must be between -9 and 2'\n )\n }\n ]\n }\n }\n\n static isLatLong(value?: FormStateValue | FormState): value is LatLongState {\n return (\n isFormState(value) &&\n NumberField.isNumber(value.latitude) &&\n NumberField.isNumber(value.longitude)\n )\n }\n}\n"],"mappings":"AAAA,SAASA,aAAa,QAAoC,oBAAoB;AAE9E,OAAOC,UAAU,MAAM,sBAAsB;AAE7C,SAASC,mBAAmB;AAC5B,SACEC,aAAa,EACbC,WAAW;AAEb,SACEC,uBAAuB,EACvBC,yBAAyB;AAE3B,SAASC,WAAW;AACpB,SAASC,0BAA0B;AAKnC,SAASC,CAAC,IAAIC,OAAO;AAErB,SAASC,eAAe;AASxB,SAASC,yBAAyB;;AAElC;AACA;AACA,MAAMC,iBAAiB,GAAG,CAAC,EAAC;;AAE5B,OAAO,MAAMC,YAAY,SAASX,aAAa,CAAC;EAM9CY,WAAWA,CACTC,GAA0B,EAC1BC,KAAqD,EACrD;IACA,KAAK,CAACD,GAAG,EAAEC,KAAK,CAAC;IAEjB,MAAM;MAAEC,IAAI;MAAEC,OAAO;MAAEC;IAAO,CAAC,GAAGJ,GAAG;IAErC,MAAMK,UAAU,GAAGF,OAAO,CAACG,QAAQ,KAAK,KAAK;;IAE7C;IACA,MAAMC,WAAW,GAAGH,MAAM,EAAEI,QAAQ,EAAEC,GAAG,IAAI,KAAK;IAClD,MAAMC,WAAW,GAAGN,MAAM,EAAEI,QAAQ,EAAEG,GAAG,IAAI,MAAM;IACnD,MAAMC,YAAY,GAAGR,MAAM,EAAES,SAAS,EAAEJ,GAAG,IAAI,CAAC,MAAM;IACtD,MAAMK,YAAY,GAAGV,MAAM,EAAES,SAAS,EAAEF,GAAG,IAAI,KAAK;IAEpD,MAAMI,UAAU,GAAG9B,UAAU,CAAC,IAAI,CAAC+B,KAAK,CAAC;IAEzC,MAAMC,wBAA0C,GAC9CrB,yBAAyB,CAAC;MACxB,kBAAkB,EAAEF,OAAO,CACzB,mCAAmC,EACnC,OACF,CAAC;MACD,eAAe,EAAEA,OAAO,CAAC,oCAAoC,EAAE,OAAO;IACxE,CAAC,CAAC;IAEJ,MAAMwB,oBAAoB,GAAGxB,OAAO,CAClC,uCAAuC,EACvC,OAAO,EACP;MAAEqB,UAAU;MAAEN,GAAG,EAAEF,WAAW;MAAEI,GAAG,EAAED;IAAY,CACnD,CAAC;IACD,MAAMS,qBAAqB,GAAGzB,OAAO,CACnC,wCAAwC,EACxC,OAAO,EACP;MAAEqB,UAAU;MAAEN,GAAG,EAAEG,YAAY;MAAED,GAAG,EAAEG;IAAa,CACrD,CAAC;IAED,MAAMM,gBAAkC,GAAGxB,yBAAyB,CAAC;MACnE,GAAGqB,wBAAwB;MAC3B,cAAc,EAAEvB,OAAO,CACrB,0CAA0C,EAC1C,OACF,CAAC;MACD,aAAa,EAAEA,OAAO,CAAC,sCAAsC,EAAE,OAAO,EAAE;QACtEqB;MACF,CAAC,CAAC;MACF,YAAY,EAAEG,oBAAoB;MAClC,YAAY,EAAEA;IAChB,CAAC,CAAC;IAEF,MAAMG,iBAAmC,GAAGzB,yBAAyB,CAAC;MACpE,GAAGqB,wBAAwB;MAC3B,cAAc,EAAEvB,OAAO,CACrB,2CAA2C,EAC3C,OACF,CAAC;MACD,aAAa,EAAEA,OAAO,CAAC,uCAAuC,EAAE,OAAO,EAAE;QACvEqB;MACF,CAAC,CAAC;MACF,YAAY,EAAEI,qBAAqB;MACnC,YAAY,EAAEA;IAChB,CAAC,CAAC;IAEF,IAAI,CAACG,UAAU,GAAG,IAAIpC,mBAAmB,CACvC,CACE;MACEqC,IAAI,EAAEvC,aAAa,CAACO,WAAW;MAC/BW,IAAI,EAAE,GAAGA,IAAI,YAAY;MACzBsB,KAAK,EAAE,kCAAkC;MACzCpB,MAAM,EAAE;QACNK,GAAG,EAAEF,WAAW;QAChBI,GAAG,EAAED,WAAW;QAChBe,SAAS,EAAE5B;MACb,CAAC;MACDM,OAAO,EAAE;QACPG,QAAQ,EAAED,UAAU;QACpBqB,YAAY,EAAE,IAAI;QAClBC,OAAO,EAAE,uBAAuB;QAChCC,MAAM,EAAE,GAAG;QACXX,wBAAwB,EAAEG;MAC5B;IACF,CAAC,EACD;MACEG,IAAI,EAAEvC,aAAa,CAACO,WAAW;MAC/BW,IAAI,EAAE,GAAGA,IAAI,aAAa;MAC1BsB,KAAK,EAAE,mCAAmC;MAC1CpB,MAAM,EAAE;QACNK,GAAG,EAAEG,YAAY;QACjBD,GAAG,EAAEG,YAAY;QACjBW,SAAS,EAAE5B;MACb,CAAC;MACDM,OAAO,EAAE;QACPG,QAAQ,EAAED,UAAU;QACpBqB,YAAY,EAAE,IAAI;QAClBC,OAAO,EAAE,uBAAuB;QAChCC,MAAM,EAAE,GAAG;QACXX,wBAAwB,EAAEI;MAC5B;IACF,CAAC,CACF,EACD;MAAE,GAAGpB,KAAK;MAAE4B,MAAM,EAAE;IAAK,CAAC,EAC1B;MACEC,KAAK,EAAE,CAAC,GAAG5B,IAAI,YAAY,EAAE,GAAGA,IAAI,aAAa;IACnD,CACF,CAAC;IAED,IAAI,CAACC,OAAO,GAAGA,OAAO;IACtB,IAAI,CAAC4B,UAAU,GAAG,IAAI,CAACT,UAAU,CAACS,UAAU;IAC5C,IAAI,CAACC,WAAW,GAAG,IAAI,CAACV,UAAU,CAACU,WAAW;EAChD;EAEAC,qBAAqBA,CAACC,KAA0B,EAAE;IAChD,MAAMC,KAAK,GAAG,KAAK,CAACF,qBAAqB,CAACC,KAAK,CAAC;IAChD,OAAOpC,YAAY,CAACsC,SAAS,CAACD,KAAK,CAAC,GAAGA,KAAK,GAAGE,SAAS;EAC1D;EAEAC,6BAA6BA,CAC3BH,KAA+B,EAC/BI,WAAuB,EACf;IACR,IAAI,CAACJ,KAAK,EAAE;MACV,OAAO,EAAE;IACX;;IAEA;IACA,OAAO,GAAGA,KAAK,CAAC3B,QAAQ,KAAK2B,KAAK,CAACtB,SAAS,EAAE;EAChD;EAEA2B,yBAAyBA,CACvBN,KAA0B,EAC1BO,UAAsB,EACtB;IACA,MAAMN,KAAK,GAAG,IAAI,CAACF,qBAAqB,CAACC,KAAK,CAAC;IAE/C,OAAO,IAAI,CAACI,6BAA6B,CAACH,KAAK,EAAEM,UAAU,CAAC;EAC9D;EAEAC,4BAA4BA,CAACP,KAA+B,EAAiB;IAC3E,IAAI,CAACA,KAAK,EAAE;MACV,OAAO,IAAI;IACb;;IAEA;IACA,OAAO,aAAaA,KAAK,CAAC3B,QAAQ,gBAAgB2B,KAAK,CAACtB,SAAS,EAAE;EACrE;EAEA8B,wBAAwBA,CAACT,KAA0B,EAAE;IACnD,MAAMC,KAAK,GAAG,IAAI,CAACF,qBAAqB,CAACC,KAAK,CAAC;IAE/C,OAAO,IAAI,CAACQ,4BAA4B,CAACP,KAAK,CAAC;EACjD;EAEAS,YAAYA,CAACC,OAAsB,EAAE;IACnC,MAAMC,SAAS,GAAG,KAAK,CAACF,YAAY,CAACC,OAAO,CAAC;IAC7C,OAAOvD,yBAAyB,CAAC,IAAI,EAAEwD,SAAS,EAAED,OAAO,CAAC;EAC5D;EAEAE,aAAaA,CACXN,UAAsB,EACtBO,MAA8B,EACK;IACnC,MAAMC,SAAS,GAAG,IAAI,CAACC,SAAS,CAACT,UAAU,EAAEO,MAAM,CAAC;IACpD,OAAO3D,uBAAuB,CAAC4D,SAAS,CAAC;EAC3C;EAEAE,OAAOA,CAAChB,KAAkC,EAAE;IAC1C,OAAOrC,YAAY,CAACsC,SAAS,CAACD,KAAK,CAAC;EACtC;;EAEA;AACF;AACA;EACEiB,oBAAoBA,CAAA,EAA6B;IAC/C,OAAOtD,YAAY,CAACsD,oBAAoB,CAAC,CAAC;EAC5C;;EAEA;AACF;AACA;EACE,OAAOA,oBAAoBA,CAAA,EAA6B;IACtD,OAAO;MACLC,UAAU,EAAE,CACV;QAAE9B,IAAI,EAAE,UAAU;QAAE+B,QAAQ,EAAE3D,eAAe,CAACW;MAAS,CAAC,EACxD;QACEiB,IAAI,EAAE,gBAAgB;QACtB+B,QAAQ,EAAE9D,0BAA0B,CAClC,kEACF;MACF,CAAC,EACD;QACE+B,IAAI,EAAE,iBAAiB;QACvB+B,QAAQ,EAAE9D,0BAA0B,CAClC,mEACF;MACF,CAAC,CACF;MACD+D,sBAAsB,EAAE,CACtB;QACEhC,IAAI,EAAE,aAAa;QACnB+B,QAAQ,EAAE9D,0BAA0B,CAClC,+DACF;MACF,CAAC,EACD;QACE+B,IAAI,EAAE,aAAa;QACnB+B,QAAQ,EAAE9D,0BAA0B,CAClC,+DACF;MACF,CAAC,EACD;QACE+B,IAAI,EAAE,cAAc;QACpB+B,QAAQ,EAAE9D,0BAA0B,CAClC,+DACF;MACF,CAAC,EACD;QACE+B,IAAI,EAAE,cAAc;QACpB+B,QAAQ,EAAE9D,0BAA0B,CAClC,+DACF;MACF,CAAC;IAEL,CAAC;EACH;EAEA,OAAO4C,SAASA,CAACD,KAAkC,EAAyB;IAC1E,OACE/C,WAAW,CAAC+C,KAAK,CAAC,IAClB5C,WAAW,CAACiE,QAAQ,CAACrB,KAAK,CAAC3B,QAAQ,CAAC,IACpCjB,WAAW,CAACiE,QAAQ,CAACrB,KAAK,CAACtB,SAAS,CAAC;EAEzC;AACF","ignoreList":[]}
1
+ {"version":3,"file":"LatLongField.js","names":["ComponentType","lowerFirst","ComponentCollection","FormComponent","isFormState","deduplicateErrorsByHref","getLocationFieldViewModel","NumberField","createLowerFirstExpression","getTranslatedLabel","t","tPlugin","messageTemplate","convertToLanguageMessages","DECIMAL_PRECISION","LatLongField","constructor","def","props","name","options","schema","isRequired","required","latitudeMin","latitudeMax","longitudeMin","longitudeMax","getMinMax","latitudeMessages","longitudeMessages","buildErrorMessages","label","collection","type","title","min","max","precision","optionalText","classes","suffix","customValidationMessages","parent","peers","formSchema","stateSchema","getFormValueFromState","state","value","isLatLong","undefined","getDisplayStringFromFormValue","translator","latitude","longitude","getDisplayStringFromState","getContextValueFromFormValue","getContextValueFromState","getViewModel","context","viewModel","getViewErrors","errors","allErrors","getErrors","isState","getValidationMessagesOverride","translatedLabel","language","getAllPossibleErrors","baseErrors","template","advancedSettingsErrors","isNumber","fieldLabel","latitudeRangeMessage","longitudeRangeMessage"],"sources":["../../../../../src/server/plugins/engine/components/LatLongField.ts"],"sourcesContent":["import { ComponentType, type LatLongFieldComponent } from '@defra/forms-model'\nimport { type LanguageMessages, type ObjectSchema } from 'joi'\nimport lowerFirst from 'lodash/lowerFirst.js'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n deduplicateErrorsByHref,\n getLocationFieldViewModel\n} from '~/src/server/plugins/engine/components/LocationFieldHelpers.js'\nimport { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'\nimport {\n createLowerFirstExpression,\n getTranslatedLabel\n} from '~/src/server/plugins/engine/components/helpers/index.js'\nimport {\n type LatLongState,\n type RenderContext\n} from '~/src/server/plugins/engine/components/types.js'\nimport { t as tPlugin } from '~/src/server/plugins/engine/i18n/index.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type ErrorMessageTemplateList,\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'\n\n// Precision constants\n// UK latitude/longitude requires high precision for accurate location (within ~11mm)\nconst DECIMAL_PRECISION = 7 // 7 decimal places\n\nexport class LatLongField extends FormComponent {\n declare options: LatLongFieldComponent['options']\n declare formSchema: ObjectSchema<FormPayload>\n declare stateSchema: ObjectSchema<FormState>\n declare collection: ComponentCollection\n\n constructor(\n def: LatLongFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { name, options, schema } = def\n\n const isRequired = options.required !== false\n\n const { latitudeMin, latitudeMax, longitudeMin, longitudeMax } =\n LatLongField.getMinMax(schema)\n\n const { latitudeMessages, longitudeMessages } =\n LatLongField.buildErrorMessages(\n this.label,\n latitudeMin,\n latitudeMax,\n longitudeMin,\n longitudeMax,\n 'en-GB'\n )\n\n this.collection = new ComponentCollection(\n [\n {\n type: ComponentType.NumberField,\n name: `${name}__latitude`,\n title: 'components.latLongField.latitude',\n schema: {\n min: latitudeMin,\n max: latitudeMax,\n precision: DECIMAL_PRECISION\n },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n suffix: '°',\n customValidationMessages: latitudeMessages\n }\n },\n {\n type: ComponentType.NumberField,\n name: `${name}__longitude`,\n title: 'components.latLongField.longitude',\n schema: {\n min: longitudeMin,\n max: longitudeMax,\n precision: DECIMAL_PRECISION\n },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n suffix: '°',\n customValidationMessages: longitudeMessages\n }\n }\n ],\n { ...props, parent: this },\n {\n peers: [`${name}__latitude`, `${name}__longitude`]\n }\n )\n\n this.options = options\n this.formSchema = this.collection.formSchema\n this.stateSchema = this.collection.stateSchema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const value = super.getFormValueFromState(state)\n return LatLongField.isLatLong(value) ? value : undefined\n }\n\n getDisplayStringFromFormValue(\n value: LatLongState | undefined,\n translator: Translator\n ): string {\n if (!value) {\n return ''\n }\n\n const { t } = translator\n\n // Output format: Latitude: <<entry>>\\nLongitude: <<entry>>\n return `${t('components.latLongField.latitude')}: ${value.latitude}\\n${t('components.latLongField.longitude')}: ${value.longitude}`\n }\n\n getDisplayStringFromState(\n state: FormSubmissionState,\n translator: Translator\n ) {\n const value = this.getFormValueFromState(state)\n\n return this.getDisplayStringFromFormValue(value, translator)\n }\n\n getContextValueFromFormValue(value: LatLongState | undefined): string | null {\n if (!value) {\n return null\n }\n\n return `${value.latitude}, ${value.longitude}`\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getContextValueFromFormValue(value)\n }\n\n getViewModel(context: RenderContext) {\n const viewModel = super.getViewModel(context)\n return getLocationFieldViewModel(this, viewModel, context)\n }\n\n getViewErrors(\n translator: Translator,\n errors?: FormSubmissionError[]\n ): FormSubmissionError[] | undefined {\n const allErrors = this.getErrors(translator, errors)\n return deduplicateErrorsByHref(allErrors)\n }\n\n isState(value?: FormStateValue | FormState) {\n return LatLongField.isLatLong(value)\n }\n\n getValidationMessagesOverride(translator: Translator) {\n const def = this.def as LatLongFieldComponent\n const translatedLabel = getTranslatedLabel(def, translator)\n const { latitudeMin, latitudeMax, longitudeMin, longitudeMax } =\n LatLongField.getMinMax(def.schema)\n const { latitudeMessages, longitudeMessages } =\n LatLongField.buildErrorMessages(\n translatedLabel,\n latitudeMin,\n latitudeMax,\n longitudeMin,\n longitudeMax,\n translator.language\n )\n return {\n [`${this.name}__latitude`]: latitudeMessages,\n [`${this.name}__longitude`]: longitudeMessages\n }\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return LatLongField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n { type: 'required', template: messageTemplate.required },\n {\n type: 'latitudeFormat',\n template: createLowerFirstExpression(\n 'Enter a valid latitude for {{lowerFirst(#title)}} like 51.519450'\n )\n },\n {\n type: 'longitudeFormat',\n template: createLowerFirstExpression(\n 'Enter a valid longitude for {{lowerFirst(#title)}} like -0.127758'\n )\n }\n ],\n advancedSettingsErrors: [\n {\n type: 'latitudeMin',\n template: createLowerFirstExpression(\n 'Latitude for {{lowerFirst(#title)}} must be between 49 and 60'\n )\n },\n {\n type: 'latitudeMax',\n template: createLowerFirstExpression(\n 'Latitude for {{lowerFirst(#title)}} must be between 49 and 60'\n )\n },\n {\n type: 'longitudeMin',\n template: createLowerFirstExpression(\n 'Longitude for {{lowerFirst(#title)}} must be between -9 and 2'\n )\n },\n {\n type: 'longitudeMax',\n template: createLowerFirstExpression(\n 'Longitude for {{lowerFirst(#title)}} must be between -9 and 2'\n )\n }\n ]\n }\n }\n\n static isLatLong(value?: FormStateValue | FormState): value is LatLongState {\n return (\n isFormState(value) &&\n NumberField.isNumber(value.latitude) &&\n NumberField.isNumber(value.longitude)\n )\n }\n\n static buildErrorMessages(\n label: string,\n latitudeMin: number,\n latitudeMax: number,\n longitudeMin: number,\n longitudeMax: number,\n language: string\n ) {\n const fieldLabel = lowerFirst(label)\n\n const customValidationMessages: LanguageMessages =\n convertToLanguageMessages({\n 'number.precision': tPlugin(\n 'components.latLongField.precision',\n language\n ),\n 'number.unsafe': tPlugin('components.latLongField.notANumber', language)\n })\n\n const latitudeRangeMessage = tPlugin(\n 'components.latLongField.latitudeRange',\n language,\n { fieldLabel, min: latitudeMin, max: latitudeMax }\n )\n const longitudeRangeMessage = tPlugin(\n 'components.latLongField.longitudeRange',\n language,\n { fieldLabel, min: longitudeMin, max: longitudeMax }\n )\n\n const latitudeMessages: LanguageMessages = convertToLanguageMessages({\n ...customValidationMessages,\n 'any.required': tPlugin(\n 'components.latLongField.latitudeRequired',\n language\n ),\n 'number.base': tPlugin('components.latLongField.latitudeBase', language, {\n fieldLabel\n }),\n 'number.min': latitudeRangeMessage,\n 'number.max': latitudeRangeMessage\n })\n\n const longitudeMessages: LanguageMessages = convertToLanguageMessages({\n ...customValidationMessages,\n 'any.required': tPlugin(\n 'components.latLongField.longitudeRequired',\n language\n ),\n 'number.base': tPlugin(\n 'components.latLongField.longitudeBase',\n language,\n {\n fieldLabel\n }\n ),\n 'number.min': longitudeRangeMessage,\n 'number.max': longitudeRangeMessage\n })\n\n return {\n latitudeMessages,\n longitudeMessages\n }\n }\n\n // Read schema values from def.schema with fallback defaults\n static getMinMax(\n schema:\n | {\n latitude?: {\n min?: number\n max?: number\n }\n longitude?: {\n min?: number\n max?: number\n }\n }\n | undefined\n ) {\n return {\n latitudeMin: schema?.latitude?.min ?? 49.85,\n latitudeMax: schema?.latitude?.max ?? 60.859,\n longitudeMin: schema?.longitude?.min ?? -13.687,\n longitudeMax: schema?.longitude?.max ?? 1.767\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -4,10 +4,11 @@ import { type GeospatialField } from '../../../../server/plugins/engine/componen
4
4
  import { type LatLongField } from '../../../../server/plugins/engine/components/LatLongField.js';
5
5
  import { type OsGridRefField } from '../../../../server/plugins/engine/components/OsGridRefField.js';
6
6
  import { type DateInputItem, type Label, type RenderContext, type ViewModel } from '../../../../server/plugins/engine/components/types.js';
7
+ import { type Translator } from '../../../../server/plugins/engine/i18n/types.js';
7
8
  import { type FormSubmissionError, type FormValue } from '../../../../server/plugins/engine/types.js';
8
9
  export type LocationField = InstanceType<typeof EastingNorthingField> | InstanceType<typeof LatLongField>;
9
- export declare function joinWithAnd(items: string[]): string;
10
- export declare function formatErrorList(messages: string[]): string;
10
+ export declare function joinWithAnd(items: string[], translator: Translator): string;
11
+ export declare function formatErrorList(messages: string[], translator: Translator): string;
11
12
  export declare function mergeCssClasses(...classNames: (string | undefined)[]): string | undefined;
12
13
  export declare function deduplicateErrorsByHref(errors?: FormSubmissionError[]): FormSubmissionError[] | undefined;
13
14
  export declare function getMapLayers(component: GeospatialField | OsGridRefField | LatLongField | EastingNorthingField): string;
@@ -7,22 +7,21 @@ import { isFormValue } from "./FormComponent.js";
7
7
  * - "latitude must be..." -> "latitude must be..."
8
8
  */
9
9
  function lowercaseMessageStart(message) {
10
- if (message.startsWith('Enter ')) {
11
- return 'enter' + message.slice(5);
12
- }
13
-
14
- // Lowercase first character for any other message
10
+ // Lowercase first character for a message
15
11
  return message.charAt(0).toLowerCase() + message.slice(1);
16
12
  }
17
- export function joinWithAnd(items) {
13
+ export function joinWithAnd(items, translator) {
14
+ const {
15
+ t
16
+ } = translator;
18
17
  if (items.length === 2) {
19
- return `${items[0]} and ${items[1]}`;
18
+ return `${items[0]} ${t('common.and')} ${items[1]}`;
20
19
  }
21
20
  const leading = items.slice(0, -1).join(', ');
22
21
  const last = items[items.length - 1];
23
- return `${leading} and ${last}`;
22
+ return `${leading} ${t('common.and')} ${last}`;
24
23
  }
25
- export function formatErrorList(messages) {
24
+ export function formatErrorList(messages, translator) {
26
25
  if (!messages.length) {
27
26
  return '';
28
27
  }
@@ -30,7 +29,7 @@ export function formatErrorList(messages) {
30
29
  return messages[0];
31
30
  }
32
31
  const formattedMessages = messages.map((msg, index) => index === 0 ? msg : lowercaseMessageStart(msg));
33
- return joinWithAnd(formattedMessages);
32
+ return joinWithAnd(formattedMessages, translator);
34
33
  }
35
34
  export function mergeCssClasses(...classNames) {
36
35
  const tokens = classNames.flatMap(name => name?.split(/\s+/) ?? []).map(token => token.trim()).filter(Boolean);
@@ -110,7 +109,7 @@ export function getLocationFieldViewModel(component, viewModel, context) {
110
109
  viewModel.showFieldsetError = showFieldsetError;
111
110
  if (hasFieldErrors) {
112
111
  viewModel.errorMessage = {
113
- text: fieldErrors.length === 1 ? fieldErrors[0] : formatErrorList(fieldErrors)
112
+ text: fieldErrors.length === 1 ? fieldErrors[0] : formatErrorList(fieldErrors, context.translator)
114
113
  };
115
114
  }
116
115
  const fieldset = existingFieldset ?? {
@@ -1 +1 @@
1
- {"version":3,"file":"LocationFieldHelpers.js","names":["isFormValue","lowercaseMessageStart","message","startsWith","slice","charAt","toLowerCase","joinWithAnd","items","length","leading","join","last","formatErrorList","messages","formattedMessages","map","msg","index","mergeCssClasses","classNames","tokens","flatMap","name","split","token","trim","filter","Boolean","undefined","Array","from","Set","deduplicateErrorsByHref","errors","error","self","findIndex","err","href","getMapLayers","component","mapLayers","options","layers","sssi","push","getLocationFieldViewModel","viewModel","context","collection","fieldset","existingFieldset","label","subViewModels","getViewModel","fieldErrors","forEach","model","errorMessage","text","hasFieldErrors","type","value","classes","prefix","suffix","toString","baseItem","id","showFieldsetError","legend","result","instructionText","createLocationFieldValidator","payload","helpers","values","getFormValueFromState","getStateFromValidForm","missing","keys","key","isState","required"],"sources":["../../../../../src/server/plugins/engine/components/LocationFieldHelpers.ts"],"sourcesContent":["import { type Context, type CustomValidator } from 'joi'\n\nimport { type EastingNorthingField } from '~/src/server/plugins/engine/components/EastingNorthingField.js'\nimport { isFormValue } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type GeospatialField } from '~/src/server/plugins/engine/components/GeospatialField.js'\nimport { type LatLongField } from '~/src/server/plugins/engine/components/LatLongField.js'\nimport { type OsGridRefField } from '~/src/server/plugins/engine/components/OsGridRefField.js'\nimport {\n type DateInputItem,\n type Label,\n type RenderContext,\n type ViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport {\n type FormPayload,\n type FormSubmissionError,\n type FormValue\n} from '~/src/server/plugins/engine/types.js'\n\nexport type LocationField =\n | InstanceType<typeof EastingNorthingField>\n | InstanceType<typeof LatLongField>\n\n/**\n * Lowercases the start of a message for concatenation.\n * Handles patterns like:\n * - \"Enter latitude\" -> \"enter latitude\"\n * - \"Enter a valid latitude...\" -> \"enter a valid latitude...\"\n * - \"latitude must be...\" -> \"latitude must be...\"\n */\nfunction lowercaseMessageStart(message: string): string {\n if (message.startsWith('Enter ')) {\n return 'enter' + message.slice(5)\n }\n\n // Lowercase first character for any other message\n return message.charAt(0).toLowerCase() + message.slice(1)\n}\n\nexport function joinWithAnd(items: string[]): string {\n if (items.length === 2) {\n return `${items[0]} and ${items[1]}`\n }\n\n const leading = items.slice(0, -1).join(', ')\n const last = items[items.length - 1]\n return `${leading} and ${last}`\n}\n\nexport function formatErrorList(messages: string[]): string {\n if (!messages.length) {\n return ''\n }\n\n if (messages.length === 1) {\n return messages[0]\n }\n\n const formattedMessages = messages.map((msg, index) =>\n index === 0 ? msg : lowercaseMessageStart(msg)\n )\n\n return joinWithAnd(formattedMessages)\n}\n\nexport function mergeCssClasses(...classNames: (string | undefined)[]) {\n const tokens = classNames\n .flatMap((name) => name?.split(/\\s+/) ?? [])\n .map((token) => token.trim())\n .filter(Boolean)\n\n if (!tokens.length) {\n return undefined\n }\n\n return Array.from(new Set(tokens)).join(' ')\n}\n\nexport function deduplicateErrorsByHref(\n errors?: FormSubmissionError[]\n): FormSubmissionError[] | undefined {\n if (!errors?.length) {\n return undefined\n }\n\n return errors.filter(\n (error, index, self) =>\n index === self.findIndex((err) => err.href === error.href)\n )\n}\n\nexport function getMapLayers(\n component:\n | GeospatialField\n | OsGridRefField\n | LatLongField\n | EastingNorthingField\n) {\n const { mapLayers } = component.options\n const layers = []\n\n if (mapLayers?.sssi) {\n layers.push('sssi')\n }\n\n return layers.join(',')\n}\n\nexport function getLocationFieldViewModel(\n component: LocationField,\n viewModel: ViewModel & {\n label: Label\n id: string\n name: string\n value: FormValue\n },\n context: RenderContext\n) {\n const { collection } = component\n const { fieldset: existingFieldset, label } = viewModel\n\n const subViewModels = collection.getViewModel(context)\n\n const fieldErrors: string[] = []\n subViewModels.forEach(({ model }) => {\n if (model.errorMessage?.text) {\n fieldErrors.push(model.errorMessage.text)\n }\n })\n\n const hasFieldErrors = fieldErrors.length > 0\n\n const items: DateInputItem[] = subViewModels.map(\n ({ model }): DateInputItem => {\n let { label, type, value, classes, prefix, suffix, errorMessage } = model\n\n if (label) {\n label.toString = () => label.text\n }\n\n if (!isFormValue(value)) {\n value = undefined\n }\n\n const baseItem: DateInputItem = {\n label,\n id: model.id,\n name: model.name,\n type,\n value,\n classes: mergeCssClasses(\n classes,\n hasFieldErrors ? 'govuk-input--error' : undefined\n ),\n prefix,\n suffix\n }\n\n if (!hasFieldErrors && errorMessage) {\n baseItem.errorMessage = errorMessage\n }\n\n return baseItem\n }\n )\n\n const showFieldsetError =\n hasFieldErrors || Boolean(viewModel.errorMessage?.text)\n\n viewModel.showFieldsetError = showFieldsetError\n\n if (hasFieldErrors) {\n viewModel.errorMessage = {\n text:\n fieldErrors.length === 1 ? fieldErrors[0] : formatErrorList(fieldErrors)\n }\n }\n\n const fieldset = existingFieldset ?? {\n legend: {\n text: label.text,\n classes: 'govuk-fieldset__legend--m'\n }\n }\n\n const result = {\n ...viewModel,\n fieldset,\n items,\n mapLayers: getMapLayers(component)\n }\n\n if (component.options.instructionText) {\n return {\n ...result,\n instructionText: component.options.instructionText\n }\n }\n\n return result\n}\n\n/**\n * Validator factory for location-based fields.\n * This creates a validator that ensures all required fields are present.\n */\nexport function createLocationFieldValidator(\n component: LocationField\n): CustomValidator {\n return (payload: FormPayload, helpers) => {\n const { collection, name, options } = component\n\n const values = component.getFormValueFromState(\n component.getStateFromValidForm(payload)\n )\n\n const context: Context = {\n missing: collection.keys,\n key: name\n }\n\n if (!component.isState(values)) {\n return options.required !== false\n ? helpers.error('object.required', context)\n : payload\n }\n\n return payload\n }\n}\n"],"mappings":"AAGA,SAASA,WAAW;AAoBpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASC,qBAAqBA,CAACC,OAAe,EAAU;EACtD,IAAIA,OAAO,CAACC,UAAU,CAAC,QAAQ,CAAC,EAAE;IAChC,OAAO,OAAO,GAAGD,OAAO,CAACE,KAAK,CAAC,CAAC,CAAC;EACnC;;EAEA;EACA,OAAOF,OAAO,CAACG,MAAM,CAAC,CAAC,CAAC,CAACC,WAAW,CAAC,CAAC,GAAGJ,OAAO,CAACE,KAAK,CAAC,CAAC,CAAC;AAC3D;AAEA,OAAO,SAASG,WAAWA,CAACC,KAAe,EAAU;EACnD,IAAIA,KAAK,CAACC,MAAM,KAAK,CAAC,EAAE;IACtB,OAAO,GAAGD,KAAK,CAAC,CAAC,CAAC,QAAQA,KAAK,CAAC,CAAC,CAAC,EAAE;EACtC;EAEA,MAAME,OAAO,GAAGF,KAAK,CAACJ,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAACO,IAAI,CAAC,IAAI,CAAC;EAC7C,MAAMC,IAAI,GAAGJ,KAAK,CAACA,KAAK,CAACC,MAAM,GAAG,CAAC,CAAC;EACpC,OAAO,GAAGC,OAAO,QAAQE,IAAI,EAAE;AACjC;AAEA,OAAO,SAASC,eAAeA,CAACC,QAAkB,EAAU;EAC1D,IAAI,CAACA,QAAQ,CAACL,MAAM,EAAE;IACpB,OAAO,EAAE;EACX;EAEA,IAAIK,QAAQ,CAACL,MAAM,KAAK,CAAC,EAAE;IACzB,OAAOK,QAAQ,CAAC,CAAC,CAAC;EACpB;EAEA,MAAMC,iBAAiB,GAAGD,QAAQ,CAACE,GAAG,CAAC,CAACC,GAAG,EAAEC,KAAK,KAChDA,KAAK,KAAK,CAAC,GAAGD,GAAG,GAAGhB,qBAAqB,CAACgB,GAAG,CAC/C,CAAC;EAED,OAAOV,WAAW,CAACQ,iBAAiB,CAAC;AACvC;AAEA,OAAO,SAASI,eAAeA,CAAC,GAAGC,UAAkC,EAAE;EACrE,MAAMC,MAAM,GAAGD,UAAU,CACtBE,OAAO,CAAEC,IAAI,IAAKA,IAAI,EAAEC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAC3CR,GAAG,CAAES,KAAK,IAAKA,KAAK,CAACC,IAAI,CAAC,CAAC,CAAC,CAC5BC,MAAM,CAACC,OAAO,CAAC;EAElB,IAAI,CAACP,MAAM,CAACZ,MAAM,EAAE;IAClB,OAAOoB,SAAS;EAClB;EAEA,OAAOC,KAAK,CAACC,IAAI,CAAC,IAAIC,GAAG,CAACX,MAAM,CAAC,CAAC,CAACV,IAAI,CAAC,GAAG,CAAC;AAC9C;AAEA,OAAO,SAASsB,uBAAuBA,CACrCC,MAA8B,EACK;EACnC,IAAI,CAACA,MAAM,EAAEzB,MAAM,EAAE;IACnB,OAAOoB,SAAS;EAClB;EAEA,OAAOK,MAAM,CAACP,MAAM,CAClB,CAACQ,KAAK,EAAEjB,KAAK,EAAEkB,IAAI,KACjBlB,KAAK,KAAKkB,IAAI,CAACC,SAAS,CAAEC,GAAG,IAAKA,GAAG,CAACC,IAAI,KAAKJ,KAAK,CAACI,IAAI,CAC7D,CAAC;AACH;AAEA,OAAO,SAASC,YAAYA,CAC1BC,SAIwB,EACxB;EACA,MAAM;IAAEC;EAAU,CAAC,GAAGD,SAAS,CAACE,OAAO;EACvC,MAAMC,MAAM,GAAG,EAAE;EAEjB,IAAIF,SAAS,EAAEG,IAAI,EAAE;IACnBD,MAAM,CAACE,IAAI,CAAC,MAAM,CAAC;EACrB;EAEA,OAAOF,MAAM,CAACjC,IAAI,CAAC,GAAG,CAAC;AACzB;AAEA,OAAO,SAASoC,yBAAyBA,CACvCN,SAAwB,EACxBO,SAKC,EACDC,OAAsB,EACtB;EACA,MAAM;IAAEC;EAAW,CAAC,GAAGT,SAAS;EAChC,MAAM;IAAEU,QAAQ,EAAEC,gBAAgB;IAAEC;EAAM,CAAC,GAAGL,SAAS;EAEvD,MAAMM,aAAa,GAAGJ,UAAU,CAACK,YAAY,CAACN,OAAO,CAAC;EAEtD,MAAMO,WAAqB,GAAG,EAAE;EAChCF,aAAa,CAACG,OAAO,CAAC,CAAC;IAAEC;EAAM,CAAC,KAAK;IACnC,IAAIA,KAAK,CAACC,YAAY,EAAEC,IAAI,EAAE;MAC5BJ,WAAW,CAACV,IAAI,CAACY,KAAK,CAACC,YAAY,CAACC,IAAI,CAAC;IAC3C;EACF,CAAC,CAAC;EAEF,MAAMC,cAAc,GAAGL,WAAW,CAAC/C,MAAM,GAAG,CAAC;EAE7C,MAAMD,KAAsB,GAAG8C,aAAa,CAACtC,GAAG,CAC9C,CAAC;IAAE0C;EAAM,CAAC,KAAoB;IAC5B,IAAI;MAAEL,KAAK;MAAES,IAAI;MAAEC,KAAK;MAAEC,OAAO;MAAEC,MAAM;MAAEC,MAAM;MAAEP;IAAa,CAAC,GAAGD,KAAK;IAEzE,IAAIL,KAAK,EAAE;MACTA,KAAK,CAACc,QAAQ,GAAG,MAAMd,KAAK,CAACO,IAAI;IACnC;IAEA,IAAI,CAAC5D,WAAW,CAAC+D,KAAK,CAAC,EAAE;MACvBA,KAAK,GAAGlC,SAAS;IACnB;IAEA,MAAMuC,QAAuB,GAAG;MAC9Bf,KAAK;MACLgB,EAAE,EAAEX,KAAK,CAACW,EAAE;MACZ9C,IAAI,EAAEmC,KAAK,CAACnC,IAAI;MAChBuC,IAAI;MACJC,KAAK;MACLC,OAAO,EAAE7C,eAAe,CACtB6C,OAAO,EACPH,cAAc,GAAG,oBAAoB,GAAGhC,SAC1C,CAAC;MACDoC,MAAM;MACNC;IACF,CAAC;IAED,IAAI,CAACL,cAAc,IAAIF,YAAY,EAAE;MACnCS,QAAQ,CAACT,YAAY,GAAGA,YAAY;IACtC;IAEA,OAAOS,QAAQ;EACjB,CACF,CAAC;EAED,MAAME,iBAAiB,GACrBT,cAAc,IAAIjC,OAAO,CAACoB,SAAS,CAACW,YAAY,EAAEC,IAAI,CAAC;EAEzDZ,SAAS,CAACsB,iBAAiB,GAAGA,iBAAiB;EAE/C,IAAIT,cAAc,EAAE;IAClBb,SAAS,CAACW,YAAY,GAAG;MACvBC,IAAI,EACFJ,WAAW,CAAC/C,MAAM,KAAK,CAAC,GAAG+C,WAAW,CAAC,CAAC,CAAC,GAAG3C,eAAe,CAAC2C,WAAW;IAC3E,CAAC;EACH;EAEA,MAAML,QAAQ,GAAGC,gBAAgB,IAAI;IACnCmB,MAAM,EAAE;MACNX,IAAI,EAAEP,KAAK,CAACO,IAAI;MAChBI,OAAO,EAAE;IACX;EACF,CAAC;EAED,MAAMQ,MAAM,GAAG;IACb,GAAGxB,SAAS;IACZG,QAAQ;IACR3C,KAAK;IACLkC,SAAS,EAAEF,YAAY,CAACC,SAAS;EACnC,CAAC;EAED,IAAIA,SAAS,CAACE,OAAO,CAAC8B,eAAe,EAAE;IACrC,OAAO;MACL,GAAGD,MAAM;MACTC,eAAe,EAAEhC,SAAS,CAACE,OAAO,CAAC8B;IACrC,CAAC;EACH;EAEA,OAAOD,MAAM;AACf;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASE,4BAA4BA,CAC1CjC,SAAwB,EACP;EACjB,OAAO,CAACkC,OAAoB,EAAEC,OAAO,KAAK;IACxC,MAAM;MAAE1B,UAAU;MAAE3B,IAAI;MAAEoB;IAAQ,CAAC,GAAGF,SAAS;IAE/C,MAAMoC,MAAM,GAAGpC,SAAS,CAACqC,qBAAqB,CAC5CrC,SAAS,CAACsC,qBAAqB,CAACJ,OAAO,CACzC,CAAC;IAED,MAAM1B,OAAgB,GAAG;MACvB+B,OAAO,EAAE9B,UAAU,CAAC+B,IAAI;MACxBC,GAAG,EAAE3D;IACP,CAAC;IAED,IAAI,CAACkB,SAAS,CAAC0C,OAAO,CAACN,MAAM,CAAC,EAAE;MAC9B,OAAOlC,OAAO,CAACyC,QAAQ,KAAK,KAAK,GAC7BR,OAAO,CAACzC,KAAK,CAAC,iBAAiB,EAAEc,OAAO,CAAC,GACzC0B,OAAO;IACb;IAEA,OAAOA,OAAO;EAChB,CAAC;AACH","ignoreList":[]}
1
+ {"version":3,"file":"LocationFieldHelpers.js","names":["isFormValue","lowercaseMessageStart","message","charAt","toLowerCase","slice","joinWithAnd","items","translator","t","length","leading","join","last","formatErrorList","messages","formattedMessages","map","msg","index","mergeCssClasses","classNames","tokens","flatMap","name","split","token","trim","filter","Boolean","undefined","Array","from","Set","deduplicateErrorsByHref","errors","error","self","findIndex","err","href","getMapLayers","component","mapLayers","options","layers","sssi","push","getLocationFieldViewModel","viewModel","context","collection","fieldset","existingFieldset","label","subViewModels","getViewModel","fieldErrors","forEach","model","errorMessage","text","hasFieldErrors","type","value","classes","prefix","suffix","toString","baseItem","id","showFieldsetError","legend","result","instructionText","createLocationFieldValidator","payload","helpers","values","getFormValueFromState","getStateFromValidForm","missing","keys","key","isState","required"],"sources":["../../../../../src/server/plugins/engine/components/LocationFieldHelpers.ts"],"sourcesContent":["import { type Context, type CustomValidator } from 'joi'\n\nimport { type EastingNorthingField } from '~/src/server/plugins/engine/components/EastingNorthingField.js'\nimport { isFormValue } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type GeospatialField } from '~/src/server/plugins/engine/components/GeospatialField.js'\nimport { type LatLongField } from '~/src/server/plugins/engine/components/LatLongField.js'\nimport { type OsGridRefField } from '~/src/server/plugins/engine/components/OsGridRefField.js'\nimport {\n type DateInputItem,\n type Label,\n type RenderContext,\n type ViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport {\n type FormPayload,\n type FormSubmissionError,\n type FormValue\n} from '~/src/server/plugins/engine/types.js'\n\nexport type LocationField =\n | InstanceType<typeof EastingNorthingField>\n | InstanceType<typeof LatLongField>\n\n/**\n * Lowercases the start of a message for concatenation.\n * Handles patterns like:\n * - \"Enter latitude\" -> \"enter latitude\"\n * - \"Enter a valid latitude...\" -> \"enter a valid latitude...\"\n * - \"latitude must be...\" -> \"latitude must be...\"\n */\nfunction lowercaseMessageStart(message: string): string {\n // Lowercase first character for a message\n return message.charAt(0).toLowerCase() + message.slice(1)\n}\n\nexport function joinWithAnd(items: string[], translator: Translator): string {\n const { t } = translator\n if (items.length === 2) {\n return `${items[0]} ${t('common.and')} ${items[1]}`\n }\n\n const leading = items.slice(0, -1).join(', ')\n const last = items[items.length - 1]\n return `${leading} ${t('common.and')} ${last}`\n}\n\nexport function formatErrorList(\n messages: string[],\n translator: Translator\n): string {\n if (!messages.length) {\n return ''\n }\n\n if (messages.length === 1) {\n return messages[0]\n }\n\n const formattedMessages = messages.map((msg, index) =>\n index === 0 ? msg : lowercaseMessageStart(msg)\n )\n\n return joinWithAnd(formattedMessages, translator)\n}\n\nexport function mergeCssClasses(...classNames: (string | undefined)[]) {\n const tokens = classNames\n .flatMap((name) => name?.split(/\\s+/) ?? [])\n .map((token) => token.trim())\n .filter(Boolean)\n\n if (!tokens.length) {\n return undefined\n }\n\n return Array.from(new Set(tokens)).join(' ')\n}\n\nexport function deduplicateErrorsByHref(\n errors?: FormSubmissionError[]\n): FormSubmissionError[] | undefined {\n if (!errors?.length) {\n return undefined\n }\n\n return errors.filter(\n (error, index, self) =>\n index === self.findIndex((err) => err.href === error.href)\n )\n}\n\nexport function getMapLayers(\n component:\n | GeospatialField\n | OsGridRefField\n | LatLongField\n | EastingNorthingField\n) {\n const { mapLayers } = component.options\n const layers = []\n\n if (mapLayers?.sssi) {\n layers.push('sssi')\n }\n\n return layers.join(',')\n}\n\nexport function getLocationFieldViewModel(\n component: LocationField,\n viewModel: ViewModel & {\n label: Label\n id: string\n name: string\n value: FormValue\n },\n context: RenderContext\n) {\n const { collection } = component\n const { fieldset: existingFieldset, label } = viewModel\n\n const subViewModels = collection.getViewModel(context)\n\n const fieldErrors: string[] = []\n subViewModels.forEach(({ model }) => {\n if (model.errorMessage?.text) {\n fieldErrors.push(model.errorMessage.text)\n }\n })\n\n const hasFieldErrors = fieldErrors.length > 0\n\n const items: DateInputItem[] = subViewModels.map(\n ({ model }): DateInputItem => {\n let { label, type, value, classes, prefix, suffix, errorMessage } = model\n\n if (label) {\n label.toString = () => label.text\n }\n\n if (!isFormValue(value)) {\n value = undefined\n }\n\n const baseItem: DateInputItem = {\n label,\n id: model.id,\n name: model.name,\n type,\n value,\n classes: mergeCssClasses(\n classes,\n hasFieldErrors ? 'govuk-input--error' : undefined\n ),\n prefix,\n suffix\n }\n\n if (!hasFieldErrors && errorMessage) {\n baseItem.errorMessage = errorMessage\n }\n\n return baseItem\n }\n )\n\n const showFieldsetError =\n hasFieldErrors || Boolean(viewModel.errorMessage?.text)\n\n viewModel.showFieldsetError = showFieldsetError\n\n if (hasFieldErrors) {\n viewModel.errorMessage = {\n text:\n fieldErrors.length === 1\n ? fieldErrors[0]\n : formatErrorList(fieldErrors, context.translator)\n }\n }\n\n const fieldset = existingFieldset ?? {\n legend: {\n text: label.text,\n classes: 'govuk-fieldset__legend--m'\n }\n }\n\n const result = {\n ...viewModel,\n fieldset,\n items,\n mapLayers: getMapLayers(component)\n }\n\n if (component.options.instructionText) {\n return {\n ...result,\n instructionText: component.options.instructionText\n }\n }\n\n return result\n}\n\n/**\n * Validator factory for location-based fields.\n * This creates a validator that ensures all required fields are present.\n */\nexport function createLocationFieldValidator(\n component: LocationField\n): CustomValidator {\n return (payload: FormPayload, helpers) => {\n const { collection, name, options } = component\n\n const values = component.getFormValueFromState(\n component.getStateFromValidForm(payload)\n )\n\n const context: Context = {\n missing: collection.keys,\n key: name\n }\n\n if (!component.isState(values)) {\n return options.required !== false\n ? helpers.error('object.required', context)\n : payload\n }\n\n return payload\n }\n}\n"],"mappings":"AAGA,SAASA,WAAW;AAqBpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASC,qBAAqBA,CAACC,OAAe,EAAU;EACtD;EACA,OAAOA,OAAO,CAACC,MAAM,CAAC,CAAC,CAAC,CAACC,WAAW,CAAC,CAAC,GAAGF,OAAO,CAACG,KAAK,CAAC,CAAC,CAAC;AAC3D;AAEA,OAAO,SAASC,WAAWA,CAACC,KAAe,EAAEC,UAAsB,EAAU;EAC3E,MAAM;IAAEC;EAAE,CAAC,GAAGD,UAAU;EACxB,IAAID,KAAK,CAACG,MAAM,KAAK,CAAC,EAAE;IACtB,OAAO,GAAGH,KAAK,CAAC,CAAC,CAAC,IAAIE,CAAC,CAAC,YAAY,CAAC,IAAIF,KAAK,CAAC,CAAC,CAAC,EAAE;EACrD;EAEA,MAAMI,OAAO,GAAGJ,KAAK,CAACF,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAACO,IAAI,CAAC,IAAI,CAAC;EAC7C,MAAMC,IAAI,GAAGN,KAAK,CAACA,KAAK,CAACG,MAAM,GAAG,CAAC,CAAC;EACpC,OAAO,GAAGC,OAAO,IAAIF,CAAC,CAAC,YAAY,CAAC,IAAII,IAAI,EAAE;AAChD;AAEA,OAAO,SAASC,eAAeA,CAC7BC,QAAkB,EAClBP,UAAsB,EACd;EACR,IAAI,CAACO,QAAQ,CAACL,MAAM,EAAE;IACpB,OAAO,EAAE;EACX;EAEA,IAAIK,QAAQ,CAACL,MAAM,KAAK,CAAC,EAAE;IACzB,OAAOK,QAAQ,CAAC,CAAC,CAAC;EACpB;EAEA,MAAMC,iBAAiB,GAAGD,QAAQ,CAACE,GAAG,CAAC,CAACC,GAAG,EAAEC,KAAK,KAChDA,KAAK,KAAK,CAAC,GAAGD,GAAG,GAAGjB,qBAAqB,CAACiB,GAAG,CAC/C,CAAC;EAED,OAAOZ,WAAW,CAACU,iBAAiB,EAAER,UAAU,CAAC;AACnD;AAEA,OAAO,SAASY,eAAeA,CAAC,GAAGC,UAAkC,EAAE;EACrE,MAAMC,MAAM,GAAGD,UAAU,CACtBE,OAAO,CAAEC,IAAI,IAAKA,IAAI,EAAEC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAC3CR,GAAG,CAAES,KAAK,IAAKA,KAAK,CAACC,IAAI,CAAC,CAAC,CAAC,CAC5BC,MAAM,CAACC,OAAO,CAAC;EAElB,IAAI,CAACP,MAAM,CAACZ,MAAM,EAAE;IAClB,OAAOoB,SAAS;EAClB;EAEA,OAAOC,KAAK,CAACC,IAAI,CAAC,IAAIC,GAAG,CAACX,MAAM,CAAC,CAAC,CAACV,IAAI,CAAC,GAAG,CAAC;AAC9C;AAEA,OAAO,SAASsB,uBAAuBA,CACrCC,MAA8B,EACK;EACnC,IAAI,CAACA,MAAM,EAAEzB,MAAM,EAAE;IACnB,OAAOoB,SAAS;EAClB;EAEA,OAAOK,MAAM,CAACP,MAAM,CAClB,CAACQ,KAAK,EAAEjB,KAAK,EAAEkB,IAAI,KACjBlB,KAAK,KAAKkB,IAAI,CAACC,SAAS,CAAEC,GAAG,IAAKA,GAAG,CAACC,IAAI,KAAKJ,KAAK,CAACI,IAAI,CAC7D,CAAC;AACH;AAEA,OAAO,SAASC,YAAYA,CAC1BC,SAIwB,EACxB;EACA,MAAM;IAAEC;EAAU,CAAC,GAAGD,SAAS,CAACE,OAAO;EACvC,MAAMC,MAAM,GAAG,EAAE;EAEjB,IAAIF,SAAS,EAAEG,IAAI,EAAE;IACnBD,MAAM,CAACE,IAAI,CAAC,MAAM,CAAC;EACrB;EAEA,OAAOF,MAAM,CAACjC,IAAI,CAAC,GAAG,CAAC;AACzB;AAEA,OAAO,SAASoC,yBAAyBA,CACvCN,SAAwB,EACxBO,SAKC,EACDC,OAAsB,EACtB;EACA,MAAM;IAAEC;EAAW,CAAC,GAAGT,SAAS;EAChC,MAAM;IAAEU,QAAQ,EAAEC,gBAAgB;IAAEC;EAAM,CAAC,GAAGL,SAAS;EAEvD,MAAMM,aAAa,GAAGJ,UAAU,CAACK,YAAY,CAACN,OAAO,CAAC;EAEtD,MAAMO,WAAqB,GAAG,EAAE;EAChCF,aAAa,CAACG,OAAO,CAAC,CAAC;IAAEC;EAAM,CAAC,KAAK;IACnC,IAAIA,KAAK,CAACC,YAAY,EAAEC,IAAI,EAAE;MAC5BJ,WAAW,CAACV,IAAI,CAACY,KAAK,CAACC,YAAY,CAACC,IAAI,CAAC;IAC3C;EACF,CAAC,CAAC;EAEF,MAAMC,cAAc,GAAGL,WAAW,CAAC/C,MAAM,GAAG,CAAC;EAE7C,MAAMH,KAAsB,GAAGgD,aAAa,CAACtC,GAAG,CAC9C,CAAC;IAAE0C;EAAM,CAAC,KAAoB;IAC5B,IAAI;MAAEL,KAAK;MAAES,IAAI;MAAEC,KAAK;MAAEC,OAAO;MAAEC,MAAM;MAAEC,MAAM;MAAEP;IAAa,CAAC,GAAGD,KAAK;IAEzE,IAAIL,KAAK,EAAE;MACTA,KAAK,CAACc,QAAQ,GAAG,MAAMd,KAAK,CAACO,IAAI;IACnC;IAEA,IAAI,CAAC7D,WAAW,CAACgE,KAAK,CAAC,EAAE;MACvBA,KAAK,GAAGlC,SAAS;IACnB;IAEA,MAAMuC,QAAuB,GAAG;MAC9Bf,KAAK;MACLgB,EAAE,EAAEX,KAAK,CAACW,EAAE;MACZ9C,IAAI,EAAEmC,KAAK,CAACnC,IAAI;MAChBuC,IAAI;MACJC,KAAK;MACLC,OAAO,EAAE7C,eAAe,CACtB6C,OAAO,EACPH,cAAc,GAAG,oBAAoB,GAAGhC,SAC1C,CAAC;MACDoC,MAAM;MACNC;IACF,CAAC;IAED,IAAI,CAACL,cAAc,IAAIF,YAAY,EAAE;MACnCS,QAAQ,CAACT,YAAY,GAAGA,YAAY;IACtC;IAEA,OAAOS,QAAQ;EACjB,CACF,CAAC;EAED,MAAME,iBAAiB,GACrBT,cAAc,IAAIjC,OAAO,CAACoB,SAAS,CAACW,YAAY,EAAEC,IAAI,CAAC;EAEzDZ,SAAS,CAACsB,iBAAiB,GAAGA,iBAAiB;EAE/C,IAAIT,cAAc,EAAE;IAClBb,SAAS,CAACW,YAAY,GAAG;MACvBC,IAAI,EACFJ,WAAW,CAAC/C,MAAM,KAAK,CAAC,GACpB+C,WAAW,CAAC,CAAC,CAAC,GACd3C,eAAe,CAAC2C,WAAW,EAAEP,OAAO,CAAC1C,UAAU;IACvD,CAAC;EACH;EAEA,MAAM4C,QAAQ,GAAGC,gBAAgB,IAAI;IACnCmB,MAAM,EAAE;MACNX,IAAI,EAAEP,KAAK,CAACO,IAAI;MAChBI,OAAO,EAAE;IACX;EACF,CAAC;EAED,MAAMQ,MAAM,GAAG;IACb,GAAGxB,SAAS;IACZG,QAAQ;IACR7C,KAAK;IACLoC,SAAS,EAAEF,YAAY,CAACC,SAAS;EACnC,CAAC;EAED,IAAIA,SAAS,CAACE,OAAO,CAAC8B,eAAe,EAAE;IACrC,OAAO;MACL,GAAGD,MAAM;MACTC,eAAe,EAAEhC,SAAS,CAACE,OAAO,CAAC8B;IACrC,CAAC;EACH;EAEA,OAAOD,MAAM;AACf;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASE,4BAA4BA,CAC1CjC,SAAwB,EACP;EACjB,OAAO,CAACkC,OAAoB,EAAEC,OAAO,KAAK;IACxC,MAAM;MAAE1B,UAAU;MAAE3B,IAAI;MAAEoB;IAAQ,CAAC,GAAGF,SAAS;IAE/C,MAAMoC,MAAM,GAAGpC,SAAS,CAACqC,qBAAqB,CAC5CrC,SAAS,CAACsC,qBAAqB,CAACJ,OAAO,CACzC,CAAC;IAED,MAAM1B,OAAgB,GAAG;MACvB+B,OAAO,EAAE9B,UAAU,CAAC+B,IAAI;MACxBC,GAAG,EAAE3D;IACP,CAAC;IAED,IAAI,CAACkB,SAAS,CAAC0C,OAAO,CAACN,MAAM,CAAC,EAAE;MAC9B,OAAOlC,OAAO,CAACyC,QAAQ,KAAK,KAAK,GAC7BR,OAAO,CAACzC,KAAK,CAAC,iBAAiB,EAAEc,OAAO,CAAC,GACzC0B,OAAO;IACb;IAEA,OAAOA,OAAO;EAChB,CAAC;AACH","ignoreList":[]}
@@ -12,11 +12,13 @@ export declare class MonthYearField extends FormComponent {
12
12
  collection: ComponentCollection;
13
13
  constructor(def: MonthYearFieldComponent, props: ConstructorParameters<typeof FormComponent>[1]);
14
14
  getFormValueFromState(state: FormSubmissionState): MonthYearState | undefined;
15
- getDisplayStringFromFormValue(value: MonthYearState | undefined, _translator: Translator): string;
15
+ getDisplayStringFromFormValue(value: MonthYearState | undefined, translator: Translator): string;
16
16
  getDisplayStringFromState(state: FormSubmissionState, translator: Translator): string;
17
17
  getContextValueFromFormValue(value: MonthYearState | undefined): string | null;
18
18
  getContextValueFromState(state: FormSubmissionState): string | null;
19
- getValidationMessagesOverride(translator: Translator): LanguageMessages;
19
+ getValidationMessagesOverride(translator: Translator): {
20
+ [x: string]: LanguageMessages;
21
+ };
20
22
  getViewModel(context: RenderContext): {
21
23
  fieldset: {
22
24
  attributes?: string | Record<string, string>;
@@ -6,6 +6,7 @@ import { NumberField } from "./NumberField.js";
6
6
  import { parseStrictDate } from "../date-helper.js";
7
7
  import { buildValidationMessages } from "../i18n/buildValidationMessages.js";
8
8
  import { messageTemplate } from "../pageControllers/validationOptions.js";
9
+ import { formatDateForLanguage } from "../../payment/helper.js";
9
10
  import { convertToLanguageMessages } from "../../../utils/type-utils.js";
10
11
  export class MonthYearField extends FormComponent {
11
12
  constructor(def, props) {
@@ -69,15 +70,15 @@ export class MonthYearField extends FormComponent {
69
70
  const value = super.getFormValueFromState(state);
70
71
  return MonthYearField.isMonthYear(value) ? value : undefined;
71
72
  }
72
- getDisplayStringFromFormValue(value, _translator) {
73
+ getDisplayStringFromFormValue(value, translator) {
73
74
  if (!value) {
74
75
  return '';
75
76
  }
76
77
  const date = new Date();
78
+ // Force to first day of month in case current date was say 31st and specified month has fewer than 31 days
79
+ date.setDate(1);
77
80
  date.setMonth(value.month - 1);
78
- const monthString = date.toLocaleString('default', {
79
- month: 'long'
80
- });
81
+ const monthString = formatDateForLanguage(date, 'LLLL', translator.language);
81
82
  return `${monthString} ${value.year}`;
82
83
  }
83
84
  getDisplayStringFromState(state, translator) {
@@ -98,15 +99,17 @@ export class MonthYearField extends FormComponent {
98
99
  const {
99
100
  t
100
101
  } = translator;
101
- return convertToLanguageMessages({
102
- 'any.required': buildValidationMessages(t).objectMissing,
103
- 'number.base': buildValidationMessages(t).objectMissing,
104
- 'number.precision': buildValidationMessages(t).dateFormat,
105
- 'number.integer': buildValidationMessages(t).dateFormat,
106
- 'number.unsafe': buildValidationMessages(t).dateFormat,
107
- 'number.min': buildValidationMessages(t).dateFormat,
108
- 'number.max': buildValidationMessages(t).dateFormat
109
- });
102
+ return {
103
+ [this.name]: convertToLanguageMessages({
104
+ 'any.required': buildValidationMessages(t).objectMissing,
105
+ 'number.base': buildValidationMessages(t).objectMissing,
106
+ 'number.precision': buildValidationMessages(t).dateFormat,
107
+ 'number.integer': buildValidationMessages(t).dateFormat,
108
+ 'number.unsafe': buildValidationMessages(t).dateFormat,
109
+ 'number.min': buildValidationMessages(t).dateFormat,
110
+ 'number.max': buildValidationMessages(t).dateFormat
111
+ })
112
+ };
110
113
  }
111
114
  getViewModel(context) {
112
115
  const {
@@ -1 +1 @@
1
- {"version":3,"file":"MonthYearField.js","names":["ComponentType","format","isValid","parse","ComponentCollection","FormComponent","isFormState","isFormValue","NumberField","parseStrictDate","buildValidationMessages","messageTemplate","convertToLanguageMessages","MonthYearField","constructor","def","props","name","options","isRequired","required","customValidationMessages","objectMissing","dateFormat","collection","type","title","schema","min","max","precision","optionalText","classes","parent","custom","getValidatorMonthYear","peers","formSchema","stateSchema","getFormValueFromState","state","value","isMonthYear","undefined","getDisplayStringFromFormValue","_translator","date","Date","setMonth","month","monthString","toLocaleString","year","getDisplayStringFromState","translator","getContextValueFromFormValue","getContextValueFromState","getValidationMessagesOverride","t","getViewModel","context","errors","viewModel","fieldset","label","hasError","some","error","items","map","model","errorMessage","toString","text","trim","id","legend","isState","getAllPossibleErrors","baseErrors","template","advancedSettingsErrors","dateMin","dateMax","isNumber","component","validator","payload","helpers","values","getStateFromValidForm","missing","keys","key","earliestDate","earliestMonthYear","limit","latestDate","latestMonthYear"],"sources":["../../../../../src/server/plugins/engine/components/MonthYearField.ts"],"sourcesContent":["import { ComponentType, type MonthYearFieldComponent } from '@defra/forms-model'\nimport { format, isValid, parse } from 'date-fns'\nimport {\n type Context,\n type CustomValidator,\n type LanguageMessages,\n type ObjectSchema\n} from 'joi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n FormComponent,\n isFormState,\n isFormValue\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'\nimport {\n type DateInputItem,\n type MonthYearState,\n type RenderContext\n} from '~/src/server/plugins/engine/components/types.js'\nimport { parseStrictDate } from '~/src/server/plugins/engine/date-helper.js'\nimport { buildValidationMessages } from '~/src/server/plugins/engine/i18n/buildValidationMessages.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type ErrorMessageTemplateList,\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'\n\nexport class MonthYearField extends FormComponent {\n declare options: MonthYearFieldComponent['options']\n declare formSchema: ObjectSchema<FormPayload>\n declare stateSchema: ObjectSchema<FormState>\n declare collection: ComponentCollection\n\n constructor(\n def: MonthYearFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { name, options } = def\n\n const isRequired = options.required !== false\n\n const customValidationMessages: LanguageMessages =\n convertToLanguageMessages({\n 'any.required': messageTemplate.objectMissing,\n 'number.base': messageTemplate.objectMissing,\n 'number.precision': messageTemplate.dateFormat,\n 'number.integer': messageTemplate.dateFormat,\n 'number.unsafe': messageTemplate.dateFormat,\n 'number.min': messageTemplate.dateFormat,\n 'number.max': messageTemplate.dateFormat\n })\n\n this.collection = new ComponentCollection(\n [\n {\n type: ComponentType.NumberField,\n name: `${name}__month`,\n title: 'components.monthYearField.month',\n schema: { min: 1, max: 12, precision: 0 },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-2',\n customValidationMessages\n }\n },\n {\n type: ComponentType.NumberField,\n name: `${name}__year`,\n title: 'components.monthYearField.year',\n schema: { min: 1000, max: 3000, precision: 0 },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-4',\n customValidationMessages\n }\n }\n ],\n { ...props, parent: this },\n {\n custom: getValidatorMonthYear(this),\n peers: [`${name}__month`, `${name}__year`]\n }\n )\n\n this.options = options\n this.formSchema = this.collection.formSchema\n this.stateSchema = this.collection.stateSchema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const value = super.getFormValueFromState(state)\n return MonthYearField.isMonthYear(value) ? value : undefined\n }\n\n getDisplayStringFromFormValue(\n value: MonthYearState | undefined,\n _translator: Translator\n ): string {\n if (!value) {\n return ''\n }\n\n const date = new Date()\n date.setMonth(value.month - 1)\n\n const monthString = date.toLocaleString('default', { month: 'long' })\n return `${monthString} ${value.year}`\n }\n\n getDisplayStringFromState(\n state: FormSubmissionState,\n translator: Translator\n ) {\n const value = this.getFormValueFromState(state)\n\n return this.getDisplayStringFromFormValue(value, translator)\n }\n\n getContextValueFromFormValue(\n value: MonthYearState | undefined\n ): string | null {\n if (\n !value ||\n !isValid(\n parseStrictDate(\n value,\n `${value.year}-${value.month}-01`,\n 'yyyy-MM-dd',\n new Date()\n )\n )\n ) {\n return null\n }\n\n return format(`${value.year}-${value.month}-01`, 'yyyy-MM')\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getContextValueFromFormValue(value)\n }\n\n getValidationMessagesOverride(translator: Translator) {\n const { t } = translator\n return convertToLanguageMessages({\n 'any.required': buildValidationMessages(t).objectMissing,\n 'number.base': buildValidationMessages(t).objectMissing,\n 'number.precision': buildValidationMessages(t).dateFormat,\n 'number.integer': buildValidationMessages(t).dateFormat,\n 'number.unsafe': buildValidationMessages(t).dateFormat,\n 'number.min': buildValidationMessages(t).dateFormat,\n 'number.max': buildValidationMessages(t).dateFormat\n })\n }\n\n getViewModel(context: RenderContext) {\n const { errors } = context\n const { collection, name } = this\n\n const viewModel = super.getViewModel(context)\n let { fieldset, label } = viewModel\n\n // Check for component errors only\n const hasError = errors?.some((error) => error.name === name)\n\n // Use the component collection to generate the subitems\n const items: DateInputItem[] = collection\n .getViewModel(context)\n .map(({ model }) => {\n let { label, type, value, classes, errorMessage } = model\n\n if (label) {\n label.toString = () => label.text // Date component uses string labels\n }\n\n if (hasError || errorMessage) {\n classes = `${classes} govuk-input--error`.trim()\n }\n\n // Allow any `toString()`-able value so non-numeric\n // values are shown alongside their error messages\n if (!isFormValue(value)) {\n value = undefined\n }\n\n return {\n label,\n id: model.id,\n name: model.name,\n type,\n value,\n classes\n }\n })\n\n fieldset ??= {\n legend: {\n text: label.text,\n classes: 'govuk-fieldset__legend--m'\n }\n }\n\n return {\n ...viewModel,\n fieldset,\n items\n }\n }\n\n isState(value?: FormStateValue | FormState) {\n return MonthYearField.isMonthYear(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return MonthYearField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n { type: 'required', template: messageTemplate.required },\n {\n type: 'dateFormatMonth',\n template: '{{#label}} must include a month'\n },\n { type: 'dateFormatYear', template: '{{#label}} must include a year' }\n ],\n advancedSettingsErrors: [\n { type: 'dateEarliest', template: messageTemplate.dateMin },\n { type: 'dateLatest', template: messageTemplate.dateMax }\n ]\n }\n }\n\n static isMonthYear(\n value?: FormStateValue | FormState\n ): value is MonthYearState {\n return (\n isFormState(value) &&\n NumberField.isNumber(value.month) &&\n NumberField.isNumber(value.year)\n )\n }\n}\n\nexport function getValidatorMonthYear(component: MonthYearField) {\n const validator: CustomValidator = (payload: FormPayload, helpers) => {\n const { collection, name, options } = component\n\n const values = component.getFormValueFromState(\n component.getStateFromValidForm(payload)\n )\n\n const context: Context = {\n missing: collection.keys,\n key: name\n }\n\n if (!component.isState(values)) {\n return options.required !== false\n ? helpers.error('object.required', context)\n : payload\n }\n\n const date = parse(\n `${values.year}-${values.month}-01`,\n 'yyyy-MM-dd',\n new Date()\n )\n\n if (!isValid(date)) {\n return helpers.error('date.format', context)\n }\n\n // Minimum date from today\n const earliestDate = options.earliestMonthYear\n ? new Date(`${options.earliestMonthYear}-01`)\n : undefined\n\n if (earliestDate && date < earliestDate) {\n return helpers.error('date.min', { ...context, limit: earliestDate })\n }\n\n // Maximum date from today\n const latestDate = options.latestMonthYear\n ? new Date(`${options.latestMonthYear}-01`)\n : undefined\n\n if (latestDate && date > latestDate) {\n return helpers.error('date.max', { ...context, limit: latestDate })\n }\n\n return payload\n }\n\n return validator\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"MonthYearField.js","names":["ComponentType","format","isValid","parse","ComponentCollection","FormComponent","isFormState","isFormValue","NumberField","parseStrictDate","buildValidationMessages","messageTemplate","formatDateForLanguage","convertToLanguageMessages","MonthYearField","constructor","def","props","name","options","isRequired","required","customValidationMessages","objectMissing","dateFormat","collection","type","title","schema","min","max","precision","optionalText","classes","parent","custom","getValidatorMonthYear","peers","formSchema","stateSchema","getFormValueFromState","state","value","isMonthYear","undefined","getDisplayStringFromFormValue","translator","date","Date","setDate","setMonth","month","monthString","language","year","getDisplayStringFromState","getContextValueFromFormValue","getContextValueFromState","getValidationMessagesOverride","t","getViewModel","context","errors","viewModel","fieldset","label","hasError","some","error","items","map","model","errorMessage","toString","text","trim","id","legend","isState","getAllPossibleErrors","baseErrors","template","advancedSettingsErrors","dateMin","dateMax","isNumber","component","validator","payload","helpers","values","getStateFromValidForm","missing","keys","key","earliestDate","earliestMonthYear","limit","latestDate","latestMonthYear"],"sources":["../../../../../src/server/plugins/engine/components/MonthYearField.ts"],"sourcesContent":["import { ComponentType, type MonthYearFieldComponent } from '@defra/forms-model'\nimport { format, isValid, parse } from 'date-fns'\nimport {\n type Context,\n type CustomValidator,\n type LanguageMessages,\n type ObjectSchema\n} from 'joi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n FormComponent,\n isFormState,\n isFormValue\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'\nimport {\n type DateInputItem,\n type MonthYearState,\n type RenderContext\n} from '~/src/server/plugins/engine/components/types.js'\nimport { parseStrictDate } from '~/src/server/plugins/engine/date-helper.js'\nimport { buildValidationMessages } from '~/src/server/plugins/engine/i18n/buildValidationMessages.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type ErrorMessageTemplateList,\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { formatDateForLanguage } from '~/src/server/plugins/payment/helper.js'\nimport { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'\n\nexport class MonthYearField extends FormComponent {\n declare options: MonthYearFieldComponent['options']\n declare formSchema: ObjectSchema<FormPayload>\n declare stateSchema: ObjectSchema<FormState>\n declare collection: ComponentCollection\n\n constructor(\n def: MonthYearFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { name, options } = def\n\n const isRequired = options.required !== false\n\n const customValidationMessages: LanguageMessages =\n convertToLanguageMessages({\n 'any.required': messageTemplate.objectMissing,\n 'number.base': messageTemplate.objectMissing,\n 'number.precision': messageTemplate.dateFormat,\n 'number.integer': messageTemplate.dateFormat,\n 'number.unsafe': messageTemplate.dateFormat,\n 'number.min': messageTemplate.dateFormat,\n 'number.max': messageTemplate.dateFormat\n })\n\n this.collection = new ComponentCollection(\n [\n {\n type: ComponentType.NumberField,\n name: `${name}__month`,\n title: 'components.monthYearField.month',\n schema: { min: 1, max: 12, precision: 0 },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-2',\n customValidationMessages\n }\n },\n {\n type: ComponentType.NumberField,\n name: `${name}__year`,\n title: 'components.monthYearField.year',\n schema: { min: 1000, max: 3000, precision: 0 },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-4',\n customValidationMessages\n }\n }\n ],\n { ...props, parent: this },\n {\n custom: getValidatorMonthYear(this),\n peers: [`${name}__month`, `${name}__year`]\n }\n )\n\n this.options = options\n this.formSchema = this.collection.formSchema\n this.stateSchema = this.collection.stateSchema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const value = super.getFormValueFromState(state)\n return MonthYearField.isMonthYear(value) ? value : undefined\n }\n\n getDisplayStringFromFormValue(\n value: MonthYearState | undefined,\n translator: Translator\n ): string {\n if (!value) {\n return ''\n }\n\n const date = new Date()\n // Force to first day of month in case current date was say 31st and specified month has fewer than 31 days\n date.setDate(1)\n date.setMonth(value.month - 1)\n\n const monthString = formatDateForLanguage(date, 'LLLL', translator.language)\n return `${monthString} ${value.year}`\n }\n\n getDisplayStringFromState(\n state: FormSubmissionState,\n translator: Translator\n ) {\n const value = this.getFormValueFromState(state)\n\n return this.getDisplayStringFromFormValue(value, translator)\n }\n\n getContextValueFromFormValue(\n value: MonthYearState | undefined\n ): string | null {\n if (\n !value ||\n !isValid(\n parseStrictDate(\n value,\n `${value.year}-${value.month}-01`,\n 'yyyy-MM-dd',\n new Date()\n )\n )\n ) {\n return null\n }\n\n return format(`${value.year}-${value.month}-01`, 'yyyy-MM')\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getContextValueFromFormValue(value)\n }\n\n getValidationMessagesOverride(translator: Translator) {\n const { t } = translator\n return {\n [this.name]: convertToLanguageMessages({\n 'any.required': buildValidationMessages(t).objectMissing,\n 'number.base': buildValidationMessages(t).objectMissing,\n 'number.precision': buildValidationMessages(t).dateFormat,\n 'number.integer': buildValidationMessages(t).dateFormat,\n 'number.unsafe': buildValidationMessages(t).dateFormat,\n 'number.min': buildValidationMessages(t).dateFormat,\n 'number.max': buildValidationMessages(t).dateFormat\n })\n }\n }\n\n getViewModel(context: RenderContext) {\n const { errors } = context\n const { collection, name } = this\n\n const viewModel = super.getViewModel(context)\n let { fieldset, label } = viewModel\n\n // Check for component errors only\n const hasError = errors?.some((error) => error.name === name)\n\n // Use the component collection to generate the subitems\n const items: DateInputItem[] = collection\n .getViewModel(context)\n .map(({ model }) => {\n let { label, type, value, classes, errorMessage } = model\n\n if (label) {\n label.toString = () => label.text // Date component uses string labels\n }\n\n if (hasError || errorMessage) {\n classes = `${classes} govuk-input--error`.trim()\n }\n\n // Allow any `toString()`-able value so non-numeric\n // values are shown alongside their error messages\n if (!isFormValue(value)) {\n value = undefined\n }\n\n return {\n label,\n id: model.id,\n name: model.name,\n type,\n value,\n classes\n }\n })\n\n fieldset ??= {\n legend: {\n text: label.text,\n classes: 'govuk-fieldset__legend--m'\n }\n }\n\n return {\n ...viewModel,\n fieldset,\n items\n }\n }\n\n isState(value?: FormStateValue | FormState) {\n return MonthYearField.isMonthYear(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return MonthYearField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n { type: 'required', template: messageTemplate.required },\n {\n type: 'dateFormatMonth',\n template: '{{#label}} must include a month'\n },\n { type: 'dateFormatYear', template: '{{#label}} must include a year' }\n ],\n advancedSettingsErrors: [\n { type: 'dateEarliest', template: messageTemplate.dateMin },\n { type: 'dateLatest', template: messageTemplate.dateMax }\n ]\n }\n }\n\n static isMonthYear(\n value?: FormStateValue | FormState\n ): value is MonthYearState {\n return (\n isFormState(value) &&\n NumberField.isNumber(value.month) &&\n NumberField.isNumber(value.year)\n )\n }\n}\n\nexport function getValidatorMonthYear(component: MonthYearField) {\n const validator: CustomValidator = (payload: FormPayload, helpers) => {\n const { collection, name, options } = component\n\n const values = component.getFormValueFromState(\n component.getStateFromValidForm(payload)\n )\n\n const context: Context = {\n missing: collection.keys,\n key: name\n }\n\n if (!component.isState(values)) {\n return options.required !== false\n ? helpers.error('object.required', context)\n : payload\n }\n\n const date = parse(\n `${values.year}-${values.month}-01`,\n 'yyyy-MM-dd',\n new Date()\n )\n\n if (!isValid(date)) {\n return helpers.error('date.format', context)\n }\n\n // Minimum date from today\n const earliestDate = options.earliestMonthYear\n ? new Date(`${options.earliestMonthYear}-01`)\n : undefined\n\n if (earliestDate && date < earliestDate) {\n return helpers.error('date.min', { ...context, limit: earliestDate })\n }\n\n // Maximum date from today\n const latestDate = options.latestMonthYear\n ? new Date(`${options.latestMonthYear}-01`)\n : undefined\n\n if (latestDate && date > latestDate) {\n return helpers.error('date.max', { ...context, limit: latestDate })\n }\n\n return payload\n }\n\n return validator\n}\n"],"mappings":"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","ignoreList":[]}
@@ -124,6 +124,7 @@ export interface PaymentSessionData {
124
124
  reference: string;
125
125
  amount: number;
126
126
  description: string;
127
+ descriptionInEng?: string;
127
128
  paymentId: string;
128
129
  componentName: string;
129
130
  returnUrl: string;
@@ -19,6 +19,7 @@ export class PaymentField extends FormComponent {
19
19
  reference: joi.string().required(),
20
20
  amount: joi.number().required(),
21
21
  description: joi.string().required(),
22
+ descriptionInEng: joi.string().optional(),
22
23
  uuid: joi.string().uuid().required(),
23
24
  formId: joi.string().required(),
24
25
  isLivePayment: joi.boolean().required(),
@@ -164,10 +165,11 @@ export class PaymentField extends FormComponent {
164
165
  const {
165
166
  model
166
167
  } = args.controller;
167
- const language = resolveLanguage(request);
168
168
  const {
169
- t
170
- } = model.createTranslator(language);
169
+ language,
170
+ t,
171
+ tComponent
172
+ } = model.createTranslator(resolveLanguage(request));
171
173
  const state = await args.controller.getState(request);
172
174
  const {
173
175
  baseUrl
@@ -189,7 +191,7 @@ export class PaymentField extends FormComponent {
189
191
  if (resolvedAmount === 0) {
190
192
  return h.redirect(summaryUrl).code(StatusCodes.SEE_OTHER);
191
193
  }
192
- const description = options.description;
194
+ const description = tComponent(args.component, 'paymentDescription') || options.description;
193
195
  const slug = `/${model.basePath}`;
194
196
  const payCallbackUrl = `${baseUrl}/payment-callback?uuid=${uuid}`;
195
197
  const paymentPageUrl = args.sourceUrl;
@@ -201,7 +203,8 @@ export class PaymentField extends FormComponent {
201
203
  const amountInPence = Math.round(resolvedAmount * 100);
202
204
  const payment = await paymentService.createPayment(amountInPence, description, payCallbackUrl, reference, isLivePayment, {
203
205
  formId,
204
- slug
206
+ slug,
207
+ language
205
208
  }, prefilledEmail);
206
209
  if (!payment) {
207
210
  const message = isLivePayment ? t('components.paymentField.cannotTakePayment') : t('components.paymentField.testApiKey');
@@ -215,6 +218,7 @@ export class PaymentField extends FormComponent {
215
218
  reference,
216
219
  amount: resolvedAmount,
217
220
  description,
221
+ descriptionInEng: options.description,
218
222
  paymentId: payment.paymentId,
219
223
  componentName,
220
224
  returnUrl: summaryUrl,
@@ -1 +1 @@
1
- {"version":3,"file":"PaymentField.js","names":["randomUUID","StatusCodes","joi","logger","COMPONENT_STATE_ERROR","FormComponent","createError","getPluginOptions","PaymentErrorTypes","PaymentPreAuthError","PaymentSubmissionError","createPaymentService","formatCurrency","resolveLanguage","PaymentField","isAppendageStateSingleObject","constructor","def","props","options","paymentStateSchema","object","paymentId","string","required","reference","amount","number","description","uuid","formId","isLivePayment","boolean","preAuth","status","valid","createdAt","isoDate","unknown","label","formSchema","stateSchema","getPaymentStateFromState","state","value","name","isPaymentState","undefined","getDisplayStringFromState","_translator","getViewModel","context","payload","viewModel","translator","tComponent","paymentState","Array","isArray","isState","getFormValue","getContextValueFromState","getAllPossibleErrors","baseErrors","type","template","advancedSettingsErrors","resolveAmount","model","conditionalAmounts","length","condition","conditions","warn","fn","dispatcher","request","h","args","componentName","component","controller","language","t","createTranslator","getState","baseUrl","server","summaryUrl","basePath","existingPaymentState","redirect","code","SEE_OTHER","isLive","isPreview","formsService","services","paymentService","$$__referenceNumber","resolvedAmount","slug","payCallbackUrl","paymentPageUrl","sourceUrl","prefilledEmail","userConfirmationEmail","userConfirmationEmailAddress","amountInPence","Math","round","payment","createPayment","message","govukError","yar","flash","url","pathname","search","sessionData","returnUrl","failureUrl","set","paymentUrl","onSubmit","_metadata","PaymentIncomplete","capture","getPaymentStatus","checkPaymentAmount","markPaymentCaptured","PaymentExpired","captured","capturePayment","updatedState","Date","toISOString","page","currentState","mergeState"],"sources":["../../../../../src/server/plugins/engine/components/PaymentField.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\n\nimport {\n type FormMetadata,\n type PaymentFieldComponent\n} from '@defra/forms-model'\nimport { StatusCodes } from 'http-status-codes'\nimport joi, { type ObjectSchema } from 'joi'\n\nimport { logger } from '~/src/server/common/helpers/logging/logger.js'\nimport { COMPONENT_STATE_ERROR } from '~/src/server/constants.js'\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type PaymentState } from '~/src/server/plugins/engine/components/PaymentField.types.js'\nimport { type RenderContext } from '~/src/server/plugins/engine/components/types.js'\nimport {\n createError,\n getPluginOptions\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 {\n PaymentErrorTypes,\n PaymentPreAuthError,\n PaymentSubmissionError\n} from '~/src/server/plugins/engine/pageControllers/errors.js'\nimport {\n type FormContext,\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/plugins/engine/types/index.js'\nimport {\n type ErrorMessageTemplateList,\n type FormState,\n type FormStateValue,\n type FormSubmissionState,\n type PaymentExternalArgs\n} from '~/src/server/plugins/engine/types.js'\nimport {\n createPaymentService,\n formatCurrency\n} from '~/src/server/plugins/payment/helper.js'\nimport { resolveLanguage } from '~/src/server/utils/utils.js'\n\nexport class PaymentField extends FormComponent {\n declare options: PaymentFieldComponent['options']\n declare formSchema: ObjectSchema\n declare stateSchema: ObjectSchema\n declare def: PaymentFieldComponent\n isAppendageStateSingleObject = true\n\n constructor(\n def: PaymentFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n this.options = def.options\n this.def = def\n\n const paymentStateSchema = joi\n .object({\n paymentId: joi.string().required(),\n reference: joi.string().required(),\n amount: joi.number().required(),\n description: joi.string().required(),\n uuid: joi.string().uuid().required(),\n formId: joi.string().required(),\n isLivePayment: joi.boolean().required(),\n preAuth: joi\n .object({\n status: joi\n .string()\n .valid('success', 'failed', 'started')\n .required(),\n createdAt: joi.string().isoDate().required()\n })\n .required()\n })\n .unknown(true)\n .label(this.label)\n\n this.formSchema = paymentStateSchema\n // 'required()' forces the payment page to be invalid until we have valid payment state\n // i.e. the user will automatically be directed back to the payment page\n // if they attempt to access future pages when no payment entered yet\n this.stateSchema = paymentStateSchema.required()\n }\n\n /**\n * Gets the PaymentState from form submission state\n */\n getPaymentStateFromState(\n state: FormSubmissionState\n ): PaymentState | undefined {\n const value = state[this.name]\n return this.isPaymentState(value) ? value : undefined\n }\n\n getDisplayStringFromState(\n state: FormSubmissionState,\n _translator: Translator\n ): string {\n const value = this.getPaymentStateFromState(state)\n\n if (!value) {\n return ''\n }\n\n return `${formatCurrency(value.amount)} - ${value.description}`\n }\n\n getViewModel(context: RenderContext) {\n const { payload } = context\n const viewModel = super.getViewModel(context)\n const { translator } = context\n const { tComponent } = translator\n\n // Payload is pre-populated from state if a payment has already been made\n const paymentState = this.isPaymentState(payload[this.name] as unknown)\n ? (payload[this.name] as unknown as PaymentState)\n : undefined\n\n // Use payment state amount if pre-authorized, otherwise use default.\n // The page controller overrides this with the resolved conditional amount\n // using the full form state (which getViewModel doesn't have access to).\n const amount = paymentState?.amount ?? this.options.amount\n\n return {\n ...viewModel,\n amount: formatCurrency(amount),\n description:\n tComponent(this.def, 'paymentDescription') || this.options.description,\n paymentState\n }\n }\n\n /**\n * Type guard to check if value is PaymentState\n */\n isPaymentState(value: unknown): value is PaymentState {\n return PaymentField.isPaymentState(value)\n }\n\n /**\n * Static type guard to check if value is PaymentState\n */\n static isPaymentState(value: unknown): value is PaymentState {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return false\n }\n\n const state = value as PaymentState\n return (\n typeof state.paymentId === 'string' &&\n typeof state.amount === 'number' &&\n typeof state.description === 'string'\n )\n }\n\n /**\n * Override base isState to validate PaymentState\n */\n isState(value?: FormStateValue | FormState): value is FormState {\n return this.isPaymentState(value)\n }\n\n getFormValue(value?: FormStateValue | FormState) {\n return this.isPaymentState(value)\n ? (value as unknown as NonNullable<FormStateValue>)\n : undefined\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n return this.isPaymentState(state)\n ? `Reference: ${state.reference}\\nAmount: ${formatCurrency(state.amount)}`\n : ''\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return PaymentField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n {\n type: 'paymentRequired',\n template: 'Complete the payment to continue'\n }\n ],\n advancedSettingsErrors: []\n }\n }\n\n /**\n * Resolves the payment amount from conditional amounts configuration.\n * Evaluates conditions in order; first true condition wins.\n * Falls back to the default options.amount.\n */\n static resolveAmount(\n options: PaymentFieldComponent['options'],\n model: FormModel,\n state: FormState\n ): number {\n const { conditionalAmounts } = options\n\n if (!conditionalAmounts?.length) {\n return options.amount\n }\n\n for (const { condition, amount } of conditionalAmounts) {\n if (!model.conditions[condition]) {\n logger.warn(\n `[payment] Condition '${condition}' not found in form conditions. Skipping.`\n )\n continue\n }\n if (model.conditions[condition].fn(state)) {\n return amount\n }\n }\n\n return options.amount\n }\n\n /**\n * Dispatcher for external redirect to GOV.UK Pay\n */\n static async dispatcher(\n request: FormRequestPayload,\n h: FormResponseToolkit,\n args: PaymentExternalArgs\n ): Promise<unknown> {\n const { options, name: componentName } = args.component\n const { model } = args.controller\n const language = resolveLanguage(request)\n const { t } = model.createTranslator(language)\n const state = await args.controller.getState(request)\n const { baseUrl } = getPluginOptions(request.server)\n const summaryUrl = `${baseUrl}/${model.basePath}/summary`\n\n const existingPaymentState = state[componentName]\n if (\n PaymentField.isPaymentState(existingPaymentState) &&\n existingPaymentState.preAuth?.status === 'success'\n ) {\n return h.redirect(summaryUrl).code(StatusCodes.SEE_OTHER)\n }\n\n const isLivePayment = args.isLive && !args.isPreview\n const formId = args.controller.model.formId\n const formsService = model.services.formsService\n const paymentService = await createPaymentService(\n isLivePayment,\n formId,\n formsService\n )\n\n const uuid = randomUUID()\n\n const reference = state.$$__referenceNumber as string\n const resolvedAmount = PaymentField.resolveAmount(options, model, state)\n\n // Zero-amount safety net (page skip should prevent this, but defensive)\n if (resolvedAmount === 0) {\n return h.redirect(summaryUrl).code(StatusCodes.SEE_OTHER)\n }\n\n const description = options.description\n\n const slug = `/${model.basePath}`\n\n const payCallbackUrl = `${baseUrl}/payment-callback?uuid=${uuid}`\n const paymentPageUrl = args.sourceUrl\n\n let prefilledEmail: string | undefined\n const userConfirmationEmail = state.userConfirmationEmailAddress\n if (typeof userConfirmationEmail === 'string' && userConfirmationEmail) {\n prefilledEmail = userConfirmationEmail\n }\n\n const amountInPence = Math.round(resolvedAmount * 100)\n const payment = await paymentService.createPayment(\n amountInPence,\n description,\n payCallbackUrl,\n reference,\n isLivePayment,\n { formId, slug },\n prefilledEmail\n )\n\n if (!payment) {\n const message = isLivePayment\n ? t('components.paymentField.cannotTakePayment')\n : t('components.paymentField.testApiKey')\n const govukError = createError(componentName, message)\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n return h\n .redirect(`${request.url.pathname}${request.url.search}`)\n .code(StatusCodes.SEE_OTHER)\n }\n\n const sessionData: PaymentSessionData = {\n uuid,\n formId,\n reference,\n amount: resolvedAmount,\n description,\n paymentId: payment.paymentId,\n componentName,\n returnUrl: summaryUrl,\n failureUrl: paymentPageUrl,\n isLivePayment\n }\n\n request.yar.set(`payment-${uuid}`, sessionData)\n\n return h.redirect(payment.paymentUrl).code(StatusCodes.SEE_OTHER)\n }\n\n /**\n * Called on form submission to capture the payment\n * @see https://docs.payments.service.gov.uk/delayed_capture/#delay-taking-a-payment\n */\n async onSubmit(\n request: FormRequestPayload,\n _metadata: FormMetadata,\n context: FormContext\n ): Promise<void> {\n const language = resolveLanguage(request)\n const { t } = this.model.createTranslator(language)\n\n // Zero-amount bypass — no capture needed\n const resolvedAmount = PaymentField.resolveAmount(\n this.options,\n this.model,\n context.state\n )\n if (resolvedAmount === 0) {\n return\n }\n\n const paymentState = this.getPaymentStateFromState(context.state)\n\n if (!paymentState) {\n throw new PaymentPreAuthError(\n this,\n t('components.paymentField.completePayment'),\n true,\n PaymentErrorTypes.PaymentIncomplete\n )\n }\n\n if (paymentState.capture?.status === 'success') {\n return\n }\n\n const { paymentId, isLivePayment, formId } = paymentState\n const formsService = this.model.services.formsService\n const paymentService = await createPaymentService(\n isLivePayment,\n formId,\n formsService\n )\n\n /**\n * @see https://docs.payments.service.gov.uk/api_reference/#payment-status-lifecycle\n */\n const status = await paymentService.getPaymentStatus(\n paymentId,\n isLivePayment\n )\n\n PaymentSubmissionError.checkPaymentAmount(\n status.amount,\n resolvedAmount,\n this,\n t\n )\n\n if (status.state.status === 'success') {\n await this.markPaymentCaptured(request, paymentState)\n return\n }\n\n if (status.state.status !== 'capturable') {\n throw new PaymentPreAuthError(\n this,\n t('components.paymentField.paymentExpired'),\n true,\n PaymentErrorTypes.PaymentExpired\n )\n }\n\n const captured = await paymentService.capturePayment(\n paymentId,\n status.amount\n )\n\n if (!captured) {\n throw new PaymentPreAuthError(\n this,\n t('components.paymentField.submissionFailed'),\n false\n )\n }\n\n await this.markPaymentCaptured(request, paymentState)\n }\n\n /**\n * Updates payment state to mark capture as successful\n * This ensures we don't try to re-capture on submission retry\n */\n private async markPaymentCaptured(\n request: FormRequestPayload,\n paymentState: PaymentState\n ): Promise<void> {\n const updatedState: PaymentState = {\n ...paymentState,\n capture: {\n status: 'success',\n createdAt: new Date().toISOString()\n }\n }\n\n if (this.page) {\n const currentState = await this.page.getState(request)\n await this.page.mergeState(request, currentState, {\n [this.name]: updatedState\n })\n }\n }\n}\n\n/**\n * Session data stored when dispatching to GOV.UK Pay\n */\nexport interface PaymentSessionData {\n uuid: string\n formId: string\n reference: string\n amount: number\n description: string\n paymentId: string\n componentName: string\n returnUrl: string\n failureUrl: string\n isLivePayment: boolean\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"PaymentField.js","names":["randomUUID","StatusCodes","joi","logger","COMPONENT_STATE_ERROR","FormComponent","createError","getPluginOptions","PaymentErrorTypes","PaymentPreAuthError","PaymentSubmissionError","createPaymentService","formatCurrency","resolveLanguage","PaymentField","isAppendageStateSingleObject","constructor","def","props","options","paymentStateSchema","object","paymentId","string","required","reference","amount","number","description","descriptionInEng","optional","uuid","formId","isLivePayment","boolean","preAuth","status","valid","createdAt","isoDate","unknown","label","formSchema","stateSchema","getPaymentStateFromState","state","value","name","isPaymentState","undefined","getDisplayStringFromState","_translator","getViewModel","context","payload","viewModel","translator","tComponent","paymentState","Array","isArray","isState","getFormValue","getContextValueFromState","getAllPossibleErrors","baseErrors","type","template","advancedSettingsErrors","resolveAmount","model","conditionalAmounts","length","condition","conditions","warn","fn","dispatcher","request","h","args","componentName","component","controller","language","t","createTranslator","getState","baseUrl","server","summaryUrl","basePath","existingPaymentState","redirect","code","SEE_OTHER","isLive","isPreview","formsService","services","paymentService","$$__referenceNumber","resolvedAmount","slug","payCallbackUrl","paymentPageUrl","sourceUrl","prefilledEmail","userConfirmationEmail","userConfirmationEmailAddress","amountInPence","Math","round","payment","createPayment","message","govukError","yar","flash","url","pathname","search","sessionData","returnUrl","failureUrl","set","paymentUrl","onSubmit","_metadata","PaymentIncomplete","capture","getPaymentStatus","checkPaymentAmount","markPaymentCaptured","PaymentExpired","captured","capturePayment","updatedState","Date","toISOString","page","currentState","mergeState"],"sources":["../../../../../src/server/plugins/engine/components/PaymentField.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\n\nimport {\n type FormMetadata,\n type PaymentFieldComponent\n} from '@defra/forms-model'\nimport { StatusCodes } from 'http-status-codes'\nimport joi, { type ObjectSchema } from 'joi'\n\nimport { logger } from '~/src/server/common/helpers/logging/logger.js'\nimport { COMPONENT_STATE_ERROR } from '~/src/server/constants.js'\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type PaymentState } from '~/src/server/plugins/engine/components/PaymentField.types.js'\nimport { type RenderContext } from '~/src/server/plugins/engine/components/types.js'\nimport {\n createError,\n getPluginOptions\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 {\n PaymentErrorTypes,\n PaymentPreAuthError,\n PaymentSubmissionError\n} from '~/src/server/plugins/engine/pageControllers/errors.js'\nimport {\n type FormContext,\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/plugins/engine/types/index.js'\nimport {\n type ErrorMessageTemplateList,\n type FormState,\n type FormStateValue,\n type FormSubmissionState,\n type PaymentExternalArgs\n} from '~/src/server/plugins/engine/types.js'\nimport {\n createPaymentService,\n formatCurrency\n} from '~/src/server/plugins/payment/helper.js'\nimport { resolveLanguage } from '~/src/server/utils/utils.js'\n\nexport class PaymentField extends FormComponent {\n declare options: PaymentFieldComponent['options']\n declare formSchema: ObjectSchema\n declare stateSchema: ObjectSchema\n declare def: PaymentFieldComponent\n isAppendageStateSingleObject = true\n\n constructor(\n def: PaymentFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n this.options = def.options\n this.def = def\n\n const paymentStateSchema = joi\n .object({\n paymentId: joi.string().required(),\n reference: joi.string().required(),\n amount: joi.number().required(),\n description: joi.string().required(),\n descriptionInEng: joi.string().optional(),\n uuid: joi.string().uuid().required(),\n formId: joi.string().required(),\n isLivePayment: joi.boolean().required(),\n preAuth: joi\n .object({\n status: joi\n .string()\n .valid('success', 'failed', 'started')\n .required(),\n createdAt: joi.string().isoDate().required()\n })\n .required()\n })\n .unknown(true)\n .label(this.label)\n\n this.formSchema = paymentStateSchema\n // 'required()' forces the payment page to be invalid until we have valid payment state\n // i.e. the user will automatically be directed back to the payment page\n // if they attempt to access future pages when no payment entered yet\n this.stateSchema = paymentStateSchema.required()\n }\n\n /**\n * Gets the PaymentState from form submission state\n */\n getPaymentStateFromState(\n state: FormSubmissionState\n ): PaymentState | undefined {\n const value = state[this.name]\n return this.isPaymentState(value) ? value : undefined\n }\n\n getDisplayStringFromState(\n state: FormSubmissionState,\n _translator: Translator\n ): string {\n const value = this.getPaymentStateFromState(state)\n\n if (!value) {\n return ''\n }\n\n return `${formatCurrency(value.amount)} - ${value.description}`\n }\n\n getViewModel(context: RenderContext) {\n const { payload } = context\n const viewModel = super.getViewModel(context)\n const { translator } = context\n const { tComponent } = translator\n\n // Payload is pre-populated from state if a payment has already been made\n const paymentState = this.isPaymentState(payload[this.name] as unknown)\n ? (payload[this.name] as unknown as PaymentState)\n : undefined\n\n // Use payment state amount if pre-authorized, otherwise use default.\n // The page controller overrides this with the resolved conditional amount\n // using the full form state (which getViewModel doesn't have access to).\n const amount = paymentState?.amount ?? this.options.amount\n\n return {\n ...viewModel,\n amount: formatCurrency(amount),\n description:\n tComponent(this.def, 'paymentDescription') || this.options.description,\n paymentState\n }\n }\n\n /**\n * Type guard to check if value is PaymentState\n */\n isPaymentState(value: unknown): value is PaymentState {\n return PaymentField.isPaymentState(value)\n }\n\n /**\n * Static type guard to check if value is PaymentState\n */\n static isPaymentState(value: unknown): value is PaymentState {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return false\n }\n\n const state = value as PaymentState\n return (\n typeof state.paymentId === 'string' &&\n typeof state.amount === 'number' &&\n typeof state.description === 'string'\n )\n }\n\n /**\n * Override base isState to validate PaymentState\n */\n isState(value?: FormStateValue | FormState): value is FormState {\n return this.isPaymentState(value)\n }\n\n getFormValue(value?: FormStateValue | FormState) {\n return this.isPaymentState(value)\n ? (value as unknown as NonNullable<FormStateValue>)\n : undefined\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n return this.isPaymentState(state)\n ? `Reference: ${state.reference}\\nAmount: ${formatCurrency(state.amount)}`\n : ''\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return PaymentField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n {\n type: 'paymentRequired',\n template: 'Complete the payment to continue'\n }\n ],\n advancedSettingsErrors: []\n }\n }\n\n /**\n * Resolves the payment amount from conditional amounts configuration.\n * Evaluates conditions in order; first true condition wins.\n * Falls back to the default options.amount.\n */\n static resolveAmount(\n options: PaymentFieldComponent['options'],\n model: FormModel,\n state: FormState\n ): number {\n const { conditionalAmounts } = options\n\n if (!conditionalAmounts?.length) {\n return options.amount\n }\n\n for (const { condition, amount } of conditionalAmounts) {\n if (!model.conditions[condition]) {\n logger.warn(\n `[payment] Condition '${condition}' not found in form conditions. Skipping.`\n )\n continue\n }\n if (model.conditions[condition].fn(state)) {\n return amount\n }\n }\n\n return options.amount\n }\n\n /**\n * Dispatcher for external redirect to GOV.UK Pay\n */\n static async dispatcher(\n request: FormRequestPayload,\n h: FormResponseToolkit,\n args: PaymentExternalArgs\n ): Promise<unknown> {\n const { options, name: componentName } = args.component\n const { model } = args.controller\n const { language, t, tComponent } = model.createTranslator(\n resolveLanguage(request)\n )\n const state = await args.controller.getState(request)\n const { baseUrl } = getPluginOptions(request.server)\n const summaryUrl = `${baseUrl}/${model.basePath}/summary`\n\n const existingPaymentState = state[componentName]\n if (\n PaymentField.isPaymentState(existingPaymentState) &&\n existingPaymentState.preAuth?.status === 'success'\n ) {\n return h.redirect(summaryUrl).code(StatusCodes.SEE_OTHER)\n }\n\n const isLivePayment = args.isLive && !args.isPreview\n const formId = args.controller.model.formId\n const formsService = model.services.formsService\n const paymentService = await createPaymentService(\n isLivePayment,\n formId,\n formsService\n )\n\n const uuid = randomUUID()\n\n const reference = state.$$__referenceNumber as string\n const resolvedAmount = PaymentField.resolveAmount(options, model, state)\n\n // Zero-amount safety net (page skip should prevent this, but defensive)\n if (resolvedAmount === 0) {\n return h.redirect(summaryUrl).code(StatusCodes.SEE_OTHER)\n }\n\n const description =\n tComponent(args.component, 'paymentDescription') || options.description\n\n const slug = `/${model.basePath}`\n\n const payCallbackUrl = `${baseUrl}/payment-callback?uuid=${uuid}`\n const paymentPageUrl = args.sourceUrl\n\n let prefilledEmail: string | undefined\n const userConfirmationEmail = state.userConfirmationEmailAddress\n if (typeof userConfirmationEmail === 'string' && userConfirmationEmail) {\n prefilledEmail = userConfirmationEmail\n }\n\n const amountInPence = Math.round(resolvedAmount * 100)\n const payment = await paymentService.createPayment(\n amountInPence,\n description,\n payCallbackUrl,\n reference,\n isLivePayment,\n { formId, slug, language },\n prefilledEmail\n )\n\n if (!payment) {\n const message = isLivePayment\n ? t('components.paymentField.cannotTakePayment')\n : t('components.paymentField.testApiKey')\n const govukError = createError(componentName, message)\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n return h\n .redirect(`${request.url.pathname}${request.url.search}`)\n .code(StatusCodes.SEE_OTHER)\n }\n\n const sessionData: PaymentSessionData = {\n uuid,\n formId,\n reference,\n amount: resolvedAmount,\n description,\n descriptionInEng: options.description,\n paymentId: payment.paymentId,\n componentName,\n returnUrl: summaryUrl,\n failureUrl: paymentPageUrl,\n isLivePayment\n }\n\n request.yar.set(`payment-${uuid}`, sessionData)\n\n return h.redirect(payment.paymentUrl).code(StatusCodes.SEE_OTHER)\n }\n\n /**\n * Called on form submission to capture the payment\n * @see https://docs.payments.service.gov.uk/delayed_capture/#delay-taking-a-payment\n */\n async onSubmit(\n request: FormRequestPayload,\n _metadata: FormMetadata,\n context: FormContext\n ): Promise<void> {\n const language = resolveLanguage(request)\n const { t } = this.model.createTranslator(language)\n\n // Zero-amount bypass — no capture needed\n const resolvedAmount = PaymentField.resolveAmount(\n this.options,\n this.model,\n context.state\n )\n if (resolvedAmount === 0) {\n return\n }\n\n const paymentState = this.getPaymentStateFromState(context.state)\n\n if (!paymentState) {\n throw new PaymentPreAuthError(\n this,\n t('components.paymentField.completePayment'),\n true,\n PaymentErrorTypes.PaymentIncomplete\n )\n }\n\n if (paymentState.capture?.status === 'success') {\n return\n }\n\n const { paymentId, isLivePayment, formId } = paymentState\n const formsService = this.model.services.formsService\n const paymentService = await createPaymentService(\n isLivePayment,\n formId,\n formsService\n )\n\n /**\n * @see https://docs.payments.service.gov.uk/api_reference/#payment-status-lifecycle\n */\n const status = await paymentService.getPaymentStatus(\n paymentId,\n isLivePayment\n )\n\n PaymentSubmissionError.checkPaymentAmount(\n status.amount,\n resolvedAmount,\n this,\n t\n )\n\n if (status.state.status === 'success') {\n await this.markPaymentCaptured(request, paymentState)\n return\n }\n\n if (status.state.status !== 'capturable') {\n throw new PaymentPreAuthError(\n this,\n t('components.paymentField.paymentExpired'),\n true,\n PaymentErrorTypes.PaymentExpired\n )\n }\n\n const captured = await paymentService.capturePayment(\n paymentId,\n status.amount\n )\n\n if (!captured) {\n throw new PaymentPreAuthError(\n this,\n t('components.paymentField.submissionFailed'),\n false\n )\n }\n\n await this.markPaymentCaptured(request, paymentState)\n }\n\n /**\n * Updates payment state to mark capture as successful\n * This ensures we don't try to re-capture on submission retry\n */\n private async markPaymentCaptured(\n request: FormRequestPayload,\n paymentState: PaymentState\n ): Promise<void> {\n const updatedState: PaymentState = {\n ...paymentState,\n capture: {\n status: 'success',\n createdAt: new Date().toISOString()\n }\n }\n\n if (this.page) {\n const currentState = await this.page.getState(request)\n await this.page.mergeState(request, currentState, {\n [this.name]: updatedState\n })\n }\n }\n}\n\n/**\n * Session data stored when dispatching to GOV.UK Pay\n */\nexport interface PaymentSessionData {\n uuid: string\n formId: string\n reference: string\n amount: number\n description: string\n descriptionInEng?: string\n paymentId: string\n componentName: string\n returnUrl: string\n failureUrl: string\n isLivePayment: boolean\n}\n"],"mappings":"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","ignoreList":[]}