@defra/forms-engine-plugin 5.0.0-alpha.2 → 5.0.0-alpha.21

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 (346) hide show
  1. package/.public/javascripts/application.min.js +1 -1
  2. package/.public/javascripts/application.min.js.LICENSE.txt +7 -0
  3. package/.public/javascripts/application.min.js.map +1 -1
  4. package/.public/javascripts/shared.min.js +1 -1
  5. package/.public/javascripts/shared.min.js.LICENSE.txt +7 -0
  6. package/.public/javascripts/shared.min.js.map +1 -1
  7. package/.public/stylesheets/application.min.css.map +1 -1
  8. package/.server/client/javascripts/file-upload.js +6 -3
  9. package/.server/client/javascripts/file-upload.js.map +1 -1
  10. package/.server/client/javascripts/geospatial-map.d.ts +153 -5
  11. package/.server/client/javascripts/geospatial-map.js +157 -77
  12. package/.server/client/javascripts/geospatial-map.js.map +1 -1
  13. package/.server/client/javascripts/govuk.js +2 -1
  14. package/.server/client/javascripts/govuk.js.map +1 -1
  15. package/.server/client/javascripts/location-map.d.ts +44 -0
  16. package/.server/client/javascripts/location-map.js +54 -5
  17. package/.server/client/javascripts/location-map.js.map +1 -1
  18. package/.server/client/javascripts/map-constants.d.ts +3 -0
  19. package/.server/client/javascripts/map-constants.js +4 -0
  20. package/.server/client/javascripts/map-constants.js.map +1 -0
  21. package/.server/client/javascripts/map.d.ts +10 -0
  22. package/.server/client/javascripts/map.js +42 -0
  23. package/.server/client/javascripts/map.js.map +1 -1
  24. package/.server/client/javascripts/maps.d.ts +5 -0
  25. package/.server/client/javascripts/maps.js +6 -0
  26. package/.server/client/javascripts/maps.js.map +1 -0
  27. package/.server/client/javascripts/shared.d.ts +1 -4
  28. package/.server/client/javascripts/shared.js +1 -4
  29. package/.server/client/javascripts/shared.js.map +1 -1
  30. package/.server/client/javascripts/sssi-dataset.d.ts +18 -20
  31. package/.server/client/javascripts/sssi-dataset.js +37 -39
  32. package/.server/client/javascripts/sssi-dataset.js.map +1 -1
  33. package/.server/client/stylesheets/maps.scss +4 -0
  34. package/.server/client/stylesheets/shared.scss +1 -5
  35. package/.server/server/constants.d.ts +2 -0
  36. package/.server/server/constants.js +2 -0
  37. package/.server/server/constants.js.map +1 -1
  38. package/.server/server/plugins/engine/__stubs__/definitions.d.ts +29 -0
  39. package/.server/server/plugins/engine/__stubs__/definitions.js +125 -0
  40. package/.server/server/plugins/engine/__stubs__/definitions.js.map +1 -0
  41. package/.server/server/plugins/engine/components/AutocompleteField.d.ts +9 -3
  42. package/.server/server/plugins/engine/components/AutocompleteField.js +12 -0
  43. package/.server/server/plugins/engine/components/AutocompleteField.js.map +1 -1
  44. package/.server/server/plugins/engine/components/CheckboxesField.d.ts +21 -0
  45. package/.server/server/plugins/engine/components/CheckboxesField.js +30 -5
  46. package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
  47. package/.server/server/plugins/engine/components/ComponentBase.d.ts +1 -0
  48. package/.server/server/plugins/engine/components/ComponentBase.js +2 -0
  49. package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
  50. package/.server/server/plugins/engine/components/ComponentCollection.js +11 -6
  51. package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
  52. package/.server/server/plugins/engine/components/DatePartsField.d.ts +6 -3
  53. package/.server/server/plugins/engine/components/DatePartsField.js +12 -7
  54. package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -1
  55. package/.server/server/plugins/engine/components/DeclarationField.d.ts +5 -2
  56. package/.server/server/plugins/engine/components/DeclarationField.js +6 -4
  57. package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -1
  58. package/.server/server/plugins/engine/components/Details.d.ts +1 -0
  59. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +33 -2
  60. package/.server/server/plugins/engine/components/EastingNorthingField.js +110 -54
  61. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
  62. package/.server/server/plugins/engine/components/EmailAddressField.d.ts +4 -1
  63. package/.server/server/plugins/engine/components/EmailAddressField.js +2 -1
  64. package/.server/server/plugins/engine/components/EmailAddressField.js.map +1 -1
  65. package/.server/server/plugins/engine/components/FileUploadField.d.ts +21 -0
  66. package/.server/server/plugins/engine/components/FileUploadField.js +35 -3
  67. package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
  68. package/.server/server/plugins/engine/components/FormComponent.d.ts +5 -1
  69. package/.server/server/plugins/engine/components/FormComponent.js +5 -2
  70. package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
  71. package/.server/server/plugins/engine/components/GeospatialField.d.ts +23 -0
  72. package/.server/server/plugins/engine/components/GeospatialField.js +33 -9
  73. package/.server/server/plugins/engine/components/GeospatialField.js.map +1 -1
  74. package/.server/server/plugins/engine/components/Html.d.ts +1 -0
  75. package/.server/server/plugins/engine/components/InsetText.d.ts +1 -0
  76. package/.server/server/plugins/engine/components/LatLongField.d.ts +32 -2
  77. package/.server/server/plugins/engine/components/LatLongField.js +85 -48
  78. package/.server/server/plugins/engine/components/LatLongField.js.map +1 -1
  79. package/.server/server/plugins/engine/components/List.d.ts +1 -0
  80. package/.server/server/plugins/engine/components/ListFormComponent.d.ts +1 -0
  81. package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +8 -2
  82. package/.server/server/plugins/engine/components/LocationFieldBase.js +25 -6
  83. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
  84. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +12 -3
  85. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +21 -13
  86. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
  87. package/.server/server/plugins/engine/components/Markdown.d.ts +1 -0
  88. package/.server/server/plugins/engine/components/Markdown.js +1 -1
  89. package/.server/server/plugins/engine/components/Markdown.js.map +1 -1
  90. package/.server/server/plugins/engine/components/MonthYearField.d.ts +5 -2
  91. package/.server/server/plugins/engine/components/MonthYearField.js +13 -9
  92. package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -1
  93. package/.server/server/plugins/engine/components/MultilineTextField.d.ts +1 -0
  94. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +2 -1
  95. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +4 -3
  96. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -1
  97. package/.server/server/plugins/engine/components/NotificationBanner.d.ts +1 -0
  98. package/.server/server/plugins/engine/components/NumberField.d.ts +5 -0
  99. package/.server/server/plugins/engine/components/NumberField.js +16 -0
  100. package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
  101. package/.server/server/plugins/engine/components/OsGridRefField.d.ts +103 -1
  102. package/.server/server/plugins/engine/components/OsGridRefField.js +13 -3
  103. package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -1
  104. package/.server/server/plugins/engine/components/PaymentField.d.ts +3 -0
  105. package/.server/server/plugins/engine/components/PaymentField.js +17 -6
  106. package/.server/server/plugins/engine/components/PaymentField.js.map +1 -1
  107. package/.server/server/plugins/engine/components/SelectField.d.ts +1 -0
  108. package/.server/server/plugins/engine/components/SelectionControlField.d.ts +1 -0
  109. package/.server/server/plugins/engine/components/TelephoneNumberField.d.ts +1 -0
  110. package/.server/server/plugins/engine/components/UkAddressField.d.ts +1 -0
  111. package/.server/server/plugins/engine/components/UkAddressField.js +23 -8
  112. package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
  113. package/.server/server/plugins/engine/components/YesNoField.d.ts +5 -2
  114. package/.server/server/plugins/engine/components/YesNoField.js +17 -8
  115. package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
  116. package/.server/server/plugins/engine/components/helpers/components.js +3 -3
  117. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  118. package/.server/server/plugins/engine/components/helpers/geospatial.d.ts +8 -1
  119. package/.server/server/plugins/engine/components/helpers/geospatial.js +56 -15
  120. package/.server/server/plugins/engine/components/helpers/geospatial.js.map +1 -1
  121. package/.server/server/plugins/engine/components/helpers/index.d.ts +7 -0
  122. package/.server/server/plugins/engine/components/helpers/index.js +13 -1
  123. package/.server/server/plugins/engine/components/helpers/index.js.map +1 -1
  124. package/.server/server/plugins/engine/components/types.d.ts +1 -0
  125. package/.server/server/plugins/engine/components/types.js.map +1 -1
  126. package/.server/server/plugins/engine/errors.d.ts +49 -0
  127. package/.server/server/plugins/engine/errors.js +75 -0
  128. package/.server/server/plugins/engine/errors.js.map +1 -0
  129. package/.server/server/plugins/engine/form-availability.d.ts +3 -2
  130. package/.server/server/plugins/engine/form-availability.js +3 -2
  131. package/.server/server/plugins/engine/form-availability.js.map +1 -1
  132. package/.server/server/plugins/engine/form-context.js +2 -2
  133. package/.server/server/plugins/engine/form-context.js.map +1 -1
  134. package/.server/server/plugins/engine/helpers.d.ts +22 -2
  135. package/.server/server/plugins/engine/helpers.js +44 -5
  136. package/.server/server/plugins/engine/helpers.js.map +1 -1
  137. package/.server/server/plugins/engine/i18n/buildValidationMessages.d.ts +4 -1
  138. package/.server/server/plugins/engine/i18n/buildValidationMessages.js +31 -7
  139. package/.server/server/plugins/engine/i18n/buildValidationMessages.js.map +1 -1
  140. package/.server/server/plugins/engine/i18n/createFormTranslator.d.ts +4 -4
  141. package/.server/server/plugins/engine/i18n/createFormTranslator.js +33 -4
  142. package/.server/server/plugins/engine/i18n/createFormTranslator.js.map +1 -1
  143. package/.server/server/plugins/engine/i18n/extractBaseTranslations.d.ts +1 -1
  144. package/.server/server/plugins/engine/i18n/extractBaseTranslations.js +9 -0
  145. package/.server/server/plugins/engine/i18n/extractBaseTranslations.js.map +1 -1
  146. package/.server/server/plugins/engine/i18n/form.d.ts +13 -0
  147. package/.server/server/plugins/engine/i18n/form.js +35 -0
  148. package/.server/server/plugins/engine/i18n/form.js.map +1 -0
  149. package/.server/server/plugins/engine/i18n/languages.d.ts +10 -0
  150. package/.server/server/plugins/engine/i18n/languages.js +19 -0
  151. package/.server/server/plugins/engine/i18n/languages.js.map +1 -0
  152. package/.server/server/plugins/engine/i18n/translations/cy.json +192 -9
  153. package/.server/server/plugins/engine/i18n/translations/en-GB.json +125 -7
  154. package/.server/server/plugins/engine/i18n/types.d.ts +3 -0
  155. package/.server/server/plugins/engine/i18n/types.js.map +1 -1
  156. package/.server/server/plugins/engine/index.d.ts +4 -0
  157. package/.server/server/plugins/engine/index.js +4 -0
  158. package/.server/server/plugins/engine/index.js.map +1 -1
  159. package/.server/server/plugins/engine/models/FormModel.d.ts +0 -4
  160. package/.server/server/plugins/engine/models/FormModel.js +35 -23
  161. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  162. package/.server/server/plugins/engine/models/SummaryViewModel.js +9 -4
  163. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  164. package/.server/server/plugins/engine/models/types.d.ts +28 -1
  165. package/.server/server/plugins/engine/models/types.js.map +1 -1
  166. package/.server/server/plugins/engine/models/unavailable-view-model.d.ts +11 -2
  167. package/.server/server/plugins/engine/models/unavailable-view-model.js +23 -4
  168. package/.server/server/plugins/engine/models/unavailable-view-model.js.map +1 -1
  169. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +14 -6
  170. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -1
  171. package/.server/server/plugins/engine/outputFormatters/human/v1.js +2 -1
  172. package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
  173. package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +2 -0
  174. package/.server/server/plugins/engine/outputFormatters/machine/v2.js +2 -0
  175. package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
  176. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +7 -1
  177. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +56 -10
  178. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  179. package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +1 -1
  180. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +7 -3
  181. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  182. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js +3 -4
  183. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js.map +1 -1
  184. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +10 -4
  185. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  186. package/.server/server/plugins/engine/pageControllers/helpers/pages.js +2 -1
  187. package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -1
  188. package/.server/server/plugins/engine/pageControllers/helpers/submission.d.ts +35 -2
  189. package/.server/server/plugins/engine/pageControllers/helpers/submission.js +100 -3
  190. package/.server/server/plugins/engine/pageControllers/helpers/submission.js.map +1 -1
  191. package/.server/server/plugins/engine/pageControllers/validationOptions.d.ts +1 -1
  192. package/.server/server/plugins/engine/pageControllers/validationOptions.js +24 -3
  193. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  194. package/.server/server/plugins/engine/types/enums.d.ts +3 -3
  195. package/.server/server/plugins/engine/types/enums.js +5 -4
  196. package/.server/server/plugins/engine/types/enums.js.map +1 -1
  197. package/.server/server/plugins/engine/types/schema.js +10 -3
  198. package/.server/server/plugins/engine/types/schema.js.map +1 -1
  199. package/.server/server/plugins/engine/types.d.ts +18 -2
  200. package/.server/server/plugins/engine/types.js.map +1 -1
  201. package/.server/server/plugins/engine/unavailable-response.js +5 -2
  202. package/.server/server/plugins/engine/unavailable-response.js.map +1 -1
  203. package/.server/server/plugins/engine/views/components/_location-field-base.html +4 -2
  204. package/.server/server/plugins/engine/views/components/fileuploadfield.html +1 -1
  205. package/.server/server/plugins/engine/views/components/geospatialfield.html +1 -0
  206. package/.server/server/plugins/engine/views/components/multilinetextfield.html +12 -1
  207. package/.server/server/plugins/engine/views/components/osgridreffield.html +4 -1
  208. package/.server/server/plugins/engine/views/components/paymentfield.html +1 -1
  209. package/.server/server/plugins/engine/views/components/ukaddressfield.html +1 -1
  210. package/.server/server/plugins/engine/views/file-upload.html +1 -1
  211. package/.server/server/plugins/engine/views/partials/preview-banner.html +3 -1
  212. package/.server/server/plugins/engine/views/unavailable.html +14 -11
  213. package/.server/server/plugins/nunjucks/filters/applyUrlParam.d.ts +9 -0
  214. package/.server/server/plugins/nunjucks/filters/applyUrlParam.js +17 -0
  215. package/.server/server/plugins/nunjucks/filters/applyUrlParam.js.map +1 -0
  216. package/.server/server/plugins/nunjucks/filters/applyUrlParam.test.js +17 -0
  217. package/.server/server/plugins/nunjucks/filters/applyUrlParam.test.js.map +1 -0
  218. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  219. package/.server/server/plugins/nunjucks/filters/index.d.ts +1 -0
  220. package/.server/server/plugins/nunjucks/filters/index.js +1 -0
  221. package/.server/server/plugins/nunjucks/filters/index.js.map +1 -1
  222. package/.server/server/plugins/payment/helper.d.ts +11 -1
  223. package/.server/server/plugins/payment/helper.js +18 -2
  224. package/.server/server/plugins/payment/helper.js.map +1 -1
  225. package/.server/server/plugins/payment/helper.test.js +2 -1
  226. package/.server/server/plugins/payment/helper.test.js.map +1 -1
  227. package/.server/server/plugins/payment/service.d.ts +2 -1
  228. package/.server/server/plugins/payment/service.js +5 -2
  229. package/.server/server/plugins/payment/service.js.map +1 -1
  230. package/.server/server/plugins/payment/service.test.js +7 -3
  231. package/.server/server/plugins/payment/service.test.js.map +1 -1
  232. package/.server/server/plugins/payment/types.d.ts +4 -0
  233. package/.server/server/plugins/payment/types.js +1 -0
  234. package/.server/server/plugins/payment/types.js.map +1 -1
  235. package/.server/server/plugins/postcode-lookup/models/index.d.ts +249 -3
  236. package/.server/server/plugins/postcode-lookup/models/index.js +87 -33
  237. package/.server/server/plugins/postcode-lookup/models/index.js.map +1 -1
  238. package/.server/server/plugins/postcode-lookup/routes/index.d.ts +15 -1
  239. package/.server/server/plugins/postcode-lookup/routes/index.js +47 -15
  240. package/.server/server/plugins/postcode-lookup/routes/index.js.map +1 -1
  241. package/.server/server/plugins/postcode-lookup/routes/index.test.js +84 -0
  242. package/.server/server/plugins/postcode-lookup/routes/index.test.js.map +1 -0
  243. package/.server/server/plugins/postcode-lookup/types.d.ts +12 -0
  244. package/.server/server/plugins/postcode-lookup/types.js +6 -1
  245. package/.server/server/plugins/postcode-lookup/types.js.map +1 -1
  246. package/.server/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
  247. package/.server/server/utils/type-utils.d.ts +1 -2
  248. package/.server/server/utils/type-utils.js +0 -3
  249. package/.server/server/utils/type-utils.js.map +1 -1
  250. package/.server/server/utils/utils.d.ts +6 -0
  251. package/.server/server/utils/utils.js +18 -1
  252. package/.server/server/utils/utils.js.map +1 -1
  253. package/.server/server/utils/utils.test.js +21 -1
  254. package/.server/server/utils/utils.test.js.map +1 -1
  255. package/package.json +20 -5
  256. package/src/client/javascripts/file-upload.js +11 -3
  257. package/src/client/javascripts/geospatial-map.js +176 -81
  258. package/src/client/javascripts/govuk.js +2 -0
  259. package/src/client/javascripts/location-map.js +60 -4
  260. package/src/client/javascripts/map-constants.js +3 -0
  261. package/src/client/javascripts/map.js +47 -0
  262. package/src/client/javascripts/maps.js +5 -0
  263. package/src/client/javascripts/shared.js +1 -4
  264. package/src/client/javascripts/sssi-dataset.js +39 -41
  265. package/src/client/stylesheets/maps.scss +4 -0
  266. package/src/client/stylesheets/shared.scss +1 -5
  267. package/src/server/constants.js +2 -0
  268. package/src/server/plugins/engine/__stubs__/definitions.ts +155 -0
  269. package/src/server/plugins/engine/components/AutocompleteField.ts +13 -0
  270. package/src/server/plugins/engine/components/CheckboxesField.ts +38 -5
  271. package/src/server/plugins/engine/components/ComponentBase.ts +2 -0
  272. package/src/server/plugins/engine/components/ComponentCollection.ts +16 -11
  273. package/src/server/plugins/engine/components/DatePartsField.ts +20 -9
  274. package/src/server/plugins/engine/components/DeclarationField.ts +7 -6
  275. package/src/server/plugins/engine/components/EastingNorthingField.ts +185 -89
  276. package/src/server/plugins/engine/components/EmailAddressField.ts +2 -1
  277. package/src/server/plugins/engine/components/FileUploadField.ts +44 -3
  278. package/src/server/plugins/engine/components/FormComponent.ts +6 -5
  279. package/src/server/plugins/engine/components/GeospatialField.ts +47 -9
  280. package/src/server/plugins/engine/components/LatLongField.ts +128 -58
  281. package/src/server/plugins/engine/components/LocationFieldBase.ts +30 -7
  282. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +28 -14
  283. package/src/server/plugins/engine/components/Markdown.ts +2 -3
  284. package/src/server/plugins/engine/components/MonthYearField.ts +20 -15
  285. package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +9 -3
  286. package/src/server/plugins/engine/components/NumberField.ts +21 -1
  287. package/src/server/plugins/engine/components/OsGridRefField.ts +22 -3
  288. package/src/server/plugins/engine/components/PaymentField.ts +15 -5
  289. package/src/server/plugins/engine/components/UkAddressField.ts +30 -7
  290. package/src/server/plugins/engine/components/YesNoField.ts +17 -9
  291. package/src/server/plugins/engine/components/helpers/components.ts +3 -3
  292. package/src/server/plugins/engine/components/helpers/geospatial.ts +84 -21
  293. package/src/server/plugins/engine/components/helpers/index.ts +19 -0
  294. package/src/server/plugins/engine/components/types.ts +1 -0
  295. package/src/server/plugins/engine/errors.ts +78 -0
  296. package/src/server/plugins/engine/form-availability.ts +8 -2
  297. package/src/server/plugins/engine/form-context.ts +4 -2
  298. package/src/server/plugins/engine/helpers.ts +52 -5
  299. package/src/server/plugins/engine/i18n/buildValidationMessages.ts +16 -8
  300. package/src/server/plugins/engine/i18n/createFormTranslator.ts +62 -11
  301. package/src/server/plugins/engine/i18n/extractBaseTranslations.ts +7 -1
  302. package/src/server/plugins/engine/i18n/form.ts +54 -0
  303. package/src/server/plugins/engine/i18n/languages.ts +19 -0
  304. package/src/server/plugins/engine/i18n/translations/cy.json +192 -9
  305. package/src/server/plugins/engine/i18n/translations/en-GB.json +125 -7
  306. package/src/server/plugins/engine/i18n/types.ts +3 -0
  307. package/src/server/plugins/engine/index.ts +7 -0
  308. package/src/server/plugins/engine/models/FormModel.ts +46 -23
  309. package/src/server/plugins/engine/models/SummaryViewModel.ts +6 -5
  310. package/src/server/plugins/engine/models/types.ts +31 -0
  311. package/src/server/plugins/engine/models/unavailable-view-model.ts +26 -5
  312. package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +18 -7
  313. package/src/server/plugins/engine/outputFormatters/human/v1.ts +2 -1
  314. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +3 -0
  315. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +60 -9
  316. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +10 -5
  317. package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +2 -2
  318. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +15 -4
  319. package/src/server/plugins/engine/pageControllers/helpers/pages.ts +2 -1
  320. package/src/server/plugins/engine/pageControllers/helpers/submission.ts +143 -4
  321. package/src/server/plugins/engine/pageControllers/validationOptions.ts +29 -4
  322. package/src/server/plugins/engine/types/enums.ts +4 -3
  323. package/src/server/plugins/engine/types/schema.ts +17 -4
  324. package/src/server/plugins/engine/types.ts +19 -1
  325. package/src/server/plugins/engine/unavailable-response.ts +6 -2
  326. package/src/server/plugins/engine/views/components/_location-field-base.html +4 -2
  327. package/src/server/plugins/engine/views/components/fileuploadfield.html +1 -1
  328. package/src/server/plugins/engine/views/components/geospatialfield.html +1 -0
  329. package/src/server/plugins/engine/views/components/multilinetextfield.html +12 -1
  330. package/src/server/plugins/engine/views/components/osgridreffield.html +4 -1
  331. package/src/server/plugins/engine/views/components/paymentfield.html +1 -1
  332. package/src/server/plugins/engine/views/components/ukaddressfield.html +1 -1
  333. package/src/server/plugins/engine/views/file-upload.html +1 -1
  334. package/src/server/plugins/engine/views/partials/preview-banner.html +3 -1
  335. package/src/server/plugins/engine/views/unavailable.html +14 -11
  336. package/src/server/plugins/nunjucks/filters/applyUrlParam.js +17 -0
  337. package/src/server/plugins/nunjucks/filters/index.js +1 -0
  338. package/src/server/plugins/payment/helper.js +22 -2
  339. package/src/server/plugins/payment/service.js +6 -2
  340. package/src/server/plugins/payment/types.js +1 -0
  341. package/src/server/plugins/postcode-lookup/models/index.js +75 -43
  342. package/src/server/plugins/postcode-lookup/routes/index.js +73 -16
  343. package/src/server/plugins/postcode-lookup/types.js +6 -1
  344. package/src/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
  345. package/src/server/utils/type-utils.ts +1 -11
  346. package/src/server/utils/utils.js +19 -1
@@ -1 +1 @@
1
- {"version":3,"file":"v1.js","names":["getFormVersion","categoriseData","FormAdapterSubmissionSchemaVersion","format","context","items","model","submitResponse","formStatus","formMetadata","csvFiles","extractCsvFiles","main","v2Main","v2Data","versionMetadata","def","meta","schemaVersion","V1","timestamp","Date","referenceNumber","formName","name","formId","id","formSlug","slug","status","state","isPreview","notificationEmail","definitionLanguage","metadata","language","Object","fromEntries","entries","map","key","value","undefined","data","result","files","payload","JSON","stringify","repeaters"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/adapter/v1.ts"],"sourcesContent":["import {\n type FormMetadata,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport {\n getFormVersion,\n type checkFormStatus\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { categoriseData } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'\nimport { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/enums.js'\nimport {\n type FormAdapterSubmissionMessageData,\n type FormAdapterSubmissionMessageMeta,\n type FormAdapterSubmissionMessagePayload,\n type FormAdapterSubmissionMessageResult,\n type FormContext\n} from '~/src/server/plugins/engine/types.js'\n\nexport function format(\n context: FormContext,\n items: DetailItem[],\n model: FormModel,\n submitResponse: SubmitResponsePayload,\n formStatus: ReturnType<typeof checkFormStatus>,\n formMetadata?: FormMetadata\n): string {\n const csvFiles = extractCsvFiles(submitResponse)\n\n const { main: v2Main, ...v2Data } = categoriseData(items)\n\n const versionMetadata = getFormVersion(model.def)\n\n const meta: FormAdapterSubmissionMessageMeta = {\n schemaVersion: FormAdapterSubmissionSchemaVersion.V1,\n timestamp: new Date(),\n referenceNumber: context.referenceNumber,\n formName: model.name,\n formId: formMetadata?.id ?? '',\n formSlug: formMetadata?.slug ?? '',\n status: formStatus.state,\n isPreview: formStatus.isPreview,\n notificationEmail: formMetadata?.notificationEmail ?? ''\n }\n\n if (versionMetadata) {\n meta.versionMetadata = versionMetadata\n }\n\n const definitionLanguage = model.def.metadata?.language\n if (typeof definitionLanguage === 'string') {\n meta.language = definitionLanguage\n }\n\n const main = Object.fromEntries(\n Object.entries(v2Main).map(([key, value]) => {\n if (value === undefined) {\n return [key, null]\n }\n\n return [key, value]\n })\n )\n\n const data: FormAdapterSubmissionMessageData = {\n main,\n ...v2Data\n }\n\n const result: FormAdapterSubmissionMessageResult = {\n files: csvFiles\n }\n\n const payload: FormAdapterSubmissionMessagePayload = {\n meta,\n data,\n result\n }\n\n return JSON.stringify(payload)\n}\n\nfunction extractCsvFiles(\n submitResponse: SubmitResponsePayload\n): FormAdapterSubmissionMessageResult['files'] {\n const result =\n submitResponse.result as Partial<FormAdapterSubmissionMessageResult>\n\n return {\n main: result.files?.main ?? '',\n repeaters: result.files?.repeaters ?? {}\n }\n}\n"],"mappings":"AAKA,SACEA,cAAc;AAKhB,SAASC,cAAc;AACvB,SAASC,kCAAkC;AAS3C,OAAO,SAASC,MAAMA,CACpBC,OAAoB,EACpBC,KAAmB,EACnBC,KAAgB,EAChBC,cAAqC,EACrCC,UAA8C,EAC9CC,YAA2B,EACnB;EACR,MAAMC,QAAQ,GAAGC,eAAe,CAACJ,cAAc,CAAC;EAEhD,MAAM;IAAEK,IAAI,EAAEC,MAAM;IAAE,GAAGC;EAAO,CAAC,GAAGb,cAAc,CAACI,KAAK,CAAC;EAEzD,MAAMU,eAAe,GAAGf,cAAc,CAACM,KAAK,CAACU,GAAG,CAAC;EAEjD,MAAMC,IAAsC,GAAG;IAC7CC,aAAa,EAAEhB,kCAAkC,CAACiB,EAAE;IACpDC,SAAS,EAAE,IAAIC,IAAI,CAAC,CAAC;IACrBC,eAAe,EAAElB,OAAO,CAACkB,eAAe;IACxCC,QAAQ,EAAEjB,KAAK,CAACkB,IAAI;IACpBC,MAAM,EAAEhB,YAAY,EAAEiB,EAAE,IAAI,EAAE;IAC9BC,QAAQ,EAAElB,YAAY,EAAEmB,IAAI,IAAI,EAAE;IAClCC,MAAM,EAAErB,UAAU,CAACsB,KAAK;IACxBC,SAAS,EAAEvB,UAAU,CAACuB,SAAS;IAC/BC,iBAAiB,EAAEvB,YAAY,EAAEuB,iBAAiB,IAAI;EACxD,CAAC;EAED,IAAIjB,eAAe,EAAE;IACnBE,IAAI,CAACF,eAAe,GAAGA,eAAe;EACxC;EAEA,MAAMkB,kBAAkB,GAAG3B,KAAK,CAACU,GAAG,CAACkB,QAAQ,EAAEC,QAAQ;EACvD,IAAI,OAAOF,kBAAkB,KAAK,QAAQ,EAAE;IAC1ChB,IAAI,CAACkB,QAAQ,GAAGF,kBAAkB;EACpC;EAEA,MAAMrB,IAAI,GAAGwB,MAAM,CAACC,WAAW,CAC7BD,MAAM,CAACE,OAAO,CAACzB,MAAM,CAAC,CAAC0B,GAAG,CAAC,CAAC,CAACC,GAAG,EAAEC,KAAK,CAAC,KAAK;IAC3C,IAAIA,KAAK,KAAKC,SAAS,EAAE;MACvB,OAAO,CAACF,GAAG,EAAE,IAAI,CAAC;IACpB;IAEA,OAAO,CAACA,GAAG,EAAEC,KAAK,CAAC;EACrB,CAAC,CACH,CAAC;EAED,MAAME,IAAsC,GAAG;IAC7C/B,IAAI;IACJ,GAAGE;EACL,CAAC;EAED,MAAM8B,MAA0C,GAAG;IACjDC,KAAK,EAAEnC;EACT,CAAC;EAED,MAAMoC,OAA4C,GAAG;IACnD7B,IAAI;IACJ0B,IAAI;IACJC;EACF,CAAC;EAED,OAAOG,IAAI,CAACC,SAAS,CAACF,OAAO,CAAC;AAChC;AAEA,SAASnC,eAAeA,CACtBJ,cAAqC,EACQ;EAC7C,MAAMqC,MAAM,GACVrC,cAAc,CAACqC,MAAqD;EAEtE,OAAO;IACLhC,IAAI,EAAEgC,MAAM,CAACC,KAAK,EAAEjC,IAAI,IAAI,EAAE;IAC9BqC,SAAS,EAAEL,MAAM,CAACC,KAAK,EAAEI,SAAS,IAAI,CAAC;EACzC,CAAC;AACH","ignoreList":[]}
1
+ {"version":3,"file":"v1.js","names":["Engine","EN_GB","getFormVersion","categoriseData","buildConditionEvaluations","FormAdapterSubmissionSchemaVersion","format","context","items","model","submitResponse","formStatus","formMetadata","csvFiles","extractCsvFiles","main","v2Main","v2Data","versionMetadata","def","meta","schemaVersion","V1","timestamp","Date","referenceNumber","formName","name","formId","id","formSlug","slug","status","state","isPreview","notificationEmail","language","translator","Object","fromEntries","entries","map","key","value","undefined","data","result","files","payload","conditionEvaluations","engine","V2","JSON","stringify","repeaters"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/adapter/v1.ts"],"sourcesContent":["import {\n Engine,\n type FormMetadata,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { EN_GB } from '~/src/server/constants.js'\nimport {\n getFormVersion,\n type checkFormStatus\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { categoriseData } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'\nimport { buildConditionEvaluations } from '~/src/server/plugins/engine/pageControllers/helpers/submission.js'\nimport { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/enums.js'\nimport {\n type FormAdapterSubmissionMessageData,\n type FormAdapterSubmissionMessageMeta,\n type FormAdapterSubmissionMessagePayload,\n type FormAdapterSubmissionMessageResult,\n type FormContext\n} from '~/src/server/plugins/engine/types.js'\n\nexport function format(\n context: FormContext,\n items: DetailItem[],\n model: FormModel,\n submitResponse: SubmitResponsePayload,\n formStatus: ReturnType<typeof checkFormStatus>,\n formMetadata?: FormMetadata\n): string {\n const csvFiles = extractCsvFiles(submitResponse)\n\n const { main: v2Main, ...v2Data } = categoriseData(items)\n\n const versionMetadata = getFormVersion(model.def)\n\n const meta: FormAdapterSubmissionMessageMeta = {\n schemaVersion: FormAdapterSubmissionSchemaVersion.V1,\n timestamp: new Date(),\n referenceNumber: context.referenceNumber,\n formName: model.name,\n formId: formMetadata?.id ?? '',\n formSlug: formMetadata?.slug ?? '',\n status: formStatus.state,\n isPreview: formStatus.isPreview,\n notificationEmail: formMetadata?.notificationEmail ?? '',\n language: context.translator?.language ?? EN_GB\n }\n\n if (versionMetadata) {\n meta.versionMetadata = versionMetadata\n }\n\n const main = Object.fromEntries(\n Object.entries(v2Main).map(([key, value]) => {\n if (value === undefined) {\n return [key, null]\n }\n\n return [key, value]\n })\n )\n\n const data: FormAdapterSubmissionMessageData = {\n main,\n ...v2Data\n }\n\n const result: FormAdapterSubmissionMessageResult = {\n files: csvFiles\n }\n\n const payload: FormAdapterSubmissionMessagePayload = {\n meta,\n data,\n result,\n\n // Recorded here because only the engine holds the walked evaluation state\n // the conditions were judged against; forms-notify-listener receives the\n // flat submitted answers and resolves the outputs that qualify from these\n // outcomes alone. Condition ids are only stable in V2, so a V1 definition\n // has nothing to report - the property is still emitted, because its\n // absence is what marks a message as predating this and sends the listener\n // down its legacy path.\n conditionEvaluations:\n model.engine === Engine.V2\n ? buildConditionEvaluations(model, context)\n : []\n }\n\n return JSON.stringify(payload)\n}\n\nfunction extractCsvFiles(\n submitResponse: SubmitResponsePayload\n): FormAdapterSubmissionMessageResult['files'] {\n const result =\n submitResponse.result as Partial<FormAdapterSubmissionMessageResult>\n\n return {\n main: result.files?.main ?? '',\n repeaters: result.files?.repeaters ?? {}\n }\n}\n"],"mappings":"AAAA,SACEA,MAAM,QAGD,oBAAoB;AAE3B,SAASC,KAAK;AACd,SACEC,cAAc;AAKhB,SAASC,cAAc;AACvB,SAASC,yBAAyB;AAClC,SAASC,kCAAkC;AAS3C,OAAO,SAASC,MAAMA,CACpBC,OAAoB,EACpBC,KAAmB,EACnBC,KAAgB,EAChBC,cAAqC,EACrCC,UAA8C,EAC9CC,YAA2B,EACnB;EACR,MAAMC,QAAQ,GAAGC,eAAe,CAACJ,cAAc,CAAC;EAEhD,MAAM;IAAEK,IAAI,EAAEC,MAAM;IAAE,GAAGC;EAAO,CAAC,GAAGd,cAAc,CAACK,KAAK,CAAC;EAEzD,MAAMU,eAAe,GAAGhB,cAAc,CAACO,KAAK,CAACU,GAAG,CAAC;EAEjD,MAAMC,IAAsC,GAAG;IAC7CC,aAAa,EAAEhB,kCAAkC,CAACiB,EAAE;IACpDC,SAAS,EAAE,IAAIC,IAAI,CAAC,CAAC;IACrBC,eAAe,EAAElB,OAAO,CAACkB,eAAe;IACxCC,QAAQ,EAAEjB,KAAK,CAACkB,IAAI;IACpBC,MAAM,EAAEhB,YAAY,EAAEiB,EAAE,IAAI,EAAE;IAC9BC,QAAQ,EAAElB,YAAY,EAAEmB,IAAI,IAAI,EAAE;IAClCC,MAAM,EAAErB,UAAU,CAACsB,KAAK;IACxBC,SAAS,EAAEvB,UAAU,CAACuB,SAAS;IAC/BC,iBAAiB,EAAEvB,YAAY,EAAEuB,iBAAiB,IAAI,EAAE;IACxDC,QAAQ,EAAE7B,OAAO,CAAC8B,UAAU,EAAED,QAAQ,IAAInC;EAC5C,CAAC;EAED,IAAIiB,eAAe,EAAE;IACnBE,IAAI,CAACF,eAAe,GAAGA,eAAe;EACxC;EAEA,MAAMH,IAAI,GAAGuB,MAAM,CAACC,WAAW,CAC7BD,MAAM,CAACE,OAAO,CAACxB,MAAM,CAAC,CAACyB,GAAG,CAAC,CAAC,CAACC,GAAG,EAAEC,KAAK,CAAC,KAAK;IAC3C,IAAIA,KAAK,KAAKC,SAAS,EAAE;MACvB,OAAO,CAACF,GAAG,EAAE,IAAI,CAAC;IACpB;IAEA,OAAO,CAACA,GAAG,EAAEC,KAAK,CAAC;EACrB,CAAC,CACH,CAAC;EAED,MAAME,IAAsC,GAAG;IAC7C9B,IAAI;IACJ,GAAGE;EACL,CAAC;EAED,MAAM6B,MAA0C,GAAG;IACjDC,KAAK,EAAElC;EACT,CAAC;EAED,MAAMmC,OAA4C,GAAG;IACnD5B,IAAI;IACJyB,IAAI;IACJC,MAAM;IAEN;IACA;IACA;IACA;IACA;IACA;IACA;IACAG,oBAAoB,EAClBxC,KAAK,CAACyC,MAAM,KAAKlD,MAAM,CAACmD,EAAE,GACtB/C,yBAAyB,CAACK,KAAK,EAAEF,OAAO,CAAC,GACzC;EACR,CAAC;EAED,OAAO6C,IAAI,CAACC,SAAS,CAACL,OAAO,CAAC;AAChC;AAEA,SAASlC,eAAeA,CACtBJ,cAAqC,EACQ;EAC7C,MAAMoC,MAAM,GACVpC,cAAc,CAACoC,MAAqD;EAEtE,OAAO;IACL/B,IAAI,EAAE+B,MAAM,CAACC,KAAK,EAAEhC,IAAI,IAAI,EAAE;IAC9BuC,SAAS,EAAER,MAAM,CAACC,KAAK,EAAEO,SAAS,IAAI,CAAC;EACzC,CAAC;AACH","ignoreList":[]}
@@ -1,5 +1,6 @@
1
1
  import { addDays, format as dateFormat } from 'date-fns';
2
2
  import { config } from "../../../../../config/index.js";
3
+ import { EN_GB } from "../../../../constants.js";
3
4
  import { getAnswer } from "../../components/helpers/components.js";
4
5
  import { escapeMarkdown } from "../../components/helpers/index.js";
5
6
  import { PaymentField } from "../../components/index.js";
@@ -73,7 +74,7 @@ function appendPaymentSection(paymentItems, lines) {
73
74
  return;
74
75
  }
75
76
  const formattedAmount = formatCurrency(paymentState.amount);
76
- const dateOfPayment = paymentState.preAuth?.createdAt ? formatPaymentDate(paymentState.preAuth.createdAt) : '';
77
+ const dateOfPayment = paymentState.preAuth?.createdAt ? formatPaymentDate(paymentState.preAuth.createdAt, EN_GB) : '';
77
78
  lines.push('---\n', `# Your payment of ${formattedAmount} was successful\n`, '## Payment for\n', `${escapeMarkdown(paymentState.description)}\n`, '---\n', '## Total amount\n', `${formattedAmount}\n`, '---\n', '## Date of payment\n', `${escapeMarkdown(dateOfPayment)}\n`, '---\n');
78
79
  }
79
80
  //# sourceMappingURL=v1.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"v1.js","names":["addDays","format","dateFormat","config","getAnswer","escapeMarkdown","PaymentField","formatCurrency","formatPaymentDate","designerUrl","get","_context","items","model","submitResponse","formStatus","_formMetadata","translator","createTranslator","files","result","formName","name","now","Date","formattedNow","fileExpiryDate","formattedExpiryDate","lines","push","isPreview","state","regularItems","filter","item","isPaymentItem","paymentItems","forEach","label","filename","fileId","repeaters","field","main","appendPaymentSection","join","length","paymentItem","paymentField","paymentState","getPaymentStateFromState","formattedAmount","amount","dateOfPayment","preAuth","createdAt","description"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/human/v1.ts"],"sourcesContent":["import {\n type FormMetadata,\n type SubmitResponsePayload\n} from '@defra/forms-model'\nimport { addDays, format as dateFormat } from 'date-fns'\n\nimport { config } from '~/src/config/index.js'\nimport { getAnswer } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers/index.js'\nimport { PaymentField } from '~/src/server/plugins/engine/components/index.js'\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type DetailItem,\n type DetailItemField\n} from '~/src/server/plugins/engine/models/types.js'\nimport { type FormContext } from '~/src/server/plugins/engine/types.js'\nimport {\n formatCurrency,\n formatPaymentDate\n} from '~/src/server/plugins/payment/helper.js'\n\nconst designerUrl = config.get('designerUrl')\n\nexport function format(\n _context: FormContext,\n items: DetailItem[],\n model: FormModel,\n submitResponse: SubmitResponsePayload,\n formStatus: ReturnType<typeof checkFormStatus>,\n _formMetadata?: FormMetadata\n) {\n const translator = model.createTranslator()\n const { files } = submitResponse.result\n\n const formName = escapeMarkdown(model.name)\n\n /**\n * @todo Refactor this below but the code to\n * generate the question and answers works for now\n */\n const now = new Date()\n const formattedNow = `${dateFormat(now, 'h:mmaaa')} on ${dateFormat(now, 'd MMMM yyyy')}`\n\n const fileExpiryDate = addDays(now, 90)\n const formattedExpiryDate = `${dateFormat(fileExpiryDate, 'h:mmaaa')} on ${dateFormat(fileExpiryDate, 'eeee d MMMM yyyy')}`\n\n const lines: string[] = []\n\n lines.push(\n `^ For security reasons, the links in this email expire at ${escapeMarkdown(formattedExpiryDate)}\\n`\n )\n\n if (formStatus.isPreview) {\n lines.push(`This is a test of the ${formName} ${formStatus.state} form.\\n`)\n }\n\n lines.push(`${formName} form received at ${escapeMarkdown(formattedNow)}.\\n`)\n lines.push('---\\n')\n\n const regularItems = items.filter((item) => !isPaymentItem(item))\n const paymentItems = items.filter((item) => isPaymentItem(item))\n\n regularItems.forEach((item) => {\n const label = escapeMarkdown(item.label)\n\n lines.push(`## ${label}\\n`)\n\n if ('subItems' in item) {\n const filename = escapeMarkdown(`Download ${label} (CSV)`)\n const fileId = files.repeaters[item.name]\n\n lines.push(`[${filename}](${designerUrl}/file-download/${fileId})\\n`)\n } else {\n lines.push(\n getAnswer(item.field, item.state, translator, { format: 'email' })\n )\n }\n\n lines.push('---\\n')\n })\n\n const filename = escapeMarkdown('Download main form (CSV)')\n lines.push(`[${filename}](${designerUrl}/file-download/${files.main})\\n`)\n\n appendPaymentSection(paymentItems, lines)\n\n return lines.join('\\n')\n}\n\n/**\n * Check if an item is a PaymentField\n */\nfunction isPaymentItem(item: DetailItem): boolean {\n if ('subItems' in item) {\n return false\n }\n return item.field instanceof PaymentField\n}\n\n/**\n * Appends the payment details section to the email lines if payment exists\n */\nfunction appendPaymentSection(paymentItems: DetailItem[], lines: string[]) {\n if (paymentItems.length === 0) {\n return\n }\n\n const paymentItem = paymentItems[0] as DetailItemField\n const paymentField = paymentItem.field as PaymentField\n const paymentState = paymentField.getPaymentStateFromState(paymentItem.state)\n\n if (!paymentState) {\n return\n }\n\n const formattedAmount = formatCurrency(paymentState.amount)\n const dateOfPayment = paymentState.preAuth?.createdAt\n ? formatPaymentDate(paymentState.preAuth.createdAt)\n : ''\n\n lines.push(\n '---\\n',\n `# Your payment of ${formattedAmount} was successful\\n`,\n '## Payment for\\n',\n `${escapeMarkdown(paymentState.description)}\\n`,\n '---\\n',\n '## Total amount\\n',\n `${formattedAmount}\\n`,\n '---\\n',\n '## Date of payment\\n',\n `${escapeMarkdown(dateOfPayment)}\\n`,\n '---\\n'\n )\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"v1.js","names":["addDays","format","dateFormat","config","EN_GB","getAnswer","escapeMarkdown","PaymentField","formatCurrency","formatPaymentDate","designerUrl","get","_context","items","model","submitResponse","formStatus","_formMetadata","translator","createTranslator","files","result","formName","name","now","Date","formattedNow","fileExpiryDate","formattedExpiryDate","lines","push","isPreview","state","regularItems","filter","item","isPaymentItem","paymentItems","forEach","label","filename","fileId","repeaters","field","main","appendPaymentSection","join","length","paymentItem","paymentField","paymentState","getPaymentStateFromState","formattedAmount","amount","dateOfPayment","preAuth","createdAt","description"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/human/v1.ts"],"sourcesContent":["import {\n type FormMetadata,\n type SubmitResponsePayload\n} from '@defra/forms-model'\nimport { addDays, format as dateFormat } from 'date-fns'\n\nimport { config } from '~/src/config/index.js'\nimport { EN_GB } from '~/src/server/constants.js'\nimport { getAnswer } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers/index.js'\nimport { PaymentField } from '~/src/server/plugins/engine/components/index.js'\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type DetailItem,\n type DetailItemField\n} from '~/src/server/plugins/engine/models/types.js'\nimport { type FormContext } from '~/src/server/plugins/engine/types.js'\nimport {\n formatCurrency,\n formatPaymentDate\n} from '~/src/server/plugins/payment/helper.js'\n\nconst designerUrl = config.get('designerUrl')\n\nexport function format(\n _context: FormContext,\n items: DetailItem[],\n model: FormModel,\n submitResponse: SubmitResponsePayload,\n formStatus: ReturnType<typeof checkFormStatus>,\n _formMetadata?: FormMetadata\n) {\n const translator = model.createTranslator()\n const { files } = submitResponse.result\n\n const formName = escapeMarkdown(model.name)\n\n /**\n * @todo Refactor this below but the code to\n * generate the question and answers works for now\n */\n const now = new Date()\n const formattedNow = `${dateFormat(now, 'h:mmaaa')} on ${dateFormat(now, 'd MMMM yyyy')}`\n\n const fileExpiryDate = addDays(now, 90)\n const formattedExpiryDate = `${dateFormat(fileExpiryDate, 'h:mmaaa')} on ${dateFormat(fileExpiryDate, 'eeee d MMMM yyyy')}`\n\n const lines: string[] = []\n\n lines.push(\n `^ For security reasons, the links in this email expire at ${escapeMarkdown(formattedExpiryDate)}\\n`\n )\n\n if (formStatus.isPreview) {\n lines.push(`This is a test of the ${formName} ${formStatus.state} form.\\n`)\n }\n\n lines.push(`${formName} form received at ${escapeMarkdown(formattedNow)}.\\n`)\n lines.push('---\\n')\n\n const regularItems = items.filter((item) => !isPaymentItem(item))\n const paymentItems = items.filter((item) => isPaymentItem(item))\n\n regularItems.forEach((item) => {\n const label = escapeMarkdown(item.label)\n\n lines.push(`## ${label}\\n`)\n\n if ('subItems' in item) {\n const filename = escapeMarkdown(`Download ${label} (CSV)`)\n const fileId = files.repeaters[item.name]\n\n lines.push(`[${filename}](${designerUrl}/file-download/${fileId})\\n`)\n } else {\n lines.push(\n getAnswer(item.field, item.state, translator, { format: 'email' })\n )\n }\n\n lines.push('---\\n')\n })\n\n const filename = escapeMarkdown('Download main form (CSV)')\n lines.push(`[${filename}](${designerUrl}/file-download/${files.main})\\n`)\n\n appendPaymentSection(paymentItems, lines)\n\n return lines.join('\\n')\n}\n\n/**\n * Check if an item is a PaymentField\n */\nfunction isPaymentItem(item: DetailItem): boolean {\n if ('subItems' in item) {\n return false\n }\n return item.field instanceof PaymentField\n}\n\n/**\n * Appends the payment details section to the email lines if payment exists\n */\nfunction appendPaymentSection(paymentItems: DetailItem[], lines: string[]) {\n if (paymentItems.length === 0) {\n return\n }\n\n const paymentItem = paymentItems[0] as DetailItemField\n const paymentField = paymentItem.field as PaymentField\n const paymentState = paymentField.getPaymentStateFromState(paymentItem.state)\n\n if (!paymentState) {\n return\n }\n\n const formattedAmount = formatCurrency(paymentState.amount)\n const dateOfPayment = paymentState.preAuth?.createdAt\n ? formatPaymentDate(paymentState.preAuth.createdAt, EN_GB)\n : ''\n\n lines.push(\n '---\\n',\n `# Your payment of ${formattedAmount} was successful\\n`,\n '## Payment for\\n',\n `${escapeMarkdown(paymentState.description)}\\n`,\n '---\\n',\n '## Total amount\\n',\n `${formattedAmount}\\n`,\n '---\\n',\n '## Date of payment\\n',\n `${escapeMarkdown(dateOfPayment)}\\n`,\n '---\\n'\n )\n}\n"],"mappings":"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","ignoreList":[]}
@@ -12,6 +12,7 @@ interface PaymentOutput {
12
12
  reference: string;
13
13
  amount: number;
14
14
  description: string;
15
+ descriptionInEng?: string;
15
16
  createdAt: string;
16
17
  }
17
18
  export declare function format(context: FormContext, items: DetailItem[], model: FormModel, _submitResponse: SubmitResponsePayload, _formStatus: ReturnType<typeof checkFormStatus>): string;
@@ -47,6 +48,7 @@ export declare function format(context: FormContext, items: DetailItem[], model:
47
48
  * reference: 'REF-123',
48
49
  * amount: 10.00,
49
50
  * description: 'Application fee',
51
+ * descriptionInEng: 'Applicaiton fee',
50
52
  * createdAt: '2025-01-23T10:30:00.000Z'
51
53
  * }
52
54
  * }
@@ -56,6 +56,7 @@ export function format(context, items, model, _submitResponse, _formStatus) {
56
56
  * reference: 'REF-123',
57
57
  * amount: 10.00,
58
58
  * description: 'Application fee',
59
+ * descriptionInEng: 'Applicaiton fee',
59
60
  * createdAt: '2025-01-23T10:30:00.000Z'
60
61
  * }
61
62
  * }
@@ -149,6 +150,7 @@ function extractPayment(item) {
149
150
  reference: paymentState.reference,
150
151
  amount: paymentState.amount,
151
152
  description: paymentState.description,
153
+ descriptionInEng: item.field.def.options.description,
152
154
  createdAt: paymentState.preAuth?.createdAt ?? ''
153
155
  };
154
156
  }
@@ -1 +1 @@
1
- {"version":3,"file":"v2.js","names":["config","FileUploadField","PaymentField","designerUrl","get","format","context","items","model","_submitResponse","_formStatus","now","Date","categorisedData","categoriseData","meta","schemaVersion","timestamp","toISOString","definition","def","referenceNumber","data","body","JSON","stringify","output","main","repeaters","files","forEach","item","name","state","extractRepeaters","isFileUploadFieldItem","extractFileUploads","isPaymentFieldItem","payment","extractPayment","field","getFormValueFromState","subItems","inputRepeaterItem","outputRepeaterItem","repeaterComponent","push","fileUploadState","map","fileState","file","status","form","fileId","fileName","filename","userDownloadLink","paymentState","getPaymentStateFromState","undefined","paymentId","reference","amount","description","createdAt","preAuth"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/machine/v2.ts"],"sourcesContent":["import { type SubmitResponsePayload } from '@defra/forms-model'\n\nimport { config } from '~/src/config/index.js'\nimport {\n FileUploadField,\n PaymentField\n} from '~/src/server/plugins/engine/components/index.js'\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport {\n type FileUploadFieldDetailitem,\n type FormAdapterFile,\n type FormContext,\n type RichFormValue\n} from '~/src/server/plugins/engine/types.js'\n\n/**\n * Payment data for the machine output format\n * Defined locally to avoid circular dependency with types.ts\n */\ninterface PaymentOutput {\n paymentId: string\n reference: string\n amount: number\n description: string\n createdAt: string\n}\n\nconst designerUrl = config.get('designerUrl')\n\nexport function format(\n context: FormContext,\n items: DetailItem[],\n model: FormModel,\n _submitResponse: SubmitResponsePayload,\n _formStatus: ReturnType<typeof checkFormStatus>\n) {\n const now = new Date()\n\n const categorisedData = categoriseData(items)\n\n const meta: Record<string, unknown> = {\n schemaVersion: '2',\n timestamp: now.toISOString(),\n definition: model.def,\n referenceNumber: context.referenceNumber\n }\n\n const data = {\n meta,\n data: categorisedData\n }\n\n const body = JSON.stringify(data)\n\n return body\n}\n\n/**\n * Categories the form submission data into the \"main\" body and \"repeaters\".\n *\n * {\n * main: {\n * componentName: 'componentValue',\n * },\n * repeaters: {\n * repeaterName: [\n * {\n * textComponentName: 'componentValue'\n * },\n * {\n * richComponentName: { foo: 'bar', 'baz': true }\n * }\n * ]\n * },\n * files: {\n * fileComponentName: [\n * {\n * fileId: '123-456-789',\n * fileName: 'example.pdf',\n * userDownloadLink: 'https://forms-designer/file-download/123-456-789'\n * }\n * ]\n * },\n * payments: {\n * paymentComponentName: {\n * paymentId: 'abc123',\n * reference: 'REF-123',\n * amount: 10.00,\n * description: 'Application fee',\n * createdAt: '2025-01-23T10:30:00.000Z'\n * }\n * }\n * }\n */\nexport function categoriseData(items: DetailItem[]) {\n const output: {\n main: Record<string, RichFormValue>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<\n string,\n { fileId: string; fileName: string; userDownloadLink: string }[]\n >\n payment?: PaymentOutput\n } = { main: {}, repeaters: {}, files: {} }\n\n items.forEach((item) => {\n const { name, state } = item\n\n if ('subItems' in item) {\n output.repeaters[name] = extractRepeaters(item)\n } else if (isFileUploadFieldItem(item)) {\n output.files[name] = extractFileUploads(item)\n } else if (isPaymentFieldItem(item)) {\n const payment = extractPayment(item)\n if (payment) {\n output.payment = payment\n }\n } else {\n output.main[name] = item.field.getFormValueFromState(state)\n }\n })\n\n return output\n}\n\n/**\n * Returns the \"repeaters\" section of the response body\n * @param item - the repeater item\n * @returns the repeater item\n */\nfunction extractRepeaters(item: DetailItemRepeat) {\n const repeaters: Record<string, RichFormValue>[] = []\n\n item.subItems.forEach((inputRepeaterItem) => {\n const outputRepeaterItem: Record<string, RichFormValue> = {}\n\n inputRepeaterItem.forEach((repeaterComponent) => {\n const { field, state } = repeaterComponent\n\n outputRepeaterItem[repeaterComponent.name] =\n field.getFormValueFromState(state)\n })\n\n repeaters.push(outputRepeaterItem)\n })\n\n return repeaters\n}\n\n/**\n * Returns the \"files\" section of the response body\n * @param item - the file upload item in the form\n * @returns the file upload data\n */\nfunction extractFileUploads(\n item: FileUploadFieldDetailitem\n): FormAdapterFile[] {\n const fileUploadState = item.field.getFormValueFromState(item.state) ?? []\n\n return fileUploadState.map((fileState) => {\n const { file } = fileState.status.form\n return {\n fileId: file.fileId,\n fileName: file.filename,\n userDownloadLink: `${designerUrl}/file-download/${file.fileId}`\n }\n })\n}\n\nfunction isFileUploadFieldItem(\n item: DetailItemField\n): item is FileUploadFieldDetailitem {\n return item.field instanceof FileUploadField\n}\n\nfunction isPaymentFieldItem(item: DetailItemField): item is DetailItemField & {\n field: PaymentField\n} {\n return item.field instanceof PaymentField\n}\n\n/**\n * Returns the \"payments\" section of the response body\n * @param item - the payment item in the form\n * @returns the payment data\n */\nfunction extractPayment(\n item: DetailItemField & { field: PaymentField }\n): PaymentOutput | undefined {\n const paymentState = item.field.getPaymentStateFromState(item.state)\n\n if (!paymentState) {\n return undefined\n }\n\n return {\n paymentId: paymentState.paymentId,\n reference: paymentState.reference,\n amount: paymentState.amount,\n description: paymentState.description,\n createdAt: paymentState.preAuth?.createdAt ?? ''\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"v2.js","names":["config","FileUploadField","PaymentField","designerUrl","get","format","context","items","model","_submitResponse","_formStatus","now","Date","categorisedData","categoriseData","meta","schemaVersion","timestamp","toISOString","definition","def","referenceNumber","data","body","JSON","stringify","output","main","repeaters","files","forEach","item","name","state","extractRepeaters","isFileUploadFieldItem","extractFileUploads","isPaymentFieldItem","payment","extractPayment","field","getFormValueFromState","subItems","inputRepeaterItem","outputRepeaterItem","repeaterComponent","push","fileUploadState","map","fileState","file","status","form","fileId","fileName","filename","userDownloadLink","paymentState","getPaymentStateFromState","undefined","paymentId","reference","amount","description","descriptionInEng","options","createdAt","preAuth"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/machine/v2.ts"],"sourcesContent":["import { type SubmitResponsePayload } from '@defra/forms-model'\n\nimport { config } from '~/src/config/index.js'\nimport {\n FileUploadField,\n PaymentField\n} from '~/src/server/plugins/engine/components/index.js'\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport {\n type FileUploadFieldDetailitem,\n type FormAdapterFile,\n type FormContext,\n type RichFormValue\n} from '~/src/server/plugins/engine/types.js'\n\n/**\n * Payment data for the machine output format\n * Defined locally to avoid circular dependency with types.ts\n */\ninterface PaymentOutput {\n paymentId: string\n reference: string\n amount: number\n description: string\n descriptionInEng?: string\n createdAt: string\n}\n\nconst designerUrl = config.get('designerUrl')\n\nexport function format(\n context: FormContext,\n items: DetailItem[],\n model: FormModel,\n _submitResponse: SubmitResponsePayload,\n _formStatus: ReturnType<typeof checkFormStatus>\n) {\n const now = new Date()\n\n const categorisedData = categoriseData(items)\n\n const meta: Record<string, unknown> = {\n schemaVersion: '2',\n timestamp: now.toISOString(),\n definition: model.def,\n referenceNumber: context.referenceNumber\n }\n\n const data = {\n meta,\n data: categorisedData\n }\n\n const body = JSON.stringify(data)\n\n return body\n}\n\n/**\n * Categories the form submission data into the \"main\" body and \"repeaters\".\n *\n * {\n * main: {\n * componentName: 'componentValue',\n * },\n * repeaters: {\n * repeaterName: [\n * {\n * textComponentName: 'componentValue'\n * },\n * {\n * richComponentName: { foo: 'bar', 'baz': true }\n * }\n * ]\n * },\n * files: {\n * fileComponentName: [\n * {\n * fileId: '123-456-789',\n * fileName: 'example.pdf',\n * userDownloadLink: 'https://forms-designer/file-download/123-456-789'\n * }\n * ]\n * },\n * payments: {\n * paymentComponentName: {\n * paymentId: 'abc123',\n * reference: 'REF-123',\n * amount: 10.00,\n * description: 'Application fee',\n * descriptionInEng: 'Applicaiton fee',\n * createdAt: '2025-01-23T10:30:00.000Z'\n * }\n * }\n * }\n */\nexport function categoriseData(items: DetailItem[]) {\n const output: {\n main: Record<string, RichFormValue>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<\n string,\n { fileId: string; fileName: string; userDownloadLink: string }[]\n >\n payment?: PaymentOutput\n } = { main: {}, repeaters: {}, files: {} }\n\n items.forEach((item) => {\n const { name, state } = item\n\n if ('subItems' in item) {\n output.repeaters[name] = extractRepeaters(item)\n } else if (isFileUploadFieldItem(item)) {\n output.files[name] = extractFileUploads(item)\n } else if (isPaymentFieldItem(item)) {\n const payment = extractPayment(item)\n if (payment) {\n output.payment = payment\n }\n } else {\n output.main[name] = item.field.getFormValueFromState(state)\n }\n })\n\n return output\n}\n\n/**\n * Returns the \"repeaters\" section of the response body\n * @param item - the repeater item\n * @returns the repeater item\n */\nfunction extractRepeaters(item: DetailItemRepeat) {\n const repeaters: Record<string, RichFormValue>[] = []\n\n item.subItems.forEach((inputRepeaterItem) => {\n const outputRepeaterItem: Record<string, RichFormValue> = {}\n\n inputRepeaterItem.forEach((repeaterComponent) => {\n const { field, state } = repeaterComponent\n\n outputRepeaterItem[repeaterComponent.name] =\n field.getFormValueFromState(state)\n })\n\n repeaters.push(outputRepeaterItem)\n })\n\n return repeaters\n}\n\n/**\n * Returns the \"files\" section of the response body\n * @param item - the file upload item in the form\n * @returns the file upload data\n */\nfunction extractFileUploads(\n item: FileUploadFieldDetailitem\n): FormAdapterFile[] {\n const fileUploadState = item.field.getFormValueFromState(item.state) ?? []\n\n return fileUploadState.map((fileState) => {\n const { file } = fileState.status.form\n return {\n fileId: file.fileId,\n fileName: file.filename,\n userDownloadLink: `${designerUrl}/file-download/${file.fileId}`\n }\n })\n}\n\nfunction isFileUploadFieldItem(\n item: DetailItemField\n): item is FileUploadFieldDetailitem {\n return item.field instanceof FileUploadField\n}\n\nfunction isPaymentFieldItem(item: DetailItemField): item is DetailItemField & {\n field: PaymentField\n} {\n return item.field instanceof PaymentField\n}\n\n/**\n * Returns the \"payments\" section of the response body\n * @param item - the payment item in the form\n * @returns the payment data\n */\nfunction extractPayment(\n item: DetailItemField & { field: PaymentField }\n): PaymentOutput | undefined {\n const paymentState = item.field.getPaymentStateFromState(item.state)\n\n if (!paymentState) {\n return undefined\n }\n\n return {\n paymentId: paymentState.paymentId,\n reference: paymentState.reference,\n amount: paymentState.amount,\n description: paymentState.description,\n descriptionInEng: item.field.def.options.description,\n createdAt: paymentState.preAuth?.createdAt ?? ''\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -33,7 +33,7 @@ export declare class FileUploadPageController extends QuestionPageController {
33
33
  getUploadFromState(state: FormSubmissionState): UploadInitiateResponse | undefined;
34
34
  makeGetItemDeleteRouteHandler(): (request: FormRequest, context: FormContext, h: FormResponseToolkit) => import("@hapi/hapi").ResponseObject;
35
35
  makePostItemDeleteRouteHandler(): (request: FormRequestPayload, context: FormContext, h: FormResponseToolkit) => Promise<import("@hapi/hapi").ResponseObject>;
36
- getErrors(details?: ValidationErrorItem[]): FormSubmissionError[] | undefined;
36
+ getErrors(details?: ValidationErrorItem[], language?: string): FormSubmissionError[] | undefined;
37
37
  getViewModel(request: FormContextRequest, context: FormContext, translator: Translator): FeaturedFormPageViewModel;
38
38
  /**
39
39
  * Refreshes the CDP upload and files in the
@@ -64,6 +64,12 @@ export declare class FileUploadPageController extends QuestionPageController {
64
64
  * @param upload - the current upload initiation response
65
65
  */
66
66
  private processUploadedFiles;
67
+ /**
68
+ * Process the error parameters so any arrays get expanded (joined), ready to be passed into the error message
69
+ * @param errorParams - received from the file upload (may be a number, a string, or a string array)
70
+ * @param finalSeparator - 'or' in English, and 'neu' in Welsh
71
+ */
72
+ private expandErrorParams;
67
73
  /**
68
74
  * Checks the payload for a file getting removed
69
75
  * and removes it from the upload files if found
@@ -8,6 +8,7 @@ import { QuestionPageController } from "./QuestionPageController.js";
8
8
  import { getProxyUrlForLocalDevelopment } from "./helpers/index.js";
9
9
  import { getUploadStatus, initiateUpload } from "../services/uploadService.js";
10
10
  import { FileStatus, UploadStatus } from "../types.js";
11
+ import { joinWithOr } from "../../../utils/utils.js";
11
12
  const MAX_UPLOADS = 25;
12
13
  const CDP_UPLOAD_TIMEOUT_MS = 60000; // 1 minute
13
14
 
@@ -133,24 +134,26 @@ export class FileUploadPageController extends QuestionPageController {
133
134
  const {
134
135
  filename
135
136
  } = fileToRemove.status.form.file;
137
+ const translator = this.getTranslator(request);
136
138
  const {
137
139
  t
138
- } = this.getTranslator(request);
140
+ } = translator;
139
141
  return h.view(this.fileDeleteViewName, {
140
142
  ...viewModel,
141
143
  context,
142
144
  backLink: this.getBackLink(request, context, t),
143
- pageTitle: `Are you sure you want to remove this file?`,
145
+ pageTitle: t('pages.fileUpload.removeFileTitle'),
144
146
  itemTitle: filename,
145
147
  confirmation: {
146
- text: 'You cannot recover removed files.'
148
+ text: t('pages.fileUpload.removeFileBody')
147
149
  },
148
150
  buttonConfirm: {
149
- text: 'Remove file'
151
+ text: t('pages.fileUpload.removeFileButton')
150
152
  },
151
153
  buttonCancel: {
152
- text: 'Cancel'
153
- }
154
+ text: t('pages.fileUpload.cancel')
155
+ },
156
+ t
154
157
  });
155
158
  };
156
159
  }
@@ -174,7 +177,7 @@ export class FileUploadPageController extends QuestionPageController {
174
177
  return this.proceed(request, h);
175
178
  };
176
179
  }
177
- getErrors(details) {
180
+ getErrors(details, language) {
178
181
  const {
179
182
  fileUpload
180
183
  } = this;
@@ -186,7 +189,7 @@ export class FileUploadPageController extends QuestionPageController {
186
189
  if (!isUploadError || isUploadRootError) {
187
190
  // The error is for the root of the upload or another
188
191
  // field on the page so defer to the getError helper
189
- errors.push(getError(error));
192
+ errors.push(getError(language, error));
190
193
  } else {
191
194
  const {
192
195
  context,
@@ -219,6 +222,9 @@ export class FileUploadPageController extends QuestionPageController {
219
222
  const {
220
223
  state
221
224
  } = context;
225
+ const {
226
+ t
227
+ } = translator;
222
228
  const upload = this.getUploadFromState(state);
223
229
  const viewModel = super.getViewModel(request, context, translator);
224
230
  const {
@@ -229,6 +235,20 @@ export class FileUploadPageController extends QuestionPageController {
229
235
  const [formComponent] = components.filter(({
230
236
  model
231
237
  }) => model.id === fileUpload.name);
238
+ formComponent.model.upload = {
239
+ count: formComponent.model.upload?.count ?? 0,
240
+ summaryList: formComponent.model.upload?.summaryList ?? {
241
+ rows: []
242
+ },
243
+ uploadingLabel: t('components.fileUploadField.uploading')
244
+ };
245
+ formComponent.model = {
246
+ ...formComponent.model,
247
+ javascript: true,
248
+ chooseFilesButtonText: t('components.fileUploadField.chooseFile'),
249
+ dropInstructionText: t('components.fileUploadField.dropFile'),
250
+ noFileChosenText: t('components.fileUploadField.noFileChosen')
251
+ };
232
252
  const index = components.indexOf(formComponent);
233
253
  const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl);
234
254
  return {
@@ -239,7 +259,8 @@ export class FileUploadPageController extends QuestionPageController {
239
259
  // Split out components before/after
240
260
  componentsBefore: components.slice(0, index),
241
261
  components: components.slice(index),
242
- proxyUrl
262
+ proxyUrl,
263
+ t
243
264
  };
244
265
  }
245
266
 
@@ -349,6 +370,10 @@ export class FileUploadPageController extends QuestionPageController {
349
370
  const rawFile = validatedStatus.form.file;
350
371
  const uploadedFiles = Array.isArray(rawFile) ? rawFile : [rawFile];
351
372
  const allErrors = [];
373
+ const translator = this.getTranslator(request);
374
+ const {
375
+ t
376
+ } = translator;
352
377
  for (const file of uploadedFiles) {
353
378
  if (file.fileStatus === FileStatus.complete) {
354
379
  const perFileState = {
@@ -367,7 +392,8 @@ export class FileUploadPageController extends QuestionPageController {
367
392
  fileUpload
368
393
  } = this;
369
394
  const name = fileUpload.name;
370
- const text = file.errorMessage ?? 'Unknown error';
395
+ const errorParams = this.expandErrorParams(file.errorParams, t('common.or'));
396
+ const text = t(`pages.fileUpload.${file.errorCode ?? 'FILE_ERROR_UNKNOWN'}`, errorParams);
371
397
  allErrors.push({
372
398
  path: [name],
373
399
  href: `#${name}`,
@@ -394,6 +420,26 @@ export class FileUploadPageController extends QuestionPageController {
394
420
  }
395
421
  }
396
422
 
423
+ /**
424
+ * Process the error parameters so any arrays get expanded (joined), ready to be passed into the error message
425
+ * @param errorParams - received from the file upload (may be a number, a string, or a string array)
426
+ * @param finalSeparator - 'or' in English, and 'neu' in Welsh
427
+ */
428
+ expandErrorParams(errorParams, finalSeparator) {
429
+ if (!errorParams) {
430
+ return {};
431
+ }
432
+ const expanded = {};
433
+ for (const [key, value] of Object.entries(errorParams)) {
434
+ if (Array.isArray(value)) {
435
+ expanded[key] = joinWithOr(value, finalSeparator);
436
+ } else {
437
+ expanded[key] = value;
438
+ }
439
+ }
440
+ return expanded;
441
+ }
442
+
397
443
  /**
398
444
  * Checks the payload for a file getting removed
399
445
  * and removes it from the upload files if found
@@ -1 +1 @@
1
- {"version":3,"file":"FileUploadPageController.js","names":["ComponentType","Boom","wait","StatusCodes","FileUploadField","tempItemSchema","getCacheService","getError","getExponentialBackoffDelay","QuestionPageController","getProxyUrlForLocalDevelopment","getUploadStatus","initiateUpload","FileStatus","UploadStatus","MAX_UPLOADS","CDP_UPLOAD_TIMEOUT_MS","prepareStatus","status","file","form","isPending","fileStatus","pending","errorMessage","prepareFileState","fileState","FileUploadPageController","fileUpload","fileDeleteViewName","constructor","model","pageDef","collection","fileUploads","fields","filter","field","type","at","length","badImplementation","path","indexOf","name","viewName","getStateKeys","component","pagePath","page","getFormDataFromState","request","state","payload","files","getFilesFromState","undefined","getState","refreshUpload","uploadState","upload","getUploadFromState","makeGetItemDeleteRouteHandler","context","h","viewModel","params","fileToRemove","find","fileId","itemId","notFound","filename","t","getTranslator","view","backLink","getBackLink","pageTitle","itemTitle","confirmation","text","buttonConfirm","buttonCancel","makePostItemDeleteRouteHandler","confirm","getFormParams","checkRemovedFiles","proceed","getErrors","details","errors","forEach","error","isUploadError","isUploadRootError","push","value","href","getViewModel","translator","components","formComponent","id","index","proxyUrl","uploadUrl","formAction","uploadId","componentsBefore","slice","checkUploadStatus","depth","initiateAndStoreNewUpload","statusResponse","err","isBoom","output","statusCode","NOT_FOUND","valueOf","badRequest","uploadStatus","initiated","Error","logger","gatewayTimeout","toFixed","delay","info","validationResult","validate","stripUnknown","processUploadedFiles","validatedItem","validatedStatus","rawFile","uploadedFiles","Array","isArray","allErrors","complete","perFileState","unshift","cacheService","server","setFlash","some","f","mergeState","filesUpdated","options","schema","getFormMetadata","services","formsService","max","Math","min","formMetadata","slug","notificationEmail","newUpload","accept"],"sources":["../../../../../src/server/plugins/engine/pageControllers/FileUploadPageController.ts"],"sourcesContent":["import { ComponentType, type PageFileUpload } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { wait } from '@hapi/hoek'\nimport { StatusCodes } from 'http-status-codes'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n FileUploadField,\n tempItemSchema\n} from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport { type FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n getCacheService,\n getError,\n getExponentialBackoffDelay\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { getProxyUrlForLocalDevelopment } from '~/src/server/plugins/engine/pageControllers/helpers/index.js'\nimport {\n getUploadStatus,\n initiateUpload\n} from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n FileStatus,\n UploadStatus,\n type AnyFormRequest,\n type FeaturedFormPageViewModel,\n type FileState,\n type FileUpload,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError,\n type FormSubmissionState,\n type ItemDeletePageViewModel,\n type UploadInitiateResponse,\n type UploadStatusFileResponse\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nconst MAX_UPLOADS = 25\nconst CDP_UPLOAD_TIMEOUT_MS = 60000 // 1 minute\n\nexport function prepareStatus(status: UploadStatusFileResponse) {\n const file = status.form.file\n const isPending = file.fileStatus === FileStatus.pending\n\n if (!file.errorMessage && isPending) {\n file.errorMessage = 'The selected file has not fully uploaded'\n }\n\n return status\n}\n\nfunction prepareFileState(fileState: FileState) {\n prepareStatus(fileState.status)\n\n return fileState\n}\n\nexport class FileUploadPageController extends QuestionPageController {\n declare pageDef: PageFileUpload\n\n fileUpload: FileUploadField\n fileDeleteViewName = 'item-delete'\n\n constructor(model: FormModel, pageDef: PageFileUpload) {\n super(model, pageDef)\n\n const { collection } = this\n\n // Get the file upload fields from the collection\n const fileUploads = collection.fields.filter(\n (field): field is FileUploadField =>\n field.type === ComponentType.FileUploadField\n )\n\n const fileUpload = fileUploads.at(0)\n\n // Assert we have exactly 1 file upload component\n if (!fileUpload || fileUploads.length > 1) {\n throw Boom.badImplementation(\n `Expected 1 FileUploadFieldComponent in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assert the file upload component is the first form component\n if (collection.fields.indexOf(fileUpload) !== 0) {\n throw Boom.badImplementation(\n `Expected '${fileUpload.name}' to be the first form component in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assign the file upload component to the controller\n this.fileUpload = fileUpload\n this.viewName = 'file-upload'\n }\n\n /**\n * Get supplementary state keys for clearing file upload state.\n * Returns the nested upload path for FileUploadField components only.\n * @param component - The component to get supplementary state keys for\n * @returns Array containing the nested upload path, e.g., [\"upload['/page-path']\"]\n * or ['upload'] if no page path is available. Returns empty array for non-FileUploadField components.\n */\n getStateKeys(component: FormComponent): string[] {\n // Only return upload keys for FileUploadField components\n if (!(component instanceof FileUploadField)) {\n return []\n }\n\n const pagePath = component.page?.path\n return pagePath ? [`upload['${pagePath}']`] : ['upload']\n }\n\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ) {\n const { fileUpload } = this\n\n const payload = super.getFormDataFromState(request, state)\n const files = this.getFilesFromState(state)\n\n // Append the files to the payload\n payload[fileUpload.name] = files.length ? files : undefined\n\n return payload\n }\n\n async getState(request: AnyFormRequest) {\n const { fileUpload } = this\n\n // Get the actual state\n const state = await super.getState(request)\n const files = this.getFilesFromState(state)\n\n // Overwrite the files with those in the upload state\n state[fileUpload.name] = files\n\n return this.refreshUpload(request, state)\n }\n\n /**\n * Get the uploaded files from state.\n */\n getFilesFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.files ?? []\n }\n\n /**\n * Get the initiated upload from state.\n */\n getUploadFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.upload\n }\n\n makeGetItemDeleteRouteHandler() {\n return (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewModel } = this\n const { params } = request\n const { state } = context\n\n const files = this.getFilesFromState(state)\n\n const fileToRemove = files.find(\n ({ status }) => status.form.file.fileId === params.itemId\n )\n\n if (!fileToRemove) {\n throw Boom.notFound('File to delete not found')\n }\n\n const { filename } = fileToRemove.status.form.file\n\n const { t } = this.getTranslator(request)\n\n return h.view(this.fileDeleteViewName, {\n ...viewModel,\n context,\n backLink: this.getBackLink(request, context, t),\n pageTitle: `Are you sure you want to remove this file?`,\n itemTitle: filename,\n confirmation: { text: 'You cannot recover removed files.' },\n buttonConfirm: { text: 'Remove file' },\n buttonCancel: { text: 'Cancel' }\n } satisfies ItemDeletePageViewModel)\n }\n }\n\n makePostItemDeleteRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { path } = this\n const { state } = context\n\n const { confirm } = this.getFormParams(request)\n\n // Check for any removed files in the POST payload\n if (confirm) {\n await this.checkRemovedFiles(request, state)\n return this.proceed(request, h, path)\n }\n\n return this.proceed(request, h)\n }\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n const { fileUpload } = this\n\n if (details) {\n const errors: FormSubmissionError[] = []\n\n details.forEach((error) => {\n const isUploadError = error.path[0] === fileUpload.name\n const isUploadRootError = isUploadError && error.path.length === 1\n\n if (!isUploadError || isUploadRootError) {\n // The error is for the root of the upload or another\n // field on the page so defer to the getError helper\n errors.push(getError(error))\n } else {\n const { context, path, type } = error\n\n if (type === 'object.unknown' && path.at(-1) === 'errorMessage') {\n const value = context?.value as string | undefined\n\n if (value) {\n const name = fileUpload.name\n const text = typeof value === 'string' ? value : 'Unknown error'\n const href = `#${name}`\n\n errors.push({ path, href, name, text })\n }\n }\n }\n })\n\n return errors\n }\n }\n\n getViewModel(\n request: FormContextRequest,\n context: FormContext,\n translator: Translator\n ): FeaturedFormPageViewModel {\n const { fileUpload } = this\n const { state } = context\n\n const upload = this.getUploadFromState(state)\n\n const viewModel = super.getViewModel(request, context, translator)\n const { components } = viewModel\n\n // Featured form component\n const [formComponent] = components.filter(\n ({ model }) => model.id === fileUpload.name\n )\n\n const index = components.indexOf(formComponent)\n\n const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl)\n\n return {\n ...viewModel,\n formAction: upload?.uploadUrl,\n uploadId: upload?.uploadId,\n formComponent,\n\n // Split out components before/after\n componentsBefore: components.slice(0, index),\n components: components.slice(index),\n proxyUrl\n }\n }\n\n /**\n * Refreshes the CDP upload and files in the\n * state and checks for any removed files.\n *\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise we initiate a new one.\n * @param request - the hapi request\n * @param state - the form state\n */\n private async refreshUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n state = await this.checkUploadStatus(request, state)\n\n return state\n }\n\n /**\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise a new one is initiated.\n * @param request - the hapi request\n * @param state - the form state\n * @param depth - the number of retries so far\n */\n private async checkUploadStatus(\n request: AnyFormRequest,\n state: FormSubmissionState,\n depth = 1\n ): Promise<FormSubmissionState> {\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n // If no upload exists, initiate a new one.\n if (!upload?.uploadId) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const uploadId = upload.uploadId\n\n let statusResponse\n\n try {\n statusResponse = await getUploadStatus(uploadId)\n } catch (err) {\n // if the user loads a file upload page and queries the cached upload, after the upload has\n // expired in CDP, we will get a 404 from the getUploadStatus endpoint.\n // In this case we want to initiate a new upload and return that state, so the form\n // doesn't blow up for the end user.\n if (\n Boom.isBoom(err) &&\n err.output.statusCode === StatusCodes.NOT_FOUND.valueOf()\n ) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n throw err\n }\n\n if (!statusResponse) {\n throw Boom.badRequest(\n `Unexpected empty response from getUploadStatus for ${uploadId}`\n )\n }\n\n // Re-use the upload if it is still in the \"initiated\" state.\n if (statusResponse.uploadStatus === UploadStatus.initiated) {\n return state\n }\n\n if (statusResponse.uploadStatus === UploadStatus.pending) {\n // Using exponential backoff delays:\n // Depth 1: 2000ms, Depth 2: 4000ms, Depth 3: 8000ms, Depth 4: 16000ms, Depth 5+: 30000ms (capped)\n // A depth of 5 (or more) implies cumulative delays roughly reaching 55 seconds.\n if (depth >= 5) {\n const err = new Error(\n `Exceeded cumulative retry delay for ${uploadId} (depth: ${depth}). Re-initiating a new upload.`\n )\n request.logger.error(\n err,\n `[uploadTimeout] Exceeded cumulative retry delay for uploadId: ${uploadId} at depth: ${depth} - re-initiating new upload`\n )\n await this.initiateAndStoreNewUpload(request, state)\n throw Boom.gatewayTimeout(\n `Timed out waiting for ${uploadId} after cumulative retries exceeding ${((CDP_UPLOAD_TIMEOUT_MS - 5000) / 1000).toFixed(0)} seconds`\n )\n }\n const delay = getExponentialBackoffDelay(depth)\n request.logger.info(\n `[uploadRetry] Waiting ${delay / 1000} seconds for uploadId: ${uploadId} to complete (retry depth: ${depth})`\n )\n await wait(delay)\n return this.checkUploadStatus(request, state, depth + 1)\n }\n\n // Only add to files state if the file validates.\n // This secures against html tampering of the file input\n // (e.g. changing it to a simple text field or similar).\n const validationResult = tempItemSchema.validate(\n { uploadId, status: statusResponse },\n { stripUnknown: true }\n )\n const error = validationResult.error\n\n if (error) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n // CDP returns form.file as a single object for one file,\n // or an array for multiple files. The Joi schema normalises\n // both to an array via .single().\n await this.processUploadedFiles(\n request,\n state,\n validationResult.value,\n files,\n upload\n )\n\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n /**\n * Processes the uploaded files from a CDP status response.\n * Complete files are added to state, rejected/pending files\n * have their error messages flashed.\n * @param request - the hapi request\n * @param state - the form state\n * @param validatedItem - the Joi-validated upload item\n * @param files - the current files array from state\n * @param upload - the current upload initiation response\n */\n private async processUploadedFiles(\n request: AnyFormRequest,\n state: FormSubmissionState,\n validatedItem: FileState,\n files: FileState[],\n upload: UploadInitiateResponse | undefined\n ) {\n const { uploadId } = validatedItem\n const validatedStatus = validatedItem.status\n const rawFile = validatedStatus.form.file as unknown as\n | FileUpload\n | FileUpload[]\n const uploadedFiles = Array.isArray(rawFile) ? rawFile : [rawFile]\n\n const allErrors: FormSubmissionError[] = []\n\n for (const file of uploadedFiles) {\n if (file.fileStatus === FileStatus.complete) {\n const perFileState: FileState = {\n uploadId,\n status: {\n ...validatedStatus,\n form: { file }\n } as FileState['status']\n }\n files.unshift(prepareFileState(perFileState))\n } else {\n // Collect the error for rejected/pending files.\n const { fileUpload } = this\n const name = fileUpload.name\n const text = file.errorMessage ?? 'Unknown error'\n allErrors.push({ path: [name], href: `#${name}`, name, text })\n }\n }\n\n if (allErrors.length) {\n const cacheService = getCacheService(request.server)\n cacheService.setFlash(request, { errors: allErrors })\n }\n\n if (uploadedFiles.some((f) => f.fileStatus === FileStatus.complete)) {\n await this.mergeState(request, state, {\n upload: { [this.path]: { files, upload } }\n })\n }\n }\n\n /**\n * Checks the payload for a file getting removed\n * and removes it from the upload files if found\n * @param request - the hapi request\n * @param state - the form state\n * @returns updated state if any files have been removed\n */\n private async checkRemovedFiles(\n request: FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { path } = this\n const { params } = request\n\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n const filesUpdated = files.filter(\n ({ status }) => status.form.file.fileId !== params.itemId\n )\n\n if (filesUpdated.length === files.length) {\n return\n }\n\n await this.mergeState(request, state, {\n upload: { [path]: { files: filesUpdated, upload } }\n })\n }\n\n /**\n * Initiates a CDP file upload and stores in the upload state\n * @param request - the hapi request\n * @param state - the form state\n */\n private async initiateAndStoreNewUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n const { fileUpload, href, path } = this\n const { options, schema } = fileUpload\n const { getFormMetadata } = this.model.services.formsService\n\n const files = this.getFilesFromState(state)\n\n // Reset the upload in state\n let upload: UploadInitiateResponse | undefined\n\n // Don't initiate anymore after minimum of `schema.max` or MAX_UPLOADS\n const max = Math.min(schema.max ?? MAX_UPLOADS, MAX_UPLOADS)\n\n if (files.length < max) {\n const formMetadata = await getFormMetadata(request.params.slug)\n const notificationEmail =\n formMetadata.notificationEmail ?? 'defraforms@defra.gov.uk'\n\n const newUpload = await initiateUpload(\n href,\n notificationEmail,\n options.accept\n )\n\n if (newUpload === undefined) {\n throw Boom.badRequest('Unexpected empty response from initiateUpload')\n }\n\n upload = newUpload\n }\n\n return this.mergeState(request, state, {\n upload: { [path]: { files, upload } }\n })\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"FileUploadPageController.js","names":["ComponentType","Boom","wait","StatusCodes","FileUploadField","tempItemSchema","getCacheService","getError","getExponentialBackoffDelay","QuestionPageController","getProxyUrlForLocalDevelopment","getUploadStatus","initiateUpload","FileStatus","UploadStatus","joinWithOr","MAX_UPLOADS","CDP_UPLOAD_TIMEOUT_MS","prepareStatus","status","file","form","isPending","fileStatus","pending","errorMessage","prepareFileState","fileState","FileUploadPageController","fileUpload","fileDeleteViewName","constructor","model","pageDef","collection","fileUploads","fields","filter","field","type","at","length","badImplementation","path","indexOf","name","viewName","getStateKeys","component","pagePath","page","getFormDataFromState","request","state","payload","files","getFilesFromState","undefined","getState","refreshUpload","uploadState","upload","getUploadFromState","makeGetItemDeleteRouteHandler","context","h","viewModel","params","fileToRemove","find","fileId","itemId","notFound","filename","translator","getTranslator","t","view","backLink","getBackLink","pageTitle","itemTitle","confirmation","text","buttonConfirm","buttonCancel","makePostItemDeleteRouteHandler","confirm","getFormParams","checkRemovedFiles","proceed","getErrors","details","language","errors","forEach","error","isUploadError","isUploadRootError","push","value","href","getViewModel","components","formComponent","id","count","summaryList","rows","uploadingLabel","javascript","chooseFilesButtonText","dropInstructionText","noFileChosenText","index","proxyUrl","uploadUrl","formAction","uploadId","componentsBefore","slice","checkUploadStatus","depth","initiateAndStoreNewUpload","statusResponse","err","isBoom","output","statusCode","NOT_FOUND","valueOf","badRequest","uploadStatus","initiated","Error","logger","gatewayTimeout","toFixed","delay","info","validationResult","validate","stripUnknown","processUploadedFiles","validatedItem","validatedStatus","rawFile","uploadedFiles","Array","isArray","allErrors","complete","perFileState","unshift","errorParams","expandErrorParams","errorCode","cacheService","server","setFlash","some","f","mergeState","finalSeparator","expanded","key","Object","entries","filesUpdated","options","schema","getFormMetadata","services","formsService","max","Math","min","formMetadata","slug","notificationEmail","newUpload","accept"],"sources":["../../../../../src/server/plugins/engine/pageControllers/FileUploadPageController.ts"],"sourcesContent":["import { ComponentType, type PageFileUpload } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { wait } from '@hapi/hoek'\nimport { StatusCodes } from 'http-status-codes'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n FileUploadField,\n tempItemSchema\n} from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport { type FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n getCacheService,\n getError,\n getExponentialBackoffDelay\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { getProxyUrlForLocalDevelopment } from '~/src/server/plugins/engine/pageControllers/helpers/index.js'\nimport {\n getUploadStatus,\n initiateUpload\n} from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n FileStatus,\n UploadStatus,\n type AnyFormRequest,\n type FeaturedFormPageViewModel,\n type FileState,\n type FileUpload,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError,\n type FormSubmissionState,\n type ItemDeletePageViewModel,\n type UploadInitiateResponse,\n type UploadStatusFileResponse\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\nimport { joinWithOr } from '~/src/server/utils/utils.js'\n\nconst MAX_UPLOADS = 25\nconst CDP_UPLOAD_TIMEOUT_MS = 60000 // 1 minute\n\nexport function prepareStatus(status: UploadStatusFileResponse) {\n const file = status.form.file\n const isPending = file.fileStatus === FileStatus.pending\n\n if (!file.errorMessage && isPending) {\n file.errorMessage = 'The selected file has not fully uploaded'\n }\n\n return status\n}\n\nfunction prepareFileState(fileState: FileState) {\n prepareStatus(fileState.status)\n\n return fileState\n}\n\nexport class FileUploadPageController extends QuestionPageController {\n declare pageDef: PageFileUpload\n\n fileUpload: FileUploadField\n fileDeleteViewName = 'item-delete'\n\n constructor(model: FormModel, pageDef: PageFileUpload) {\n super(model, pageDef)\n\n const { collection } = this\n\n // Get the file upload fields from the collection\n const fileUploads = collection.fields.filter(\n (field): field is FileUploadField =>\n field.type === ComponentType.FileUploadField\n )\n\n const fileUpload = fileUploads.at(0)\n\n // Assert we have exactly 1 file upload component\n if (!fileUpload || fileUploads.length > 1) {\n throw Boom.badImplementation(\n `Expected 1 FileUploadFieldComponent in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assert the file upload component is the first form component\n if (collection.fields.indexOf(fileUpload) !== 0) {\n throw Boom.badImplementation(\n `Expected '${fileUpload.name}' to be the first form component in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assign the file upload component to the controller\n this.fileUpload = fileUpload\n this.viewName = 'file-upload'\n }\n\n /**\n * Get supplementary state keys for clearing file upload state.\n * Returns the nested upload path for FileUploadField components only.\n * @param component - The component to get supplementary state keys for\n * @returns Array containing the nested upload path, e.g., [\"upload['/page-path']\"]\n * or ['upload'] if no page path is available. Returns empty array for non-FileUploadField components.\n */\n getStateKeys(component: FormComponent): string[] {\n // Only return upload keys for FileUploadField components\n if (!(component instanceof FileUploadField)) {\n return []\n }\n\n const pagePath = component.page?.path\n return pagePath ? [`upload['${pagePath}']`] : ['upload']\n }\n\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ) {\n const { fileUpload } = this\n\n const payload = super.getFormDataFromState(request, state)\n const files = this.getFilesFromState(state)\n\n // Append the files to the payload\n payload[fileUpload.name] = files.length ? files : undefined\n\n return payload\n }\n\n async getState(request: AnyFormRequest) {\n const { fileUpload } = this\n\n // Get the actual state\n const state = await super.getState(request)\n const files = this.getFilesFromState(state)\n\n // Overwrite the files with those in the upload state\n state[fileUpload.name] = files\n\n return this.refreshUpload(request, state)\n }\n\n /**\n * Get the uploaded files from state.\n */\n getFilesFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.files ?? []\n }\n\n /**\n * Get the initiated upload from state.\n */\n getUploadFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.upload\n }\n\n makeGetItemDeleteRouteHandler() {\n return (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewModel } = this\n const { params } = request\n const { state } = context\n\n const files = this.getFilesFromState(state)\n\n const fileToRemove = files.find(\n ({ status }) => status.form.file.fileId === params.itemId\n )\n\n if (!fileToRemove) {\n throw Boom.notFound('File to delete not found')\n }\n\n const { filename } = fileToRemove.status.form.file\n\n const translator = this.getTranslator(request)\n const { t } = translator\n\n return h.view(this.fileDeleteViewName, {\n ...viewModel,\n context,\n backLink: this.getBackLink(request, context, t),\n pageTitle: t('pages.fileUpload.removeFileTitle'),\n itemTitle: filename,\n confirmation: { text: t('pages.fileUpload.removeFileBody') },\n buttonConfirm: { text: t('pages.fileUpload.removeFileButton') },\n buttonCancel: { text: t('pages.fileUpload.cancel') },\n t\n } satisfies ItemDeletePageViewModel)\n }\n }\n\n makePostItemDeleteRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { path } = this\n const { state } = context\n\n const { confirm } = this.getFormParams(request)\n\n // Check for any removed files in the POST payload\n if (confirm) {\n await this.checkRemovedFiles(request, state)\n return this.proceed(request, h, path)\n }\n\n return this.proceed(request, h)\n }\n }\n\n getErrors(details?: ValidationErrorItem[], language?: string) {\n const { fileUpload } = this\n\n if (details) {\n const errors: FormSubmissionError[] = []\n\n details.forEach((error) => {\n const isUploadError = error.path[0] === fileUpload.name\n const isUploadRootError = isUploadError && error.path.length === 1\n\n if (!isUploadError || isUploadRootError) {\n // The error is for the root of the upload or another\n // field on the page so defer to the getError helper\n errors.push(getError(language, error))\n } else {\n const { context, path, type } = error\n\n if (type === 'object.unknown' && path.at(-1) === 'errorMessage') {\n const value = context?.value as string | undefined\n\n if (value) {\n const name = fileUpload.name\n const text = typeof value === 'string' ? value : 'Unknown error'\n const href = `#${name}`\n\n errors.push({ path, href, name, text })\n }\n }\n }\n })\n\n return errors\n }\n }\n\n getViewModel(\n request: FormContextRequest,\n context: FormContext,\n translator: Translator\n ): FeaturedFormPageViewModel {\n const { fileUpload } = this\n const { state } = context\n const { t } = translator\n\n const upload = this.getUploadFromState(state)\n\n const viewModel = super.getViewModel(request, context, translator)\n const { components } = viewModel\n\n // Featured form component\n const [formComponent] = components.filter(\n ({ model }) => model.id === fileUpload.name\n )\n\n formComponent.model.upload = {\n count: formComponent.model.upload?.count ?? 0,\n summaryList: formComponent.model.upload?.summaryList ?? { rows: [] },\n uploadingLabel: t('components.fileUploadField.uploading')\n }\n formComponent.model = {\n ...formComponent.model,\n javascript: true,\n chooseFilesButtonText: t('components.fileUploadField.chooseFile'),\n dropInstructionText: t('components.fileUploadField.dropFile'),\n noFileChosenText: t('components.fileUploadField.noFileChosen')\n }\n\n const index = components.indexOf(formComponent)\n\n const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl)\n\n return {\n ...viewModel,\n formAction: upload?.uploadUrl,\n uploadId: upload?.uploadId,\n formComponent,\n\n // Split out components before/after\n componentsBefore: components.slice(0, index),\n components: components.slice(index),\n proxyUrl,\n t\n }\n }\n\n /**\n * Refreshes the CDP upload and files in the\n * state and checks for any removed files.\n *\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise we initiate a new one.\n * @param request - the hapi request\n * @param state - the form state\n */\n private async refreshUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n state = await this.checkUploadStatus(request, state)\n\n return state\n }\n\n /**\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise a new one is initiated.\n * @param request - the hapi request\n * @param state - the form state\n * @param depth - the number of retries so far\n */\n private async checkUploadStatus(\n request: AnyFormRequest,\n state: FormSubmissionState,\n depth = 1\n ): Promise<FormSubmissionState> {\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n // If no upload exists, initiate a new one.\n if (!upload?.uploadId) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const uploadId = upload.uploadId\n\n let statusResponse\n\n try {\n statusResponse = await getUploadStatus(uploadId)\n } catch (err) {\n // if the user loads a file upload page and queries the cached upload, after the upload has\n // expired in CDP, we will get a 404 from the getUploadStatus endpoint.\n // In this case we want to initiate a new upload and return that state, so the form\n // doesn't blow up for the end user.\n if (\n Boom.isBoom(err) &&\n err.output.statusCode === StatusCodes.NOT_FOUND.valueOf()\n ) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n throw err\n }\n\n if (!statusResponse) {\n throw Boom.badRequest(\n `Unexpected empty response from getUploadStatus for ${uploadId}`\n )\n }\n\n // Re-use the upload if it is still in the \"initiated\" state.\n if (statusResponse.uploadStatus === UploadStatus.initiated) {\n return state\n }\n\n if (statusResponse.uploadStatus === UploadStatus.pending) {\n // Using exponential backoff delays:\n // Depth 1: 2000ms, Depth 2: 4000ms, Depth 3: 8000ms, Depth 4: 16000ms, Depth 5+: 30000ms (capped)\n // A depth of 5 (or more) implies cumulative delays roughly reaching 55 seconds.\n if (depth >= 5) {\n const err = new Error(\n `Exceeded cumulative retry delay for ${uploadId} (depth: ${depth}). Re-initiating a new upload.`\n )\n request.logger.error(\n err,\n `[uploadTimeout] Exceeded cumulative retry delay for uploadId: ${uploadId} at depth: ${depth} - re-initiating new upload`\n )\n await this.initiateAndStoreNewUpload(request, state)\n throw Boom.gatewayTimeout(\n `Timed out waiting for ${uploadId} after cumulative retries exceeding ${((CDP_UPLOAD_TIMEOUT_MS - 5000) / 1000).toFixed(0)} seconds`\n )\n }\n const delay = getExponentialBackoffDelay(depth)\n request.logger.info(\n `[uploadRetry] Waiting ${delay / 1000} seconds for uploadId: ${uploadId} to complete (retry depth: ${depth})`\n )\n await wait(delay)\n return this.checkUploadStatus(request, state, depth + 1)\n }\n\n // Only add to files state if the file validates.\n // This secures against html tampering of the file input\n // (e.g. changing it to a simple text field or similar).\n const validationResult = tempItemSchema.validate(\n { uploadId, status: statusResponse },\n { stripUnknown: true }\n )\n const error = validationResult.error\n\n if (error) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n // CDP returns form.file as a single object for one file,\n // or an array for multiple files. The Joi schema normalises\n // both to an array via .single().\n await this.processUploadedFiles(\n request,\n state,\n validationResult.value,\n files,\n upload\n )\n\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n /**\n * Processes the uploaded files from a CDP status response.\n * Complete files are added to state, rejected/pending files\n * have their error messages flashed.\n * @param request - the hapi request\n * @param state - the form state\n * @param validatedItem - the Joi-validated upload item\n * @param files - the current files array from state\n * @param upload - the current upload initiation response\n */\n private async processUploadedFiles(\n request: AnyFormRequest,\n state: FormSubmissionState,\n validatedItem: FileState,\n files: FileState[],\n upload: UploadInitiateResponse | undefined\n ) {\n const { uploadId } = validatedItem\n const validatedStatus = validatedItem.status\n const rawFile = validatedStatus.form.file as unknown as\n | FileUpload\n | FileUpload[]\n const uploadedFiles = Array.isArray(rawFile) ? rawFile : [rawFile]\n\n const allErrors: FormSubmissionError[] = []\n\n const translator = this.getTranslator(request)\n const { t } = translator\n\n for (const file of uploadedFiles) {\n if (file.fileStatus === FileStatus.complete) {\n const perFileState: FileState = {\n uploadId,\n status: {\n ...validatedStatus,\n form: { file }\n } as FileState['status']\n }\n files.unshift(prepareFileState(perFileState))\n } else {\n // Collect the error for rejected/pending files.\n const { fileUpload } = this\n const name = fileUpload.name\n const errorParams = this.expandErrorParams(\n file.errorParams,\n t('common.or')\n )\n const text = t(\n `pages.fileUpload.${file.errorCode ?? 'FILE_ERROR_UNKNOWN'}`,\n errorParams\n )\n allErrors.push({ path: [name], href: `#${name}`, name, text })\n }\n }\n\n if (allErrors.length) {\n const cacheService = getCacheService(request.server)\n cacheService.setFlash(request, { errors: allErrors })\n }\n\n if (uploadedFiles.some((f) => f.fileStatus === FileStatus.complete)) {\n await this.mergeState(request, state, {\n upload: { [this.path]: { files, upload } }\n })\n }\n }\n\n /**\n * Process the error parameters so any arrays get expanded (joined), ready to be passed into the error message\n * @param errorParams - received from the file upload (may be a number, a string, or a string array)\n * @param finalSeparator - 'or' in English, and 'neu' in Welsh\n */\n private expandErrorParams(\n errorParams: Record<string, number | string | string[]> | undefined,\n finalSeparator: string\n ) {\n if (!errorParams) {\n return {}\n }\n const expanded = {} as Record<string, number | string>\n for (const [key, value] of Object.entries(errorParams)) {\n if (Array.isArray(value)) {\n expanded[key] = joinWithOr(value, finalSeparator)\n } else {\n expanded[key] = value\n }\n }\n return expanded\n }\n\n /**\n * Checks the payload for a file getting removed\n * and removes it from the upload files if found\n * @param request - the hapi request\n * @param state - the form state\n * @returns updated state if any files have been removed\n */\n private async checkRemovedFiles(\n request: FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { path } = this\n const { params } = request\n\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n const filesUpdated = files.filter(\n ({ status }) => status.form.file.fileId !== params.itemId\n )\n\n if (filesUpdated.length === files.length) {\n return\n }\n\n await this.mergeState(request, state, {\n upload: { [path]: { files: filesUpdated, upload } }\n })\n }\n\n /**\n * Initiates a CDP file upload and stores in the upload state\n * @param request - the hapi request\n * @param state - the form state\n */\n private async initiateAndStoreNewUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n const { fileUpload, href, path } = this\n const { options, schema } = fileUpload\n const { getFormMetadata } = this.model.services.formsService\n\n const files = this.getFilesFromState(state)\n\n // Reset the upload in state\n let upload: UploadInitiateResponse | undefined\n\n // Don't initiate anymore after minimum of `schema.max` or MAX_UPLOADS\n const max = Math.min(schema.max ?? MAX_UPLOADS, MAX_UPLOADS)\n\n if (files.length < max) {\n const formMetadata = await getFormMetadata(request.params.slug)\n const notificationEmail =\n formMetadata.notificationEmail ?? 'defraforms@defra.gov.uk'\n\n const newUpload = await initiateUpload(\n href,\n notificationEmail,\n options.accept\n )\n\n if (newUpload === undefined) {\n throw Boom.badRequest('Unexpected empty response from initiateUpload')\n }\n\n upload = newUpload\n }\n\n return this.mergeState(request, state, {\n upload: { [path]: { files, upload } }\n })\n }\n}\n"],"mappings":"AAAA,SAASA,aAAa,QAA6B,oBAAoB;AACvE,OAAOC,IAAI,MAAM,YAAY;AAC7B,SAASC,IAAI,QAAQ,YAAY;AACjC,SAASC,WAAW,QAAQ,mBAAmB;AAG/C,SACEC,eAAe,EACfC,cAAc;AAGhB,SACEC,eAAe,EACfC,QAAQ,EACRC,0BAA0B;AAI5B,SAASC,sBAAsB;AAC/B,SAASC,8BAA8B;AACvC,SACEC,eAAe,EACfC,cAAc;AAEhB,SACEC,UAAU,EACVC,YAAY;AAkBd,SAASC,UAAU;AAEnB,MAAMC,WAAW,GAAG,EAAE;AACtB,MAAMC,qBAAqB,GAAG,KAAK,EAAC;;AAEpC,OAAO,SAASC,aAAaA,CAACC,MAAgC,EAAE;EAC9D,MAAMC,IAAI,GAAGD,MAAM,CAACE,IAAI,CAACD,IAAI;EAC7B,MAAME,SAAS,GAAGF,IAAI,CAACG,UAAU,KAAKV,UAAU,CAACW,OAAO;EAExD,IAAI,CAACJ,IAAI,CAACK,YAAY,IAAIH,SAAS,EAAE;IACnCF,IAAI,CAACK,YAAY,GAAG,0CAA0C;EAChE;EAEA,OAAON,MAAM;AACf;AAEA,SAASO,gBAAgBA,CAACC,SAAoB,EAAE;EAC9CT,aAAa,CAACS,SAAS,CAACR,MAAM,CAAC;EAE/B,OAAOQ,SAAS;AAClB;AAEA,OAAO,MAAMC,wBAAwB,SAASnB,sBAAsB,CAAC;EAGnEoB,UAAU;EACVC,kBAAkB,GAAG,aAAa;EAElCC,WAAWA,CAACC,KAAgB,EAAEC,OAAuB,EAAE;IACrD,KAAK,CAACD,KAAK,EAAEC,OAAO,CAAC;IAErB,MAAM;MAAEC;IAAW,CAAC,GAAG,IAAI;;IAE3B;IACA,MAAMC,WAAW,GAAGD,UAAU,CAACE,MAAM,CAACC,MAAM,CACzCC,KAAK,IACJA,KAAK,CAACC,IAAI,KAAKvC,aAAa,CAACI,eACjC,CAAC;IAED,MAAMyB,UAAU,GAAGM,WAAW,CAACK,EAAE,CAAC,CAAC,CAAC;;IAEpC;IACA,IAAI,CAACX,UAAU,IAAIM,WAAW,CAACM,MAAM,GAAG,CAAC,EAAE;MACzC,MAAMxC,IAAI,CAACyC,iBAAiB,CAC1B,oEAAoET,OAAO,CAACU,IAAI,GAClF,CAAC;IACH;;IAEA;IACA,IAAIT,UAAU,CAACE,MAAM,CAACQ,OAAO,CAACf,UAAU,CAAC,KAAK,CAAC,EAAE;MAC/C,MAAM5B,IAAI,CAACyC,iBAAiB,CAC1B,aAAab,UAAU,CAACgB,IAAI,iEAAiEZ,OAAO,CAACU,IAAI,GAC3G,CAAC;IACH;;IAEA;IACA,IAAI,CAACd,UAAU,GAAGA,UAAU;IAC5B,IAAI,CAACiB,QAAQ,GAAG,aAAa;EAC/B;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;EACEC,YAAYA,CAACC,SAAwB,EAAY;IAC/C;IACA,IAAI,EAAEA,SAAS,YAAY5C,eAAe,CAAC,EAAE;MAC3C,OAAO,EAAE;IACX;IAEA,MAAM6C,QAAQ,GAAGD,SAAS,CAACE,IAAI,EAAEP,IAAI;IACrC,OAAOM,QAAQ,GAAG,CAAC,WAAWA,QAAQ,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC;EAC1D;EAEAE,oBAAoBA,CAClBC,OAAuC,EACvCC,KAA0B,EAC1B;IACA,MAAM;MAAExB;IAAW,CAAC,GAAG,IAAI;IAE3B,MAAMyB,OAAO,GAAG,KAAK,CAACH,oBAAoB,CAACC,OAAO,EAAEC,KAAK,CAAC;IAC1D,MAAME,KAAK,GAAG,IAAI,CAACC,iBAAiB,CAACH,KAAK,CAAC;;IAE3C;IACAC,OAAO,CAACzB,UAAU,CAACgB,IAAI,CAAC,GAAGU,KAAK,CAACd,MAAM,GAAGc,KAAK,GAAGE,SAAS;IAE3D,OAAOH,OAAO;EAChB;EAEA,MAAMI,QAAQA,CAACN,OAAuB,EAAE;IACtC,MAAM;MAAEvB;IAAW,CAAC,GAAG,IAAI;;IAE3B;IACA,MAAMwB,KAAK,GAAG,MAAM,KAAK,CAACK,QAAQ,CAACN,OAAO,CAAC;IAC3C,MAAMG,KAAK,GAAG,IAAI,CAACC,iBAAiB,CAACH,KAAK,CAAC;;IAE3C;IACAA,KAAK,CAACxB,UAAU,CAACgB,IAAI,CAAC,GAAGU,KAAK;IAE9B,OAAO,IAAI,CAACI,aAAa,CAACP,OAAO,EAAEC,KAAK,CAAC;EAC3C;;EAEA;AACF;AACA;EACEG,iBAAiBA,CAACH,KAA0B,EAAE;IAC5C,MAAM;MAAEV;IAAK,CAAC,GAAG,IAAI;IAErB,MAAMiB,WAAW,GAAGP,KAAK,CAACQ,MAAM,GAAGlB,IAAI,CAAC;IACxC,OAAOiB,WAAW,EAAEL,KAAK,IAAI,EAAE;EACjC;;EAEA;AACF;AACA;EACEO,kBAAkBA,CAACT,KAA0B,EAAE;IAC7C,MAAM;MAAEV;IAAK,CAAC,GAAG,IAAI;IAErB,MAAMiB,WAAW,GAAGP,KAAK,CAACQ,MAAM,GAAGlB,IAAI,CAAC;IACxC,OAAOiB,WAAW,EAAEC,MAAM;EAC5B;EAEAE,6BAA6BA,CAAA,EAAG;IAC9B,OAAO,CACLX,OAAoB,EACpBY,OAAoB,EACpBC,CAAsB,KACnB;MACH,MAAM;QAAEC;MAAU,CAAC,GAAG,IAAI;MAC1B,MAAM;QAAEC;MAAO,CAAC,GAAGf,OAAO;MAC1B,MAAM;QAAEC;MAAM,CAAC,GAAGW,OAAO;MAEzB,MAAMT,KAAK,GAAG,IAAI,CAACC,iBAAiB,CAACH,KAAK,CAAC;MAE3C,MAAMe,YAAY,GAAGb,KAAK,CAACc,IAAI,CAC7B,CAAC;QAAElD;MAAO,CAAC,KAAKA,MAAM,CAACE,IAAI,CAACD,IAAI,CAACkD,MAAM,KAAKH,MAAM,CAACI,MACrD,CAAC;MAED,IAAI,CAACH,YAAY,EAAE;QACjB,MAAMnE,IAAI,CAACuE,QAAQ,CAAC,0BAA0B,CAAC;MACjD;MAEA,MAAM;QAAEC;MAAS,CAAC,GAAGL,YAAY,CAACjD,MAAM,CAACE,IAAI,CAACD,IAAI;MAElD,MAAMsD,UAAU,GAAG,IAAI,CAACC,aAAa,CAACvB,OAAO,CAAC;MAC9C,MAAM;QAAEwB;MAAE,CAAC,GAAGF,UAAU;MAExB,OAAOT,CAAC,CAACY,IAAI,CAAC,IAAI,CAAC/C,kBAAkB,EAAE;QACrC,GAAGoC,SAAS;QACZF,OAAO;QACPc,QAAQ,EAAE,IAAI,CAACC,WAAW,CAAC3B,OAAO,EAAEY,OAAO,EAAEY,CAAC,CAAC;QAC/CI,SAAS,EAAEJ,CAAC,CAAC,kCAAkC,CAAC;QAChDK,SAAS,EAAER,QAAQ;QACnBS,YAAY,EAAE;UAAEC,IAAI,EAAEP,CAAC,CAAC,iCAAiC;QAAE,CAAC;QAC5DQ,aAAa,EAAE;UAAED,IAAI,EAAEP,CAAC,CAAC,mCAAmC;QAAE,CAAC;QAC/DS,YAAY,EAAE;UAAEF,IAAI,EAAEP,CAAC,CAAC,yBAAyB;QAAE,CAAC;QACpDA;MACF,CAAmC,CAAC;IACtC,CAAC;EACH;EAEAU,8BAA8BA,CAAA,EAAG;IAC/B,OAAO,OACLlC,OAA2B,EAC3BY,OAAoB,EACpBC,CAAsB,KACnB;MACH,MAAM;QAAEtB;MAAK,CAAC,GAAG,IAAI;MACrB,MAAM;QAAEU;MAAM,CAAC,GAAGW,OAAO;MAEzB,MAAM;QAAEuB;MAAQ,CAAC,GAAG,IAAI,CAACC,aAAa,CAACpC,OAAO,CAAC;;MAE/C;MACA,IAAImC,OAAO,EAAE;QACX,MAAM,IAAI,CAACE,iBAAiB,CAACrC,OAAO,EAAEC,KAAK,CAAC;QAC5C,OAAO,IAAI,CAACqC,OAAO,CAACtC,OAAO,EAAEa,CAAC,EAAEtB,IAAI,CAAC;MACvC;MAEA,OAAO,IAAI,CAAC+C,OAAO,CAACtC,OAAO,EAAEa,CAAC,CAAC;IACjC,CAAC;EACH;EAEA0B,SAASA,CAACC,OAA+B,EAAEC,QAAiB,EAAE;IAC5D,MAAM;MAAEhE;IAAW,CAAC,GAAG,IAAI;IAE3B,IAAI+D,OAAO,EAAE;MACX,MAAME,MAA6B,GAAG,EAAE;MAExCF,OAAO,CAACG,OAAO,CAAEC,KAAK,IAAK;QACzB,MAAMC,aAAa,GAAGD,KAAK,CAACrD,IAAI,CAAC,CAAC,CAAC,KAAKd,UAAU,CAACgB,IAAI;QACvD,MAAMqD,iBAAiB,GAAGD,aAAa,IAAID,KAAK,CAACrD,IAAI,CAACF,MAAM,KAAK,CAAC;QAElE,IAAI,CAACwD,aAAa,IAAIC,iBAAiB,EAAE;UACvC;UACA;UACAJ,MAAM,CAACK,IAAI,CAAC5F,QAAQ,CAACsF,QAAQ,EAAEG,KAAK,CAAC,CAAC;QACxC,CAAC,MAAM;UACL,MAAM;YAAEhC,OAAO;YAAErB,IAAI;YAAEJ;UAAK,CAAC,GAAGyD,KAAK;UAErC,IAAIzD,IAAI,KAAK,gBAAgB,IAAII,IAAI,CAACH,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,cAAc,EAAE;YAC/D,MAAM4D,KAAK,GAAGpC,OAAO,EAAEoC,KAA2B;YAElD,IAAIA,KAAK,EAAE;cACT,MAAMvD,IAAI,GAAGhB,UAAU,CAACgB,IAAI;cAC5B,MAAMsC,IAAI,GAAG,OAAOiB,KAAK,KAAK,QAAQ,GAAGA,KAAK,GAAG,eAAe;cAChE,MAAMC,IAAI,GAAG,IAAIxD,IAAI,EAAE;cAEvBiD,MAAM,CAACK,IAAI,CAAC;gBAAExD,IAAI;gBAAE0D,IAAI;gBAAExD,IAAI;gBAAEsC;cAAK,CAAC,CAAC;YACzC;UACF;QACF;MACF,CAAC,CAAC;MAEF,OAAOW,MAAM;IACf;EACF;EAEAQ,YAAYA,CACVlD,OAA2B,EAC3BY,OAAoB,EACpBU,UAAsB,EACK;IAC3B,MAAM;MAAE7C;IAAW,CAAC,GAAG,IAAI;IAC3B,MAAM;MAAEwB;IAAM,CAAC,GAAGW,OAAO;IACzB,MAAM;MAAEY;IAAE,CAAC,GAAGF,UAAU;IAExB,MAAMb,MAAM,GAAG,IAAI,CAACC,kBAAkB,CAACT,KAAK,CAAC;IAE7C,MAAMa,SAAS,GAAG,KAAK,CAACoC,YAAY,CAAClD,OAAO,EAAEY,OAAO,EAAEU,UAAU,CAAC;IAClE,MAAM;MAAE6B;IAAW,CAAC,GAAGrC,SAAS;;IAEhC;IACA,MAAM,CAACsC,aAAa,CAAC,GAAGD,UAAU,CAAClE,MAAM,CACvC,CAAC;MAAEL;IAAM,CAAC,KAAKA,KAAK,CAACyE,EAAE,KAAK5E,UAAU,CAACgB,IACzC,CAAC;IAED2D,aAAa,CAACxE,KAAK,CAAC6B,MAAM,GAAG;MAC3B6C,KAAK,EAAEF,aAAa,CAACxE,KAAK,CAAC6B,MAAM,EAAE6C,KAAK,IAAI,CAAC;MAC7CC,WAAW,EAAEH,aAAa,CAACxE,KAAK,CAAC6B,MAAM,EAAE8C,WAAW,IAAI;QAAEC,IAAI,EAAE;MAAG,CAAC;MACpEC,cAAc,EAAEjC,CAAC,CAAC,sCAAsC;IAC1D,CAAC;IACD4B,aAAa,CAACxE,KAAK,GAAG;MACpB,GAAGwE,aAAa,CAACxE,KAAK;MACtB8E,UAAU,EAAE,IAAI;MAChBC,qBAAqB,EAAEnC,CAAC,CAAC,uCAAuC,CAAC;MACjEoC,mBAAmB,EAAEpC,CAAC,CAAC,qCAAqC,CAAC;MAC7DqC,gBAAgB,EAAErC,CAAC,CAAC,yCAAyC;IAC/D,CAAC;IAED,MAAMsC,KAAK,GAAGX,UAAU,CAAC3D,OAAO,CAAC4D,aAAa,CAAC;IAE/C,MAAMW,QAAQ,GAAGzG,8BAA8B,CAACmD,MAAM,EAAEuD,SAAS,CAAC;IAElE,OAAO;MACL,GAAGlD,SAAS;MACZmD,UAAU,EAAExD,MAAM,EAAEuD,SAAS;MAC7BE,QAAQ,EAAEzD,MAAM,EAAEyD,QAAQ;MAC1Bd,aAAa;MAEb;MACAe,gBAAgB,EAAEhB,UAAU,CAACiB,KAAK,CAAC,CAAC,EAAEN,KAAK,CAAC;MAC5CX,UAAU,EAAEA,UAAU,CAACiB,KAAK,CAACN,KAAK,CAAC;MACnCC,QAAQ;MACRvC;IACF,CAAC;EACH;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACE,MAAcjB,aAAaA,CACzBP,OAAuB,EACvBC,KAA0B,EAC1B;IACAA,KAAK,GAAG,MAAM,IAAI,CAACoE,iBAAiB,CAACrE,OAAO,EAAEC,KAAK,CAAC;IAEpD,OAAOA,KAAK;EACd;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;EACE,MAAcoE,iBAAiBA,CAC7BrE,OAAuB,EACvBC,KAA0B,EAC1BqE,KAAK,GAAG,CAAC,EACqB;IAC9B,MAAM7D,MAAM,GAAG,IAAI,CAACC,kBAAkB,CAACT,KAAK,CAAC;IAC7C,MAAME,KAAK,GAAG,IAAI,CAACC,iBAAiB,CAACH,KAAK,CAAC;;IAE3C;IACA,IAAI,CAACQ,MAAM,EAAEyD,QAAQ,EAAE;MACrB,OAAO,IAAI,CAACK,yBAAyB,CAACvE,OAAO,EAAEC,KAAK,CAAC;IACvD;IAEA,MAAMiE,QAAQ,GAAGzD,MAAM,CAACyD,QAAQ;IAEhC,IAAIM,cAAc;IAElB,IAAI;MACFA,cAAc,GAAG,MAAMjH,eAAe,CAAC2G,QAAQ,CAAC;IAClD,CAAC,CAAC,OAAOO,GAAG,EAAE;MACZ;MACA;MACA;MACA;MACA,IACE5H,IAAI,CAAC6H,MAAM,CAACD,GAAG,CAAC,IAChBA,GAAG,CAACE,MAAM,CAACC,UAAU,KAAK7H,WAAW,CAAC8H,SAAS,CAACC,OAAO,CAAC,CAAC,EACzD;QACA,OAAO,IAAI,CAACP,yBAAyB,CAACvE,OAAO,EAAEC,KAAK,CAAC;MACvD;MACA,MAAMwE,GAAG;IACX;IAEA,IAAI,CAACD,cAAc,EAAE;MACnB,MAAM3H,IAAI,CAACkI,UAAU,CACnB,sDAAsDb,QAAQ,EAChE,CAAC;IACH;;IAEA;IACA,IAAIM,cAAc,CAACQ,YAAY,KAAKtH,YAAY,CAACuH,SAAS,EAAE;MAC1D,OAAOhF,KAAK;IACd;IAEA,IAAIuE,cAAc,CAACQ,YAAY,KAAKtH,YAAY,CAACU,OAAO,EAAE;MACxD;MACA;MACA;MACA,IAAIkG,KAAK,IAAI,CAAC,EAAE;QACd,MAAMG,GAAG,GAAG,IAAIS,KAAK,CACnB,uCAAuChB,QAAQ,YAAYI,KAAK,gCAClE,CAAC;QACDtE,OAAO,CAACmF,MAAM,CAACvC,KAAK,CAClB6B,GAAG,EACH,iEAAiEP,QAAQ,cAAcI,KAAK,6BAC9F,CAAC;QACD,MAAM,IAAI,CAACC,yBAAyB,CAACvE,OAAO,EAAEC,KAAK,CAAC;QACpD,MAAMpD,IAAI,CAACuI,cAAc,CACvB,yBAAyBlB,QAAQ,uCAAuC,CAAC,CAACrG,qBAAqB,GAAG,IAAI,IAAI,IAAI,EAAEwH,OAAO,CAAC,CAAC,CAAC,UAC5H,CAAC;MACH;MACA,MAAMC,KAAK,GAAGlI,0BAA0B,CAACkH,KAAK,CAAC;MAC/CtE,OAAO,CAACmF,MAAM,CAACI,IAAI,CACjB,yBAAyBD,KAAK,GAAG,IAAI,0BAA0BpB,QAAQ,8BAA8BI,KAAK,GAC5G,CAAC;MACD,MAAMxH,IAAI,CAACwI,KAAK,CAAC;MACjB,OAAO,IAAI,CAACjB,iBAAiB,CAACrE,OAAO,EAAEC,KAAK,EAAEqE,KAAK,GAAG,CAAC,CAAC;IAC1D;;IAEA;IACA;IACA;IACA,MAAMkB,gBAAgB,GAAGvI,cAAc,CAACwI,QAAQ,CAC9C;MAAEvB,QAAQ;MAAEnG,MAAM,EAAEyG;IAAe,CAAC,EACpC;MAAEkB,YAAY,EAAE;IAAK,CACvB,CAAC;IACD,MAAM9C,KAAK,GAAG4C,gBAAgB,CAAC5C,KAAK;IAEpC,IAAIA,KAAK,EAAE;MACT,OAAO,IAAI,CAAC2B,yBAAyB,CAACvE,OAAO,EAAEC,KAAK,CAAC;IACvD;;IAEA;IACA;IACA;IACA,MAAM,IAAI,CAAC0F,oBAAoB,CAC7B3F,OAAO,EACPC,KAAK,EACLuF,gBAAgB,CAACxC,KAAK,EACtB7C,KAAK,EACLM,MACF,CAAC;IAED,OAAO,IAAI,CAAC8D,yBAAyB,CAACvE,OAAO,EAAEC,KAAK,CAAC;EACvD;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACE,MAAc0F,oBAAoBA,CAChC3F,OAAuB,EACvBC,KAA0B,EAC1B2F,aAAwB,EACxBzF,KAAkB,EAClBM,MAA0C,EAC1C;IACA,MAAM;MAAEyD;IAAS,CAAC,GAAG0B,aAAa;IAClC,MAAMC,eAAe,GAAGD,aAAa,CAAC7H,MAAM;IAC5C,MAAM+H,OAAO,GAAGD,eAAe,CAAC5H,IAAI,CAACD,IAErB;IAChB,MAAM+H,aAAa,GAAGC,KAAK,CAACC,OAAO,CAACH,OAAO,CAAC,GAAGA,OAAO,GAAG,CAACA,OAAO,CAAC;IAElE,MAAMI,SAAgC,GAAG,EAAE;IAE3C,MAAM5E,UAAU,GAAG,IAAI,CAACC,aAAa,CAACvB,OAAO,CAAC;IAC9C,MAAM;MAAEwB;IAAE,CAAC,GAAGF,UAAU;IAExB,KAAK,MAAMtD,IAAI,IAAI+H,aAAa,EAAE;MAChC,IAAI/H,IAAI,CAACG,UAAU,KAAKV,UAAU,CAAC0I,QAAQ,EAAE;QAC3C,MAAMC,YAAuB,GAAG;UAC9BlC,QAAQ;UACRnG,MAAM,EAAE;YACN,GAAG8H,eAAe;YAClB5H,IAAI,EAAE;cAAED;YAAK;UACf;QACF,CAAC;QACDmC,KAAK,CAACkG,OAAO,CAAC/H,gBAAgB,CAAC8H,YAAY,CAAC,CAAC;MAC/C,CAAC,MAAM;QACL;QACA,MAAM;UAAE3H;QAAW,CAAC,GAAG,IAAI;QAC3B,MAAMgB,IAAI,GAAGhB,UAAU,CAACgB,IAAI;QAC5B,MAAM6G,WAAW,GAAG,IAAI,CAACC,iBAAiB,CACxCvI,IAAI,CAACsI,WAAW,EAChB9E,CAAC,CAAC,WAAW,CACf,CAAC;QACD,MAAMO,IAAI,GAAGP,CAAC,CACZ,oBAAoBxD,IAAI,CAACwI,SAAS,IAAI,oBAAoB,EAAE,EAC5DF,WACF,CAAC;QACDJ,SAAS,CAACnD,IAAI,CAAC;UAAExD,IAAI,EAAE,CAACE,IAAI,CAAC;UAAEwD,IAAI,EAAE,IAAIxD,IAAI,EAAE;UAAEA,IAAI;UAAEsC;QAAK,CAAC,CAAC;MAChE;IACF;IAEA,IAAImE,SAAS,CAAC7G,MAAM,EAAE;MACpB,MAAMoH,YAAY,GAAGvJ,eAAe,CAAC8C,OAAO,CAAC0G,MAAM,CAAC;MACpDD,YAAY,CAACE,QAAQ,CAAC3G,OAAO,EAAE;QAAE0C,MAAM,EAAEwD;MAAU,CAAC,CAAC;IACvD;IAEA,IAAIH,aAAa,CAACa,IAAI,CAAEC,CAAC,IAAKA,CAAC,CAAC1I,UAAU,KAAKV,UAAU,CAAC0I,QAAQ,CAAC,EAAE;MACnE,MAAM,IAAI,CAACW,UAAU,CAAC9G,OAAO,EAAEC,KAAK,EAAE;QACpCQ,MAAM,EAAE;UAAE,CAAC,IAAI,CAAClB,IAAI,GAAG;YAAEY,KAAK;YAAEM;UAAO;QAAE;MAC3C,CAAC,CAAC;IACJ;EACF;;EAEA;AACF;AACA;AACA;AACA;EACU8F,iBAAiBA,CACvBD,WAAmE,EACnES,cAAsB,EACtB;IACA,IAAI,CAACT,WAAW,EAAE;MAChB,OAAO,CAAC,CAAC;IACX;IACA,MAAMU,QAAQ,GAAG,CAAC,CAAoC;IACtD,KAAK,MAAM,CAACC,GAAG,EAAEjE,KAAK,CAAC,IAAIkE,MAAM,CAACC,OAAO,CAACb,WAAW,CAAC,EAAE;MACtD,IAAIN,KAAK,CAACC,OAAO,CAACjD,KAAK,CAAC,EAAE;QACxBgE,QAAQ,CAACC,GAAG,CAAC,GAAGtJ,UAAU,CAACqF,KAAK,EAAE+D,cAAc,CAAC;MACnD,CAAC,MAAM;QACLC,QAAQ,CAACC,GAAG,CAAC,GAAGjE,KAAK;MACvB;IACF;IACA,OAAOgE,QAAQ;EACjB;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;EACE,MAAc3E,iBAAiBA,CAC7BrC,OAA2B,EAC3BC,KAA0B,EAC1B;IACA,MAAM;MAAEV;IAAK,CAAC,GAAG,IAAI;IACrB,MAAM;MAAEwB;IAAO,CAAC,GAAGf,OAAO;IAE1B,MAAMS,MAAM,GAAG,IAAI,CAACC,kBAAkB,CAACT,KAAK,CAAC;IAC7C,MAAME,KAAK,GAAG,IAAI,CAACC,iBAAiB,CAACH,KAAK,CAAC;IAE3C,MAAMmH,YAAY,GAAGjH,KAAK,CAAClB,MAAM,CAC/B,CAAC;MAAElB;IAAO,CAAC,KAAKA,MAAM,CAACE,IAAI,CAACD,IAAI,CAACkD,MAAM,KAAKH,MAAM,CAACI,MACrD,CAAC;IAED,IAAIiG,YAAY,CAAC/H,MAAM,KAAKc,KAAK,CAACd,MAAM,EAAE;MACxC;IACF;IAEA,MAAM,IAAI,CAACyH,UAAU,CAAC9G,OAAO,EAAEC,KAAK,EAAE;MACpCQ,MAAM,EAAE;QAAE,CAAClB,IAAI,GAAG;UAAEY,KAAK,EAAEiH,YAAY;UAAE3G;QAAO;MAAE;IACpD,CAAC,CAAC;EACJ;;EAEA;AACF;AACA;AACA;AACA;EACE,MAAc8D,yBAAyBA,CACrCvE,OAAuB,EACvBC,KAA0B,EAC1B;IACA,MAAM;MAAExB,UAAU;MAAEwE,IAAI;MAAE1D;IAAK,CAAC,GAAG,IAAI;IACvC,MAAM;MAAE8H,OAAO;MAAEC;IAAO,CAAC,GAAG7I,UAAU;IACtC,MAAM;MAAE8I;IAAgB,CAAC,GAAG,IAAI,CAAC3I,KAAK,CAAC4I,QAAQ,CAACC,YAAY;IAE5D,MAAMtH,KAAK,GAAG,IAAI,CAACC,iBAAiB,CAACH,KAAK,CAAC;;IAE3C;IACA,IAAIQ,MAA0C;;IAE9C;IACA,MAAMiH,GAAG,GAAGC,IAAI,CAACC,GAAG,CAACN,MAAM,CAACI,GAAG,IAAI9J,WAAW,EAAEA,WAAW,CAAC;IAE5D,IAAIuC,KAAK,CAACd,MAAM,GAAGqI,GAAG,EAAE;MACtB,MAAMG,YAAY,GAAG,MAAMN,eAAe,CAACvH,OAAO,CAACe,MAAM,CAAC+G,IAAI,CAAC;MAC/D,MAAMC,iBAAiB,GACrBF,YAAY,CAACE,iBAAiB,IAAI,yBAAyB;MAE7D,MAAMC,SAAS,GAAG,MAAMxK,cAAc,CACpCyF,IAAI,EACJ8E,iBAAiB,EACjBV,OAAO,CAACY,MACV,CAAC;MAED,IAAID,SAAS,KAAK3H,SAAS,EAAE;QAC3B,MAAMxD,IAAI,CAACkI,UAAU,CAAC,+CAA+C,CAAC;MACxE;MAEAtE,MAAM,GAAGuH,SAAS;IACpB;IAEA,OAAO,IAAI,CAAClB,UAAU,CAAC9G,OAAO,EAAEC,KAAK,EAAE;MACrCQ,MAAM,EAAE;QAAE,CAAClB,IAAI,GAAG;UAAEY,KAAK;UAAEM;QAAO;MAAE;IACtC,CAAC,CAAC;EACJ;AACF","ignoreList":[]}
@@ -37,7 +37,7 @@ export declare class QuestionPageController extends PageController {
37
37
  */
38
38
  getFormParams(request?: FormContextRequest): FormPayloadParams;
39
39
  getStateFromValidForm(request: FormContextRequest, state: FormSubmissionState, payload: FormPayload): FormState;
40
- getErrors(details?: ValidationErrorItem[]): import("../../../../server/plugins/engine/types.js").FormSubmissionError[] | undefined;
40
+ getErrors(details?: ValidationErrorItem[], language?: string): import("../../../../server/plugins/engine/types.js").FormSubmissionError[] | undefined;
41
41
  getState(request: AnyFormRequest): Promise<FormSubmissionState>;
42
42
  setState(request: AnyFormRequest, state: FormSubmissionState): Promise<FormSubmissionState>;
43
43
  mergeState(request: AnyFormRequest, state: FormSubmissionState, update: object): Promise<FormSubmissionState>;
@@ -126,7 +126,11 @@ export class QuestionPageController extends PageController {
126
126
  if (!pageTitle) {
127
127
  pageTitle = labelOrLegend.text;
128
128
  }
129
- labelOrLegend.text = isOptional ? `${pageTitle} ${t('common.optional')}` : pageTitle;
129
+
130
+ // The question text may have already appended 'optional' in the relevant language
131
+ const optionalWord = t('common.optional');
132
+ const optionalAlreadyAppended = pageTitle.endsWith(` ${optionalWord}`);
133
+ labelOrLegend.text = isOptional && !optionalAlreadyAppended ? `${pageTitle} ${optionalWord}` : pageTitle;
130
134
  }
131
135
 
132
136
  // Fallback if still no pageTitle set
@@ -292,8 +296,8 @@ export class QuestionPageController extends PageController {
292
296
  getStateFromValidForm(request, state, payload) {
293
297
  return this.collection.getStateFromValidForm(payload);
294
298
  }
295
- getErrors(details) {
296
- return getErrors(details);
299
+ getErrors(details, language) {
300
+ return getErrors(language, details);
297
301
  }
298
302
  async getState(request) {
299
303
  const {