@defra/forms-engine-plugin 4.0.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/.public/stylesheets/application.min.css +3 -3
  2. package/.public/stylesheets/application.min.css.map +1 -1
  3. package/.server/client/stylesheets/application.scss +14 -0
  4. package/.server/config/index.d.ts +1 -0
  5. package/.server/config/index.js +7 -0
  6. package/.server/config/index.js.map +1 -1
  7. package/.server/index.js +6 -2
  8. package/.server/index.js.map +1 -1
  9. package/.server/server/constants.d.ts +2 -0
  10. package/.server/server/constants.js +2 -0
  11. package/.server/server/constants.js.map +1 -1
  12. package/.server/server/forms/components.json +7 -0
  13. package/.server/server/forms/register-as-a-unicorn-breeder.yaml +18 -2
  14. package/.server/server/plugins/engine/components/UkAddressField.d.ts +15 -9
  15. package/.server/server/plugins/engine/components/UkAddressField.js +67 -6
  16. package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
  17. package/.server/server/plugins/engine/configureEnginePlugin.d.ts +1 -1
  18. package/.server/server/plugins/engine/configureEnginePlugin.js +6 -3
  19. package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
  20. package/.server/server/plugins/engine/models/FormModel.d.ts +2 -0
  21. package/.server/server/plugins/engine/models/FormModel.js +3 -1
  22. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  23. package/.server/server/plugins/engine/options.js +2 -1
  24. package/.server/server/plugins/engine/options.js.map +1 -1
  25. package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +1 -0
  26. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +46 -3
  27. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  28. package/.server/server/plugins/engine/plugin.js +13 -1
  29. package/.server/server/plugins/engine/plugin.js.map +1 -1
  30. package/.server/server/plugins/engine/routes/index.js +41 -3
  31. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  32. package/.server/server/plugins/engine/types.d.ts +19 -1
  33. package/.server/server/plugins/engine/types.js.map +1 -1
  34. package/.server/server/plugins/engine/validationHelpers.d.ts +15 -0
  35. package/.server/server/plugins/engine/validationHelpers.js +29 -0
  36. package/.server/server/plugins/engine/validationHelpers.js.map +1 -0
  37. package/.server/server/plugins/engine/views/components/ukaddressfield.html +50 -6
  38. package/.server/server/plugins/engine/vision.js +3 -1
  39. package/.server/server/plugins/engine/vision.js.map +1 -1
  40. package/.server/server/plugins/postcode-lookup/index.d.ts +8 -0
  41. package/.server/server/plugins/postcode-lookup/index.js +21 -0
  42. package/.server/server/plugins/postcode-lookup/index.js.map +1 -0
  43. package/.server/server/plugins/postcode-lookup/models/index.d.ts +255 -0
  44. package/.server/server/plugins/postcode-lookup/models/index.js +517 -0
  45. package/.server/server/plugins/postcode-lookup/models/index.js.map +1 -0
  46. package/.server/server/plugins/postcode-lookup/routes/index.d.ts +19 -0
  47. package/.server/server/plugins/postcode-lookup/routes/index.js +267 -0
  48. package/.server/server/plugins/postcode-lookup/routes/index.js.map +1 -0
  49. package/.server/server/plugins/postcode-lookup/service.d.ts +26 -0
  50. package/.server/server/plugins/postcode-lookup/service.js +148 -0
  51. package/.server/server/plugins/postcode-lookup/service.js.map +1 -0
  52. package/.server/server/plugins/postcode-lookup/service.test.js +144 -0
  53. package/.server/server/plugins/postcode-lookup/service.test.js.map +1 -0
  54. package/.server/server/plugins/postcode-lookup/test/__stubs__/postcode.d.ts +282 -0
  55. package/.server/server/plugins/postcode-lookup/test/__stubs__/postcode.js +370 -0
  56. package/.server/server/plugins/postcode-lookup/test/__stubs__/postcode.js.map +1 -0
  57. package/.server/server/plugins/postcode-lookup/test/__stubs__/query.d.ts +131 -0
  58. package/.server/server/plugins/postcode-lookup/test/__stubs__/query.js +195 -0
  59. package/.server/server/plugins/postcode-lookup/test/__stubs__/query.js.map +1 -0
  60. package/.server/server/plugins/postcode-lookup/test/__stubs__/uprn.d.ts +51 -0
  61. package/.server/server/plugins/postcode-lookup/test/__stubs__/uprn.js +52 -0
  62. package/.server/server/plugins/postcode-lookup/test/__stubs__/uprn.js.map +1 -0
  63. package/.server/server/plugins/postcode-lookup/types.d.ts +204 -0
  64. package/.server/server/plugins/postcode-lookup/types.js +144 -0
  65. package/.server/server/plugins/postcode-lookup/types.js.map +1 -0
  66. package/.server/server/plugins/postcode-lookup/views/postcode-lookup-details.html +83 -0
  67. package/.server/server/routes/types.d.ts +6 -1
  68. package/.server/server/routes/types.js +6 -0
  69. package/.server/server/routes/types.js.map +1 -1
  70. package/.server/server/schemas/index.js +1 -1
  71. package/.server/server/schemas/index.js.map +1 -1
  72. package/.server/server/types.d.ts +1 -0
  73. package/.server/server/types.js.map +1 -1
  74. package/package.json +2 -2
  75. package/src/client/stylesheets/application.scss +14 -0
  76. package/src/config/index.ts +9 -1
  77. package/src/index.ts +5 -4
  78. package/src/server/constants.js +2 -0
  79. package/src/server/forms/components.json +7 -0
  80. package/src/server/forms/register-as-a-unicorn-breeder.yaml +18 -2
  81. package/src/server/plugins/engine/components/UkAddressField.test.ts +50 -27
  82. package/src/server/plugins/engine/components/UkAddressField.ts +91 -8
  83. package/src/server/plugins/engine/configureEnginePlugin.ts +5 -3
  84. package/src/server/plugins/engine/models/FormModel.ts +10 -2
  85. package/src/server/plugins/engine/options.js +2 -1
  86. package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +1 -0
  87. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +69 -1
  88. package/src/server/plugins/engine/plugin.ts +13 -1
  89. package/src/server/plugins/engine/routes/index.test.ts +1 -0
  90. package/src/server/plugins/engine/routes/index.ts +71 -3
  91. package/src/server/plugins/engine/types.ts +21 -1
  92. package/src/server/plugins/engine/validationHelpers.ts +48 -0
  93. package/src/server/plugins/engine/views/components/ukaddressfield.html +50 -6
  94. package/src/server/plugins/engine/vision.ts +6 -0
  95. package/src/server/plugins/postcode-lookup/index.js +21 -0
  96. package/src/server/plugins/postcode-lookup/models/index.js +549 -0
  97. package/src/server/plugins/postcode-lookup/routes/index.js +258 -0
  98. package/src/server/plugins/postcode-lookup/service.js +188 -0
  99. package/src/server/plugins/postcode-lookup/service.test.js +177 -0
  100. package/src/server/plugins/postcode-lookup/test/__stubs__/postcode.js +382 -0
  101. package/src/server/plugins/postcode-lookup/test/__stubs__/query.js +200 -0
  102. package/src/server/plugins/postcode-lookup/test/__stubs__/uprn.js +53 -0
  103. package/src/server/plugins/postcode-lookup/types.js +143 -0
  104. package/src/server/plugins/postcode-lookup/views/postcode-lookup-details.html +83 -0
  105. package/src/server/postcode-lookup.test.ts +64 -0
  106. package/src/server/routes/types.ts +7 -1
  107. package/src/server/schemas/index.ts +5 -7
  108. package/src/server/types.ts +1 -0
@@ -0,0 +1,549 @@
1
+ import Joi from 'joi'
2
+
3
+ import * as service from '~/src/server/plugins/postcode-lookup/service.js'
4
+ import { crumbSchema } from '~/src/server/schemas/index.js'
5
+
6
+ // Field names/ids
7
+ const postcodeQueryFieldName = 'postcodeQuery'
8
+ const buildingNameQueryFieldName = 'buildingNameQuery'
9
+ const uprnFieldName = 'uprn'
10
+
11
+ const line1FieldName = 'addressLine1'
12
+ const line2FieldName = 'addressLine2'
13
+ const townFieldName = 'town'
14
+ const countyFieldName = 'county'
15
+ const postcodeFieldName = 'postcode'
16
+
17
+ const selectLabelText = 'Select an address'
18
+
19
+ const GOVUK_MARGIN_RIGHT_1 = 'govuk-!-margin-right-1'
20
+
21
+ export const steps = {
22
+ // Step 1: Postcode/building name input
23
+ details: 'details',
24
+ // Step 2: Select address
25
+ select: 'select',
26
+ // Step 3: Manual address
27
+ manual: 'manual'
28
+ }
29
+
30
+ export const JOURNEY_BASE_URL = '/postcode-lookup'
31
+
32
+ /**
33
+ * Build form errors
34
+ * @param {Error} [err]
35
+ */
36
+ function buildErrors(err) {
37
+ const hasErrors = Joi.isError(err) && err.details.length > 0
38
+
39
+ if (!hasErrors) {
40
+ return {}
41
+ }
42
+
43
+ /**
44
+ * Get error by path
45
+ * @param {string} fieldName
46
+ */
47
+ const getError = (fieldName) => {
48
+ return err.details.find((item) => item.path[0] === fieldName)
49
+ }
50
+
51
+ const postcodeQueryError = getError(postcodeQueryFieldName)
52
+ const buildingNameQueryError = getError(buildingNameQueryFieldName)
53
+ const uprnError = getError(uprnFieldName)
54
+ const line1Error = getError(line1FieldName)
55
+ const line2Error = getError(line2FieldName)
56
+ const townError = getError(townFieldName)
57
+ const countyError = getError(countyFieldName)
58
+ const postcodeError = getError(postcodeFieldName)
59
+
60
+ /**
61
+ * @type {{ text: string, href: string }[]}
62
+ */
63
+ const errors = []
64
+
65
+ /**
66
+ * Push error
67
+ * @param {string} fieldName - the field name
68
+ * @param {ValidationErrorItem} [item] - the joi validation error
69
+ */
70
+ const pushError = (fieldName, item) => {
71
+ if (item) {
72
+ errors.push({
73
+ text: item.message,
74
+ href: `#${fieldName}`
75
+ })
76
+ }
77
+ }
78
+
79
+ pushError(postcodeQueryFieldName, postcodeQueryError)
80
+ pushError(buildingNameQueryFieldName, buildingNameQueryError)
81
+ pushError(uprnFieldName, uprnError)
82
+ pushError(line1FieldName, line1Error)
83
+ pushError(line2FieldName, line2Error)
84
+ pushError(townFieldName, townError)
85
+ pushError(countyFieldName, countyError)
86
+ pushError(postcodeFieldName, postcodeError)
87
+
88
+ return {
89
+ errors,
90
+ postcodeQueryError,
91
+ buildingNameQueryError,
92
+ uprnError,
93
+ line1Error,
94
+ line2Error,
95
+ townError,
96
+ countyError,
97
+ postcodeError
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Search ordnance survey for addresses
103
+ * @param {string} postcodeQuery
104
+ * @param {string} buildingNameQuery
105
+ * @param {string} apiKey
106
+ */
107
+ async function getAddresses(postcodeQuery, buildingNameQuery, apiKey) {
108
+ const addresses = await service.search(
109
+ postcodeQuery,
110
+ buildingNameQuery,
111
+ apiKey
112
+ )
113
+ const addressCount = addresses.length
114
+ const singleAddress = addressCount === 1 ? addresses.at(0) : undefined
115
+ const hasAddresses = addressCount > 0
116
+ const hasMultipleAddresses = addressCount > 1
117
+
118
+ return {
119
+ hasAddresses,
120
+ hasMultipleAddresses,
121
+ singleAddress,
122
+ addresses,
123
+ addressCount
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Get the details view fields
129
+ * @param {PostcodeLookupDetailsData | undefined} details
130
+ * @param {OptionalValidationErrorItem} postcodeQueryError
131
+ * @param {OptionalValidationErrorItem} buildingNameQueryError
132
+ */
133
+ function getDetailsFields(details, postcodeQueryError, buildingNameQueryError) {
134
+ return {
135
+ [postcodeQueryFieldName]: {
136
+ id: postcodeQueryFieldName,
137
+ name: postcodeQueryFieldName,
138
+ label: {
139
+ text: 'Postcode'
140
+ },
141
+ hint: {
142
+ text: 'For example, AA3 1AB'
143
+ },
144
+ value: details?.postcodeQuery,
145
+ errorMessage: postcodeQueryError && { text: postcodeQueryError.message }
146
+ },
147
+ [buildingNameQueryFieldName]: {
148
+ id: buildingNameQueryFieldName,
149
+ name: buildingNameQueryFieldName,
150
+ label: {
151
+ text: 'Building name or number (optional)'
152
+ },
153
+ hint: {
154
+ text: 'For example, 15 or Prospect Cottage'
155
+ },
156
+ value: details?.buildingNameQuery,
157
+ errorMessage: buildingNameQueryError && {
158
+ text: buildingNameQueryError.message
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Get the select view fields
166
+ * @param {PostcodeLookupDetailsData} details
167
+ * @param {boolean} hasMultipleAddresses
168
+ * @param {Address | undefined} singleAddress
169
+ * @param {PostcodeLookupSelectPayload | undefined} payload
170
+ * @param {OptionalValidationErrorItem} uprnError
171
+ * @param {Address[]} addresses
172
+ */
173
+ function getSelectFields(
174
+ details,
175
+ hasMultipleAddresses,
176
+ singleAddress,
177
+ payload,
178
+ uprnError,
179
+ addresses
180
+ ) {
181
+ return {
182
+ [postcodeQueryFieldName]: {
183
+ id: postcodeQueryFieldName,
184
+ name: postcodeQueryFieldName,
185
+ type: 'hidden',
186
+ value: details.postcodeQuery
187
+ },
188
+ [buildingNameQueryFieldName]: {
189
+ id: buildingNameQueryFieldName,
190
+ name: buildingNameQueryFieldName,
191
+ type: 'hidden',
192
+ value: details.buildingNameQuery
193
+ },
194
+ [uprnFieldName]: {
195
+ id: uprnFieldName,
196
+ name: uprnFieldName,
197
+ label: hasMultipleAddresses
198
+ ? {
199
+ text: selectLabelText
200
+ }
201
+ : undefined,
202
+ value: singleAddress ? singleAddress.uprn : payload?.uprn,
203
+ errorMessage: uprnError && { text: uprnError.message },
204
+ items: hasMultipleAddresses
205
+ ? [{ text: selectLabelText, value: '' }].concat(
206
+ addresses.map((item) => ({
207
+ text: item.formatted,
208
+ value: item.uprn
209
+ }))
210
+ )
211
+ : undefined,
212
+ type: singleAddress ? 'hidden' : undefined
213
+ }
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Get the manual view fields
219
+ * @param {PostcodeLookupManualPayload | undefined} payload
220
+ * @param {OptionalValidationErrorItem} line1Error
221
+ * @param {OptionalValidationErrorItem} line2Error
222
+ * @param {OptionalValidationErrorItem} townError
223
+ * @param {OptionalValidationErrorItem} countyError
224
+ * @param {OptionalValidationErrorItem} postcodeError
225
+ */
226
+ function getManualFields(
227
+ payload,
228
+ line1Error,
229
+ line2Error,
230
+ townError,
231
+ countyError,
232
+ postcodeError
233
+ ) {
234
+ return {
235
+ [line1FieldName]: {
236
+ id: line1FieldName,
237
+ name: line1FieldName,
238
+ label: {
239
+ text: 'Address line 1'
240
+ },
241
+ value: payload?.addressLine1,
242
+ errorMessage: line1Error && { text: line1Error.message }
243
+ },
244
+ [line2FieldName]: {
245
+ id: line2FieldName,
246
+ name: line2FieldName,
247
+ label: {
248
+ text: 'Address line 2 (optional)'
249
+ },
250
+ value: payload?.addressLine2,
251
+ errorMessage: line2Error && { text: line2Error.message }
252
+ },
253
+ [townFieldName]: {
254
+ id: townFieldName,
255
+ name: townFieldName,
256
+ label: {
257
+ text: 'Town or city'
258
+ },
259
+ classes: 'govuk-!-width-two-thirds',
260
+ value: payload?.town,
261
+ errorMessage: townError && { text: townError.message }
262
+ },
263
+ [countyFieldName]: {
264
+ id: countyFieldName,
265
+ name: countyFieldName,
266
+ label: {
267
+ text: 'County (optional)'
268
+ },
269
+ value: payload?.county,
270
+ errorMessage: countyError && { text: countyError.message }
271
+ },
272
+ [postcodeFieldName]: {
273
+ id: postcodeFieldName,
274
+ name: postcodeFieldName,
275
+ label: {
276
+ text: 'Postcode'
277
+ },
278
+ classes: 'govuk-input--width-10',
279
+ value: payload?.postcode,
280
+ errorMessage: postcodeError && { text: postcodeError.message }
281
+ }
282
+ }
283
+ }
284
+
285
+ export const stepSchema = Joi.string()
286
+ .valid(...Object.keys(steps))
287
+ .required()
288
+
289
+ const sharedPayloadSchemaKeys = {
290
+ crumb: crumbSchema,
291
+ step: stepSchema
292
+ }
293
+
294
+ /**
295
+ * Postcode lookup details form payload schema
296
+ * @type {ObjectSchema<PostcodeLookupDetailsPayload>}
297
+ */
298
+ export const detailsPayloadSchema = Joi.object()
299
+ .keys({
300
+ ...sharedPayloadSchemaKeys,
301
+ [postcodeQueryFieldName]: Joi.string()
302
+ .pattern(/^[a-zA-Z]{1,2}\d[a-zA-Z\d]?\s?\d[a-zA-Z]{2}$/)
303
+ .trim()
304
+ .required()
305
+ .messages({
306
+ 'string.pattern.base':
307
+ 'Enter a valid postcode or enter an address manually',
308
+ '*': 'Enter a postcode'
309
+ }),
310
+ [buildingNameQueryFieldName]: Joi.string()
311
+ .trim()
312
+ .required()
313
+ .allow('')
314
+ .trim()
315
+ })
316
+ .required()
317
+
318
+ /**
319
+ * Postcode lookup select form payload schema
320
+ * @type {ObjectSchema<PostcodeLookupSelectPayload>}
321
+ */
322
+ export const selectPayloadSchema = Joi.object()
323
+ .keys({
324
+ ...sharedPayloadSchemaKeys,
325
+ [uprnFieldName]: Joi.string().required().messages({
326
+ '*': selectLabelText
327
+ })
328
+ })
329
+ .required()
330
+
331
+ /**
332
+ * Postcode lookup manual form payload schema
333
+ * @type {ObjectSchema<PostcodeLookupManualPayload>}
334
+ */
335
+ export const manualPayloadSchema = Joi.object()
336
+ .keys({
337
+ ...sharedPayloadSchemaKeys,
338
+ [line1FieldName]: Joi.string().trim().required().messages({
339
+ '*': 'Enter address line 1'
340
+ }),
341
+ [line2FieldName]: Joi.string().trim().allow('').required(),
342
+ [townFieldName]: Joi.string().trim().required().messages({
343
+ '*': 'Enter town or city'
344
+ }),
345
+ [countyFieldName]: Joi.string().trim().allow('').required(),
346
+ [postcodeFieldName]: Joi.string().trim().required().messages({
347
+ '*': 'Enter postcode'
348
+ })
349
+ })
350
+ .required()
351
+
352
+ /**
353
+ * Get the postcode lookup href
354
+ * @param {string} [step] - the postcode lookup step
355
+ */
356
+ function getHref(step) {
357
+ const query = step ? `?step=${step}` : ''
358
+
359
+ return `${JOURNEY_BASE_URL}${query}`
360
+ }
361
+
362
+ /**
363
+ * The postcode lookup details form view model
364
+ * @param {PostcodeLookupSessionData} data
365
+ * @param {PostcodeLookupDetailsData} [payload]
366
+ * @param {Error} [err]
367
+ */
368
+ export function detailsViewModel(data, payload, err) {
369
+ const { componentTitle: pageTitle, formName, sourceUrl } = data.initial
370
+
371
+ const backLink = {
372
+ href: sourceUrl
373
+ }
374
+
375
+ const { errors, postcodeQueryError, buildingNameQueryError } =
376
+ buildErrors(err)
377
+
378
+ // Model fields
379
+ const fields = getDetailsFields(
380
+ payload ?? data.details,
381
+ postcodeQueryError,
382
+ buildingNameQueryError
383
+ )
384
+
385
+ // Model buttons
386
+ const continueButton = {
387
+ text: 'Find address',
388
+ classes: GOVUK_MARGIN_RIGHT_1
389
+ }
390
+ const manualLink = {
391
+ text: 'enter address manually',
392
+ href: getHref(steps.manual)
393
+ }
394
+
395
+ return {
396
+ step: steps.details,
397
+ showTitle: true,
398
+ name: formName,
399
+ serviceUrl: sourceUrl,
400
+ pageTitle,
401
+ backLink,
402
+ errors,
403
+ fields,
404
+ buttons: { continueButton, manualLink }
405
+ }
406
+ }
407
+
408
+ /**
409
+ * The postcode lookup select form view model
410
+ * @param {{ session: PostcodeLookupSessionData, apiKey: string }} data
411
+ * @param {PostcodeLookupSelectPayload} [payload]
412
+ * @param {Error} [err]
413
+ */
414
+ export async function selectViewModel(data, payload, err) {
415
+ const { session, apiKey } = data
416
+ const { details, initial } = session
417
+ const { postcodeQuery, buildingNameQuery } = details
418
+
419
+ // Search for addresses
420
+ const {
421
+ hasAddresses,
422
+ hasMultipleAddresses,
423
+ singleAddress,
424
+ addresses,
425
+ addressCount
426
+ } = await getAddresses(postcodeQuery, buildingNameQuery, apiKey)
427
+
428
+ const title = hasAddresses ? initial.componentTitle : 'No address found'
429
+ const formPath = initial.sourceUrl
430
+ const href = getHref()
431
+
432
+ const backLink = { href }
433
+
434
+ const { errors, uprnError } = buildErrors(err)
435
+
436
+ // Model fields
437
+ const fields = getSelectFields(
438
+ details,
439
+ hasMultipleAddresses,
440
+ singleAddress,
441
+ payload,
442
+ uprnError,
443
+ addresses
444
+ )
445
+
446
+ const searchAgainLink = {
447
+ text: 'Search again',
448
+ href
449
+ }
450
+
451
+ // Model buttons
452
+ const continueButton = {
453
+ href: hasAddresses ? undefined : href,
454
+ text: hasAddresses ? 'Use this address' : 'Search again',
455
+ classes: GOVUK_MARGIN_RIGHT_1
456
+ }
457
+ const manualLink = {
458
+ text: 'enter address manually',
459
+ href: `${href}?step=${steps.manual}`
460
+ }
461
+
462
+ return {
463
+ step: steps.select,
464
+ showTitle: true,
465
+ name: title,
466
+ serviceUrl: formPath,
467
+ pageTitle: title,
468
+ backLink,
469
+ errors,
470
+ searchAgainLink,
471
+ fields,
472
+ details,
473
+ addressCount,
474
+ singleAddress,
475
+ hasAddresses,
476
+ hasMultipleAddresses,
477
+ buttons: { continueButton, manualLink }
478
+ }
479
+ }
480
+
481
+ /**
482
+ * The postcode lookup manual form view model
483
+ * @param {PostcodeLookupSessionData} data
484
+ * @param {PostcodeLookupManualPayload} [payload]
485
+ * @param {Error} [err]
486
+ */
487
+ export function manualViewModel(data, payload, err) {
488
+ const { componentTitle, sourceUrl, componentHint } = data.initial
489
+ const formPath = sourceUrl
490
+ const href = getHref()
491
+
492
+ const backLink = {
493
+ href
494
+ }
495
+
496
+ const {
497
+ errors,
498
+ line1Error,
499
+ line2Error,
500
+ townError,
501
+ countyError,
502
+ postcodeError
503
+ } = buildErrors(err)
504
+
505
+ // Model hint
506
+ const hint = componentHint && {
507
+ text: componentHint
508
+ }
509
+
510
+ // Model fields
511
+ const fields = getManualFields(
512
+ payload,
513
+ line1Error,
514
+ line2Error,
515
+ townError,
516
+ countyError,
517
+ postcodeError
518
+ )
519
+
520
+ // Model buttons
521
+ const continueButton = {
522
+ text: 'Use this address',
523
+ classes: GOVUK_MARGIN_RIGHT_1
524
+ }
525
+ const detailsLink = {
526
+ text: 'find an address instead',
527
+ href
528
+ }
529
+
530
+ return {
531
+ step: steps.manual,
532
+ showTitle: true,
533
+ name: componentTitle,
534
+ serviceUrl: formPath,
535
+ pageTitle: componentTitle,
536
+ backLink,
537
+ errors,
538
+ hint,
539
+ fields,
540
+ buttons: { continueButton, detailsLink }
541
+ }
542
+ }
543
+
544
+ /** @typedef { ValidationErrorItem | undefined } OptionalValidationErrorItem */
545
+
546
+ /**
547
+ * @import { ObjectSchema, ValidationErrorItem } from 'joi'
548
+ * @import { Address, PostcodeLookupDetailsData, PostcodeLookupDetailsPayload, PostcodeLookupManualPayload, PostcodeLookupSelectPayload, PostcodeLookupSessionData } from '~/src/server/plugins/postcode-lookup/types.js'
549
+ */