@defra/forms-engine-plugin 5.0.0-alpha.10 → 5.0.0-alpha.12

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 (99) 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/.server/client/javascripts/file-upload.js +1 -0
  8. package/.server/client/javascripts/file-upload.js.map +1 -1
  9. package/.server/client/javascripts/geospatial-map.js +2 -28
  10. package/.server/client/javascripts/geospatial-map.js.map +1 -1
  11. package/.server/client/javascripts/govuk.js +2 -1
  12. package/.server/client/javascripts/govuk.js.map +1 -1
  13. package/.server/client/javascripts/location-map.js +3 -2
  14. package/.server/client/javascripts/location-map.js.map +1 -1
  15. package/.server/client/javascripts/map.d.ts +6 -0
  16. package/.server/client/javascripts/map.js +37 -0
  17. package/.server/client/javascripts/map.js.map +1 -1
  18. package/.server/server/plugins/engine/components/CheckboxesField.d.ts +20 -2
  19. package/.server/server/plugins/engine/components/CheckboxesField.js +24 -12
  20. package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
  21. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +5 -0
  22. package/.server/server/plugins/engine/components/EastingNorthingField.js +17 -0
  23. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
  24. package/.server/server/plugins/engine/components/FileUploadField.d.ts +10 -1
  25. package/.server/server/plugins/engine/components/FileUploadField.js +21 -7
  26. package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
  27. package/.server/server/plugins/engine/components/GeospatialField.d.ts +21 -0
  28. package/.server/server/plugins/engine/components/GeospatialField.js +32 -9
  29. package/.server/server/plugins/engine/components/GeospatialField.js.map +1 -1
  30. package/.server/server/plugins/engine/components/LatLongField.d.ts +4 -0
  31. package/.server/server/plugins/engine/components/LocationFieldBase.js +4 -1
  32. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
  33. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +5 -1
  34. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +10 -2
  35. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
  36. package/.server/server/plugins/engine/components/helpers/geospatial.d.ts +8 -1
  37. package/.server/server/plugins/engine/components/helpers/geospatial.js +56 -15
  38. package/.server/server/plugins/engine/components/helpers/geospatial.js.map +1 -1
  39. package/.server/server/plugins/engine/form-availability.d.ts +3 -2
  40. package/.server/server/plugins/engine/form-availability.js +3 -2
  41. package/.server/server/plugins/engine/form-availability.js.map +1 -1
  42. package/.server/server/plugins/engine/form-context.js +2 -2
  43. package/.server/server/plugins/engine/form-context.js.map +1 -1
  44. package/.server/server/plugins/engine/i18n/buildValidationMessages.d.ts +1 -1
  45. package/.server/server/plugins/engine/i18n/buildValidationMessages.js +30 -9
  46. package/.server/server/plugins/engine/i18n/buildValidationMessages.js.map +1 -1
  47. package/.server/server/plugins/engine/i18n/translations/cy.json +79 -13
  48. package/.server/server/plugins/engine/i18n/translations/en-GB.json +42 -13
  49. package/.server/server/plugins/engine/i18n/types.d.ts +1 -0
  50. package/.server/server/plugins/engine/i18n/types.js.map +1 -1
  51. package/.server/server/plugins/engine/models/unavailable-view-model.d.ts +11 -2
  52. package/.server/server/plugins/engine/models/unavailable-view-model.js +23 -4
  53. package/.server/server/plugins/engine/models/unavailable-view-model.js.map +1 -1
  54. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +6 -0
  55. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +34 -1
  56. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  57. package/.server/server/plugins/engine/pageControllers/validationOptions.d.ts +1 -1
  58. package/.server/server/plugins/engine/pageControllers/validationOptions.js +1 -1
  59. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  60. package/.server/server/plugins/engine/types.d.ts +4 -0
  61. package/.server/server/plugins/engine/types.js.map +1 -1
  62. package/.server/server/plugins/engine/unavailable-response.js +5 -2
  63. package/.server/server/plugins/engine/unavailable-response.js.map +1 -1
  64. package/.server/server/plugins/engine/views/components/_location-field-base.html +3 -2
  65. package/.server/server/plugins/engine/views/file-upload.html +1 -1
  66. package/.server/server/plugins/engine/views/unavailable.html +14 -11
  67. package/.server/server/utils/utils.d.ts +6 -0
  68. package/.server/server/utils/utils.js +18 -1
  69. package/.server/server/utils/utils.js.map +1 -1
  70. package/.server/server/utils/utils.test.js +21 -1
  71. package/.server/server/utils/utils.test.js.map +1 -1
  72. package/package.json +2 -1
  73. package/src/client/javascripts/file-upload.js +1 -0
  74. package/src/client/javascripts/geospatial-map.js +2 -31
  75. package/src/client/javascripts/govuk.js +2 -0
  76. package/src/client/javascripts/location-map.js +3 -1
  77. package/src/client/javascripts/map.js +42 -0
  78. package/src/server/plugins/engine/components/CheckboxesField.ts +34 -13
  79. package/src/server/plugins/engine/components/EastingNorthingField.ts +31 -0
  80. package/src/server/plugins/engine/components/FileUploadField.ts +29 -7
  81. package/src/server/plugins/engine/components/GeospatialField.ts +43 -11
  82. package/src/server/plugins/engine/components/LocationFieldBase.ts +3 -1
  83. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +13 -3
  84. package/src/server/plugins/engine/components/helpers/geospatial.ts +84 -21
  85. package/src/server/plugins/engine/form-availability.ts +8 -2
  86. package/src/server/plugins/engine/form-context.ts +4 -2
  87. package/src/server/plugins/engine/i18n/buildValidationMessages.ts +15 -10
  88. package/src/server/plugins/engine/i18n/translations/cy.json +79 -13
  89. package/src/server/plugins/engine/i18n/translations/en-GB.json +42 -13
  90. package/src/server/plugins/engine/i18n/types.ts +1 -0
  91. package/src/server/plugins/engine/models/unavailable-view-model.ts +26 -5
  92. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +42 -1
  93. package/src/server/plugins/engine/pageControllers/validationOptions.ts +2 -2
  94. package/src/server/plugins/engine/types.ts +4 -0
  95. package/src/server/plugins/engine/unavailable-response.ts +6 -2
  96. package/src/server/plugins/engine/views/components/_location-field-base.html +3 -2
  97. package/src/server/plugins/engine/views/file-upload.html +1 -1
  98. package/src/server/plugins/engine/views/unavailable.html +14 -11
  99. package/src/server/utils/utils.js +19 -1
@@ -16,15 +16,61 @@
16
16
  "numberMax": "Rhaid i {{#label}} fod yn {{#limit}} neu is",
17
17
  "maxWords": "Rhaid i {{#label}} fod yn {{#limit}} o eiriau neu lai",
18
18
  "unicode": "Mae {{#label}} yn cynnwys nodau annilys, er enghraifft, dashes hirion",
19
- "arrayMin": "Dewiswch o leiaf {{#limit}} o opsiynau o'r rhestr",
20
- "arrayMax": "Dim ond {{#limit}} y gellir eu dewis o'r rhestr",
21
- "arrayLength": "Dewiswch ddim ond {{#limit}} o opsiynau o'r rhestr",
22
- "featuresMin": "Diffiniwch o leiaf {{#limit}} nodwedd",
23
- "featuresMax": "Dim ond {{#limit}} nodwedd y gellir eu diffinio",
24
- "featuresLength": "Diffiniwch nodweddion {{#limit}} yn union",
25
- "filesMin": "Rhaid i chi uwchlwytho {{#limit}} o ffeiliau neu fwy",
26
- "filesMax": "Dim ond {{#limit}} o ffeiliau neu lai y gallwch eu huwchlwytho",
27
- "filesLength": "Rhaid i chi uwchlwytho'n union {{#limit}} o ffeiliau"
19
+ "arrayMin_zero": "Dewiswch o leiaf 1 opsiwn o'r rhestr",
20
+ "arrayMin_one": "Dewiswch o leiaf {{#limit}} opsiwn o'r rhestr",
21
+ "arrayMin_two": "Dewiswch o leiaf ddau opsiwn o'r rhestr",
22
+ "arrayMin_few": "Dewiswch o leiaf {{#limit}} opsiwn o'r rhestr",
23
+ "arrayMin_many": "Dewiswch o leiaf {{#limit}} opsiwn o'r rhestr",
24
+ "arrayMin_other": "Dewiswch o leiaf {{#limit}} opsiwn o'r rhestr",
25
+ "arrayMax_zero": "Dim ond {{#limit}} opsiynau y gellir eu dewis o'r rhestr",
26
+ "arrayMax_one": "Dim ond {{#limit}} opsiwn y gellir ei ddewis o'r rhestr",
27
+ "arrayMax_two": "Dim ond dwy opsiwn y gellir eu dewis o'r rhestr",
28
+ "arrayMax_few": "Dim ond {{#limit}} opsiwn y gellir eu dewis o'r rhestr",
29
+ "arrayMax_many": "Dim ond {{#limit}} opsiwn y gellir eu dewis o'r rhestr",
30
+ "arrayMax_other": "Dim ond {{#limit}} opsiwn y gellir eu dewis o'r rhestr",
31
+ "arrayLength_zero": "Dewiswch 0 opsiwn yn unig o'r rhestr",
32
+ "arrayLength_one": "Dewiswch un opsiwn yn unig o'r rhestr",
33
+ "arrayLength_two": "Dewiswch {{#limit}} opsiwn yn unig o'r rhestr",
34
+ "arrayLength_few": "Dewiswch {{#limit}} opsiwn yn unig o'r rhestr",
35
+ "arrayLength_many": "Dewiswch {{#limit}} opsiwn yn unig o'r rhestr",
36
+ "arrayLength_other": "Dewiswch {{#limit}} opsiwn yn unig o'r rhestr",
37
+
38
+ "featuresMin_zero": "Diffiniwch o leiaf {{#limit}} nodwedd",
39
+ "featuresMin_one": "Diffiniwch o leiaf un nodwedd",
40
+ "featuresMin_two": "Diffiniwch o leiaf {{#limit}} nodwedd",
41
+ "featuresMin_few": "Diffiniwch o leiaf {{#limit}} nodwedd",
42
+ "featuresMin_many": "Diffiniwch o leiaf {{#limit}} nodwedd",
43
+ "featuresMin_other": "Diffiniwch o leiaf {{#limit}} nodwedd",
44
+ "featuresMax_zero": "Dim ond {{#limit}} nodwedd y gellir eu diffinio",
45
+ "featuresMax_one": "Dim ond {{#limit}} nodwedd y gellir ei diffinio",
46
+ "featuresMax_two": "Dim ond {{#limit}} nodwedd y gellir eu diffinio",
47
+ "featuresMax_few": "Dim ond {{#limit}} nodwedd y gellir eu diffinio",
48
+ "featuresMax_many": "Dim ond {{#limit}} nodwedd y gellir eu diffinio",
49
+ "featuresMax_other": "Dim ond {{#limit}} nodwedd y gellir eu diffinio",
50
+ "featuresLength_zero": "Diffiniwch yn union {{#limit}} nodwedd",
51
+ "featuresLength_one": "Diffiniwch un nodwedd yn union",
52
+ "featuresLength_two": "Diffiniwch nodweddion {{#limit}} yn union",
53
+ "featuresLength_few": "Diffiniwch nodweddion {{#limit}} yn union",
54
+ "featuresLength_many": "Diffiniwch nodweddion {{#limit}} yn union",
55
+ "featuresLength_other": "Diffiniwch nodweddion {{#limit}} yn union",
56
+ "filesMin_zero": "Rhaid i chi uwchlwytho {{#limit}} ffeil neu fwy",
57
+ "filesMin_one": "Rhaid i chi uwchlwytho un ffeil neu fwy",
58
+ "filesMin_two": "Rhaid i chi uwchlwytho {{#limit}} o ffeiliau neu fwy",
59
+ "filesMin_few": "Rhaid i chi uwchlwytho {{#limit}} o ffeiliau neu fwy",
60
+ "filesMin_many": "Rhaid i chi uwchlwytho {{#limit}} o ffeiliau neu fwy",
61
+ "filesMin_other": "Rhaid i chi uwchlwytho {{#limit}} o ffeiliau neu fwy",
62
+ "filesMax_zero": "Dim ond {{#limit}} ffeil neu lai y gallwch eu huwchlwytho",
63
+ "filesMax_one": "Dim ond un ffeil neu lai y gallwch ei huwchlwytho",
64
+ "filesMax_two": "Dim ond {{#limit}} o ffeiliau neu lai y gallwch eu huwchlwytho",
65
+ "filesMax_few": "Dim ond {{#limit}} o ffeiliau neu lai y gallwch eu huwchlwytho",
66
+ "filesMax_many": "Dim ond {{#limit}} o ffeiliau neu lai y gallwch eu huwchlwytho",
67
+ "filesMax_other": "Dim ond {{#limit}} o ffeiliau neu lai y gallwch eu huwchlwytho",
68
+ "filesLength_zero": "Rhaid i chi uwchlwytho union {{#limit}} ffeil",
69
+ "filesLength_one": "Rhaid i chi uwchlwytho union un ffeil",
70
+ "filesLength_two": "Rhaid i chi uwchlwytho'n union {{#limit}} o ffeiliau",
71
+ "filesLength_few": "Rhaid i chi uwchlwytho'n union {{#limit}} o ffeiliau",
72
+ "filesLength_many": "Rhaid i chi uwchlwytho'n union {{#limit}} o ffeiliau",
73
+ "filesLength_other": "Rhaid i chi uwchlwytho'n union {{#limit}} o ffeiliau"
28
74
  },
29
75
 
30
76
  "errors": {
@@ -44,7 +90,7 @@
44
90
  "and": "a",
45
91
  "england": "Lloegr",
46
92
  "wales": "Cymru",
47
- "northernIreland": "Gogledd Iwerddon",
93
+ "northern-ireland": "Gogledd Iwerddon",
48
94
  "scotland": "Yr Alban",
49
95
  "sendFeedback": "Anfon adborth"
50
96
  },
@@ -129,7 +175,13 @@
129
175
  "removeFileTitle": "Ydych chi'n siŵr eich bod chi eisiau dileu'r ffeil hon?",
130
176
  "removeFileBody": "Ni allwch adfer ffeiliau a dynnwyd.",
131
177
  "removeFileButton": "Dileu ffeil",
132
- "cancel": "Canslo"
178
+ "cancel": "Canslo",
179
+ "FILE_VIRUS": "Mae'r ffeil a ddewiswyd yn cynnwys firws",
180
+ "FILE_EMPTY": "Mae'r ffeil a ddewiswyd yn wag",
181
+ "FILE_TOO_LARGE": "Rhaid i'r ffeil a ddewiswyd fod yn llai na [[maxFileSizeFormatted]]",
182
+ "FILE_INVALID_TYPE": "Rhaid i'r ffeil a ddewiswyd fod yn [[fileExtensions]]",
183
+ "FILE_UPLOAD_FAILED": "Ni ellid uwchlwytho'r ffeil a ddewiswyd - rhowch gynnig arall arni",
184
+ "FILE_ERROR_UNKNOWN": "Gwall anhysbys"
133
185
  },
134
186
 
135
187
  "exit": {
@@ -155,6 +207,16 @@
155
207
  "bannerForce": "Rhagolwg o ffurflen [[previewMode]] yw hon. Peidiwch â rhoi gwybodaeth bersonol.",
156
208
  "bannerText": "Dyma ragolwg o dudalen ffurflen [[previewMode]] rydych chi'n ei golygu.",
157
209
  "previousPages": "Mae'n dibynnu ar atebion o dudalennau cynharach yn y ffurflen. Yn y fersiwn fyw, bydd angen i ddefnyddwyr gwblhau'r cwestiynau hynny yn gyntaf."
210
+ },
211
+
212
+ "formUnavailable": {
213
+ "title": "Mae'n ddrwg gennyf, nid yw'r ffurflen hon ar gael",
214
+ "subHeading": "Nid yw '[[formTitle]]' ar gael mwyach.",
215
+ "contactDetailsHeading": "Manylion cyswllt ar gyfer [[organisationName]]",
216
+ "telephone": "Ffôn",
217
+ "callCharges": "Dysgwch am gostau galwadau",
218
+ "email": "E-bost",
219
+ "onlineContactForm": "Ffurflen gyswllt ar-lein"
158
220
  }
159
221
  },
160
222
 
@@ -244,7 +306,8 @@
244
306
  "eastingDigits": "Rhaid i {{#label}} ar gyfer [[fieldLabel]] fod rhwng 1 a 6 digid",
245
307
  "northingDigits": "Rhaid i {{#label}} ar gyfer [[fieldLabel]] fod rhwng 1 a 7 digid",
246
308
  "eastingRange": "Rhaid i {{#label}} ar gyfer [[fieldLabel]] fod rhwng [[min]] a [[max]]",
247
- "northingRange": "Rhaid i {{#label}} ar gyfer [[fieldLabel]] fod rhwng [[min]] a [[max]]"
309
+ "northingRange": "Rhaid i {{#label}} ar gyfer [[fieldLabel]] fod rhwng [[min]] a [[max]]",
310
+ "wrongCountry": "Rhaid i leoliad fod y tu mewn i [[country]]"
248
311
  },
249
312
 
250
313
  "nationalGridField": {
@@ -319,7 +382,10 @@
319
382
  "filesCount_two": "Uwchlwythwyd [[count]] ffeil",
320
383
  "filesCount_few": "Uwchlwythwyd [[count]] ffeil",
321
384
  "filesCount_many": "Uwchlwythwyd [[count]] o ffeiliau",
322
- "filesCount_other": "Uwchlwythwyd [[count]] ffeil"
385
+ "filesCount_other": "Uwchlwythwyd [[count]] ffeil",
386
+ "chooseFile": "Dewiswch ffeil",
387
+ "dropFile": "neu ollwng ffeil",
388
+ "noFileChosen": "Dim ffeil wedi'i dewis"
323
389
  },
324
390
 
325
391
  "characterCount": {
@@ -16,15 +16,24 @@
16
16
  "numberMax": "{{#label}} must be {{#limit}} or lower",
17
17
  "maxWords": "{{#label}} must be {{#limit}} words or fewer",
18
18
  "unicode": "{{#label}} includes invalid characters, for example, long dashes",
19
- "arrayMin": "Select at least {{#limit}} options from the list",
20
- "arrayMax": "Only {{#limit}} can be selected from the list",
21
- "arrayLength": "Select only {{#limit}} options from the list",
22
- "featuresMin": "Define at least {{#limit}} features",
23
- "featuresMax": "Only {{#limit}} features can be defined",
24
- "featuresLength": "Define exactly {{#limit}} features",
25
- "filesMin": "You must upload {{#limit}} files or more",
26
- "filesMax": "You can only upload {{#limit}} files or less",
27
- "filesLength": "You must upload exactly {{#limit}} files"
19
+ "arrayMin_one": "Select at least {{#limit}} option from the list",
20
+ "arrayMin_other": "Select at least {{#limit}} options from the list",
21
+ "arrayMax_one": "Only {{#limit}} option can be selected from the list",
22
+ "arrayMax_other": "Only {{#limit}} options can be selected from the list",
23
+ "arrayLength_one": "Select only {{#limit}} option from the list",
24
+ "arrayLength_other": "Select only {{#limit}} options from the list",
25
+ "featuresMin_one": "Define at least {{#limit}} feature",
26
+ "featuresMin_other": "Define at least {{#limit}} features",
27
+ "featuresMax_one": "Only {{#limit}} feature can be defined",
28
+ "featuresMax_other": "Only {{#limit}} features can be defined",
29
+ "featuresLength_one": "Define exactly {{#limit}} feature",
30
+ "featuresLength_other": "Define exactly {{#limit}} features",
31
+ "filesMin_one": "You must upload {{#limit}} file or more",
32
+ "filesMin_other": "You must upload {{#limit}} files or more",
33
+ "filesMax_one": "You can only upload {{#limit}} file or less",
34
+ "filesMax_other": "You can only upload {{#limit}} files or less",
35
+ "filesLength_one": "You must upload exactly {{#limit}} file",
36
+ "filesLength_other": "You must upload exactly {{#limit}} files"
28
37
  },
29
38
 
30
39
  "errors": {
@@ -44,7 +53,7 @@
44
53
  "and": "and",
45
54
  "england": "England",
46
55
  "wales": "Wales",
47
- "northernIreland": "Northern Ireland",
56
+ "northern-ireland": "Northern Ireland",
48
57
  "scotland": "Scotland",
49
58
  "sendFeedback": "Send feedback"
50
59
  },
@@ -113,7 +122,13 @@
113
122
  "removeFileTitle": "Are you sure you want to remove this file?",
114
123
  "removeFileBody": "You cannot recover removed files.",
115
124
  "removeFileButton": "Remove file",
116
- "cancel": "Cancel"
125
+ "cancel": "Cancel",
126
+ "FILE_VIRUS": "The selected file contains a virus",
127
+ "FILE_EMPTY": "The selected file is empty",
128
+ "FILE_TOO_LARGE": "The selected file must be smaller than [[maxFileSizeFormatted]]",
129
+ "FILE_INVALID_TYPE": "The selected file must be a [[fileExtensions]]",
130
+ "FILE_UPLOAD_FAILED": "The selected file could not be uploaded - try again",
131
+ "FILE_ERROR_UNKNOWN": "Unknown error"
117
132
  },
118
133
 
119
134
  "exit": {
@@ -139,6 +154,16 @@
139
154
  "bannerForce": "This is a preview of a [[previewMode]] form. Do not enter personal information.",
140
155
  "bannerText": "This is a preview of a [[previewMode]] form page you are editing.",
141
156
  "previousPages": "It depends on answers from earlier pages in the form. In the live version, users will need to complete those questions first."
157
+ },
158
+
159
+ "formUnavailable": {
160
+ "title": "Sorry, this form is unavailable",
161
+ "subHeading": "'[[formTitle]]' is no longer available.",
162
+ "contactDetailsHeading": "Contact details for [[organisationName]]",
163
+ "telephone": "Telephone",
164
+ "callCharges": "Find out about call charges",
165
+ "email": "Email",
166
+ "onlineContactForm": "Online contact form"
142
167
  }
143
168
  },
144
169
 
@@ -224,7 +249,8 @@
224
249
  "eastingDigits": "{{#label}} for [[fieldLabel]] must be between 1 and 6 digits",
225
250
  "northingDigits": "{{#label}} for [[fieldLabel]] must be between 1 and 7 digits",
226
251
  "eastingRange": "{{#label}} for [[fieldLabel]] must be between [[min]] and [[max]]",
227
- "northingRange": "{{#label}} for [[fieldLabel]] must be between [[min]] and [[max]]"
252
+ "northingRange": "{{#label}} for [[fieldLabel]] must be between [[min]] and [[max]]",
253
+ "wrongCountry": "Location must be within [[country]]"
228
254
  },
229
255
 
230
256
  "nationalGridField": {
@@ -291,7 +317,10 @@
291
317
  "uploadFailed": "There was a problem with your uploaded files. Re-upload them before submitting the form again.",
292
318
  "uploadedFilesHeading": "Uploaded files",
293
319
  "filesCount_one": "[[count]] file uploaded",
294
- "filesCount_other": "[[count]] files uploaded"
320
+ "filesCount_other": "[[count]] files uploaded",
321
+ "chooseFile": "Choose file",
322
+ "dropFile": "or drop file",
323
+ "noFileChosen": "No file chosen"
295
324
  },
296
325
 
297
326
  "characterCount": {
@@ -15,6 +15,7 @@ export type FormDefinitionTranslations = Record<string, {
15
15
  shortDescription: string;
16
16
  errorDescription: string;
17
17
  paymentDescription: string;
18
+ instructionText: string;
18
19
  }>>;
19
20
  sections: Record<string, Partial<Pick<Section, 'title'>>>;
20
21
  listItems: Record<string, Partial<Pick<Item, 'text' | 'hint'>>>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../../../../src/server/plugins/engine/i18n/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Item,\n type Page,\n type Section\n} from '@defra/forms-model'\n\n/**\n * Structure of the `translations` block in a form definition.\n * Also used to derive the valid property keys for the typed tX functions.\n */\nexport type FormDefinitionTranslations = Record<\n string,\n {\n pages: Record<string, Partial<{ title: string; repeatTitle: string }>>\n // ComponentDef is a discriminated union; Pick across the full union is not feasible.\n // The four keys below are the complete set of translatable component properties.\n components: Record<\n string,\n Partial<{\n title: string\n hint: string\n content: string\n shortDescription: string\n errorDescription: string\n paymentDescription: string\n }>\n >\n sections: Record<string, Partial<Pick<Section, 'title'>>>\n listItems: Record<string, Partial<Pick<Item, 'text' | 'hint'>>>\n form: Record<string, string>\n }\n>\n\ntype EntityTranslations = FormDefinitionTranslations[string]\n\n/**\n * Scoped translator returned by model.createTranslator(language).\n * t — plugin namespace (UI strings, buttons, errors, sub-field labels)\n * tForm — form namespace: form attributes, currently only form name\n * tPage — form namespace: page titles\n * tComponent — form namespace: component titles, hints, shortDescriptions\n * tSection — form namespace: section titles\n * tListItem — form namespace: list item text\n * language - current language\n */\nexport interface Translator {\n t: (key: string, opts?: Record<string, unknown>) => string\n tForm: (prop: string) => string\n tPage: (page: Page, prop: keyof EntityTranslations['pages'][string]) => string\n tComponent: (\n component: ComponentDef,\n prop: keyof EntityTranslations['components'][string]\n ) => string\n tSection: (\n section: Section,\n prop: keyof EntityTranslations['sections'][string]\n ) => string\n tListItem: (\n item: Item,\n prop: keyof EntityTranslations['listItems'][string]\n ) => string\n language: string\n}\n"],"mappings":"","ignoreList":[]}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../../../src/server/plugins/engine/i18n/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Item,\n type Page,\n type Section\n} from '@defra/forms-model'\n\n/**\n * Structure of the `translations` block in a form definition.\n * Also used to derive the valid property keys for the typed tX functions.\n */\nexport type FormDefinitionTranslations = Record<\n string,\n {\n pages: Record<string, Partial<{ title: string; repeatTitle: string }>>\n // ComponentDef is a discriminated union; Pick across the full union is not feasible.\n // The four keys below are the complete set of translatable component properties.\n components: Record<\n string,\n Partial<{\n title: string\n hint: string\n content: string\n shortDescription: string\n errorDescription: string\n paymentDescription: string\n instructionText: string\n }>\n >\n sections: Record<string, Partial<Pick<Section, 'title'>>>\n listItems: Record<string, Partial<Pick<Item, 'text' | 'hint'>>>\n form: Record<string, string>\n }\n>\n\ntype EntityTranslations = FormDefinitionTranslations[string]\n\n/**\n * Scoped translator returned by model.createTranslator(language).\n * t — plugin namespace (UI strings, buttons, errors, sub-field labels)\n * tForm — form namespace: form attributes, currently only form name\n * tPage — form namespace: page titles\n * tComponent — form namespace: component titles, hints, shortDescriptions\n * tSection — form namespace: section titles\n * tListItem — form namespace: list item text\n * language - current language\n */\nexport interface Translator {\n t: (key: string, opts?: Record<string, unknown>) => string\n tForm: (prop: string) => string\n tPage: (page: Page, prop: keyof EntityTranslations['pages'][string]) => string\n tComponent: (\n component: ComponentDef,\n prop: keyof EntityTranslations['components'][string]\n ) => string\n tSection: (\n section: Section,\n prop: keyof EntityTranslations['sections'][string]\n ) => string\n tListItem: (\n item: Item,\n prop: keyof EntityTranslations['listItems'][string]\n ) => string\n language: string\n}\n"],"mappings":"","ignoreList":[]}
@@ -1,8 +1,17 @@
1
- import { type FormMetadata, type FormMetadataContact } from '@defra/forms-model';
1
+ import { type FormDefinition, type FormMetadata, type FormMetadataContact } from '@defra/forms-model';
2
+ import { type Translator } from '../../../../server/plugins/engine/types/index.js';
2
3
  export interface UnavailableViewModel {
3
4
  pageTitle: string;
4
5
  formTitle: string;
5
6
  organisationName: string;
6
7
  contact?: FormMetadataContact;
8
+ context: {
9
+ translator: Translator;
10
+ };
11
+ language: string;
12
+ languages: {
13
+ code: string;
14
+ name: string;
15
+ }[];
7
16
  }
8
- export declare function unavailableViewModel(metadata: FormMetadata): UnavailableViewModel;
17
+ export declare function unavailableViewModel(metadata: FormMetadata, definition: FormDefinition | undefined, language: string): UnavailableViewModel;
@@ -1,9 +1,28 @@
1
- export function unavailableViewModel(metadata) {
1
+ import { createFormTranslator } from "../i18n/createFormTranslator.js";
2
+ import { getAvailableLanguages } from "../i18n/languages.js";
3
+ export function unavailableViewModel(metadata, definition, language) {
4
+ const translator = createFormTranslator(metadata, definition, language);
5
+ const {
6
+ t,
7
+ tForm
8
+ } = translator;
2
9
  return {
3
- pageTitle: 'Sorry, this form is unavailable',
4
- formTitle: metadata.title,
10
+ pageTitle: t('pages.formUnavailable.title'),
11
+ formTitle: tForm('title') || metadata.title,
5
12
  organisationName: metadata.organisation,
6
- contact: metadata.contact
13
+ contact: metadata.contact,
14
+ context: {
15
+ translator
16
+ },
17
+ language,
18
+ // Always get Welsh and English
19
+ languages: getAvailableLanguages({
20
+ metadata: {
21
+ translations: {
22
+ cy: {}
23
+ }
24
+ }
25
+ })
7
26
  };
8
27
  }
9
28
  //# sourceMappingURL=unavailable-view-model.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"unavailable-view-model.js","names":["unavailableViewModel","metadata","pageTitle","formTitle","title","organisationName","organisation","contact"],"sources":["../../../../../src/server/plugins/engine/models/unavailable-view-model.ts"],"sourcesContent":["import { type FormMetadata, type FormMetadataContact } from '@defra/forms-model'\n\nexport interface UnavailableViewModel {\n pageTitle: string\n formTitle: string\n organisationName: string\n contact?: FormMetadataContact\n}\n\nexport function unavailableViewModel(\n metadata: FormMetadata\n): UnavailableViewModel {\n return {\n pageTitle: 'Sorry, this form is unavailable',\n formTitle: metadata.title,\n organisationName: metadata.organisation,\n contact: metadata.contact\n }\n}\n"],"mappings":"AASA,OAAO,SAASA,oBAAoBA,CAClCC,QAAsB,EACA;EACtB,OAAO;IACLC,SAAS,EAAE,iCAAiC;IAC5CC,SAAS,EAAEF,QAAQ,CAACG,KAAK;IACzBC,gBAAgB,EAAEJ,QAAQ,CAACK,YAAY;IACvCC,OAAO,EAAEN,QAAQ,CAACM;EACpB,CAAC;AACH","ignoreList":[]}
1
+ {"version":3,"file":"unavailable-view-model.js","names":["createFormTranslator","getAvailableLanguages","unavailableViewModel","metadata","definition","language","translator","t","tForm","pageTitle","formTitle","title","organisationName","organisation","contact","context","languages","translations","cy"],"sources":["../../../../../src/server/plugins/engine/models/unavailable-view-model.ts"],"sourcesContent":["import {\n type FormDefinition,\n type FormMetadata,\n type FormMetadataContact\n} from '@defra/forms-model'\n\nimport { createFormTranslator } from '~/src/server/plugins/engine/i18n/createFormTranslator.js'\nimport { getAvailableLanguages } from '~/src/server/plugins/engine/i18n/languages.js'\nimport { type Translator } from '~/src/server/plugins/engine/types/index.js'\n\nexport interface UnavailableViewModel {\n pageTitle: string\n formTitle: string\n organisationName: string\n contact?: FormMetadataContact\n context: { translator: Translator }\n language: string\n languages: { code: string; name: string }[]\n}\n\nexport function unavailableViewModel(\n metadata: FormMetadata,\n definition: FormDefinition | undefined,\n language: string\n): UnavailableViewModel {\n const translator = createFormTranslator(metadata, definition, language)\n const { t, tForm } = translator\n return {\n pageTitle: t('pages.formUnavailable.title'),\n formTitle: tForm('title') || metadata.title,\n organisationName: metadata.organisation,\n contact: metadata.contact,\n context: { translator },\n language,\n // Always get Welsh and English\n languages: getAvailableLanguages({\n metadata: { translations: { cy: {} } }\n } as unknown as FormDefinition)\n }\n}\n"],"mappings":"AAMA,SAASA,oBAAoB;AAC7B,SAASC,qBAAqB;AAa9B,OAAO,SAASC,oBAAoBA,CAClCC,QAAsB,EACtBC,UAAsC,EACtCC,QAAgB,EACM;EACtB,MAAMC,UAAU,GAAGN,oBAAoB,CAACG,QAAQ,EAAEC,UAAU,EAAEC,QAAQ,CAAC;EACvE,MAAM;IAAEE,CAAC;IAAEC;EAAM,CAAC,GAAGF,UAAU;EAC/B,OAAO;IACLG,SAAS,EAAEF,CAAC,CAAC,6BAA6B,CAAC;IAC3CG,SAAS,EAAEF,KAAK,CAAC,OAAO,CAAC,IAAIL,QAAQ,CAACQ,KAAK;IAC3CC,gBAAgB,EAAET,QAAQ,CAACU,YAAY;IACvCC,OAAO,EAAEX,QAAQ,CAACW,OAAO;IACzBC,OAAO,EAAE;MAAET;IAAW,CAAC;IACvBD,QAAQ;IACR;IACAW,SAAS,EAAEf,qBAAqB,CAAC;MAC/BE,QAAQ,EAAE;QAAEc,YAAY,EAAE;UAAEC,EAAE,EAAE,CAAC;QAAE;MAAE;IACvC,CAA8B;EAChC,CAAC;AACH","ignoreList":[]}
@@ -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
 
@@ -241,6 +242,13 @@ export class FileUploadPageController extends QuestionPageController {
241
242
  },
242
243
  uploadingLabel: t('components.fileUploadField.uploading')
243
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
+ };
244
252
  const index = components.indexOf(formComponent);
245
253
  const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl);
246
254
  return {
@@ -362,6 +370,10 @@ export class FileUploadPageController extends QuestionPageController {
362
370
  const rawFile = validatedStatus.form.file;
363
371
  const uploadedFiles = Array.isArray(rawFile) ? rawFile : [rawFile];
364
372
  const allErrors = [];
373
+ const translator = this.getTranslator(request);
374
+ const {
375
+ t
376
+ } = translator;
365
377
  for (const file of uploadedFiles) {
366
378
  if (file.fileStatus === FileStatus.complete) {
367
379
  const perFileState = {
@@ -380,7 +392,8 @@ export class FileUploadPageController extends QuestionPageController {
380
392
  fileUpload
381
393
  } = this;
382
394
  const name = fileUpload.name;
383
- 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);
384
397
  allErrors.push({
385
398
  path: [name],
386
399
  href: `#${name}`,
@@ -407,6 +420,26 @@ export class FileUploadPageController extends QuestionPageController {
407
420
  }
408
421
  }
409
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
+
410
443
  /**
411
444
  * Checks the payload for a file getting removed
412
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","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","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 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\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 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":"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","ignoreList":[]}
@@ -9,5 +9,5 @@ export declare const opts: ReferenceOptions;
9
9
  export declare const messageTemplate: ValidationMessages;
10
10
  export declare const messages: LanguageMessagesExt;
11
11
  export declare const messagesPre: LanguageMessages;
12
- export declare function buildLanguageMessages(t: (key: string) => string): LanguageMessages;
12
+ export declare function buildLanguageMessages(t: (key: string, options?: Record<string, unknown>) => string): LanguageMessages;
13
13
  export declare const validationOptions: ValidationOptions;
@@ -14,7 +14,7 @@ export const opts = {
14
14
  * Components that have not yet been migrated to use model.validationMessages still
15
15
  * import messageTemplate directly and get the English version.
16
16
  */
17
- export const messageTemplate = buildValidationMessages(key => t(key, 'en-GB'));
17
+ export const messageTemplate = buildValidationMessages((key, options) => t(key, 'en-GB', options));
18
18
  export const messages = {
19
19
  'string.base': messageTemplate.required,
20
20
  'string.min': messageTemplate.min,