@defra/forms-engine-plugin 3.0.9 → 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 (127) 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 +15 -2
  29. package/.server/server/plugins/engine/plugin.js.map +1 -1
  30. package/.server/server/plugins/engine/routes/index.d.ts +2 -2
  31. package/.server/server/plugins/engine/routes/index.js +49 -9
  32. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  33. package/.server/server/plugins/engine/routes/questions.d.ts +4 -4
  34. package/.server/server/plugins/engine/routes/questions.js +10 -10
  35. package/.server/server/plugins/engine/routes/questions.js.map +1 -1
  36. package/.server/server/plugins/engine/routes/repeaters/item-delete.d.ts +2 -1
  37. package/.server/server/plugins/engine/routes/repeaters/item-delete.js +31 -27
  38. package/.server/server/plugins/engine/routes/repeaters/item-delete.js.map +1 -1
  39. package/.server/server/plugins/engine/routes/repeaters/summary.d.ts +2 -1
  40. package/.server/server/plugins/engine/routes/repeaters/summary.js +31 -27
  41. package/.server/server/plugins/engine/routes/repeaters/summary.js.map +1 -1
  42. package/.server/server/plugins/engine/types.d.ts +21 -3
  43. package/.server/server/plugins/engine/types.js.map +1 -1
  44. package/.server/server/plugins/engine/validationHelpers.d.ts +15 -0
  45. package/.server/server/plugins/engine/validationHelpers.js +29 -0
  46. package/.server/server/plugins/engine/validationHelpers.js.map +1 -0
  47. package/.server/server/plugins/engine/views/components/ukaddressfield.html +50 -6
  48. package/.server/server/plugins/engine/vision.js +3 -1
  49. package/.server/server/plugins/engine/vision.js.map +1 -1
  50. package/.server/server/plugins/postcode-lookup/index.d.ts +8 -0
  51. package/.server/server/plugins/postcode-lookup/index.js +21 -0
  52. package/.server/server/plugins/postcode-lookup/index.js.map +1 -0
  53. package/.server/server/plugins/postcode-lookup/models/index.d.ts +255 -0
  54. package/.server/server/plugins/postcode-lookup/models/index.js +517 -0
  55. package/.server/server/plugins/postcode-lookup/models/index.js.map +1 -0
  56. package/.server/server/plugins/postcode-lookup/routes/index.d.ts +19 -0
  57. package/.server/server/plugins/postcode-lookup/routes/index.js +267 -0
  58. package/.server/server/plugins/postcode-lookup/routes/index.js.map +1 -0
  59. package/.server/server/plugins/postcode-lookup/service.d.ts +26 -0
  60. package/.server/server/plugins/postcode-lookup/service.js +148 -0
  61. package/.server/server/plugins/postcode-lookup/service.js.map +1 -0
  62. package/.server/server/plugins/postcode-lookup/service.test.js +144 -0
  63. package/.server/server/plugins/postcode-lookup/service.test.js.map +1 -0
  64. package/.server/server/plugins/postcode-lookup/test/__stubs__/postcode.d.ts +282 -0
  65. package/.server/server/plugins/postcode-lookup/test/__stubs__/postcode.js +370 -0
  66. package/.server/server/plugins/postcode-lookup/test/__stubs__/postcode.js.map +1 -0
  67. package/.server/server/plugins/postcode-lookup/test/__stubs__/query.d.ts +131 -0
  68. package/.server/server/plugins/postcode-lookup/test/__stubs__/query.js +195 -0
  69. package/.server/server/plugins/postcode-lookup/test/__stubs__/query.js.map +1 -0
  70. package/.server/server/plugins/postcode-lookup/test/__stubs__/uprn.d.ts +51 -0
  71. package/.server/server/plugins/postcode-lookup/test/__stubs__/uprn.js +52 -0
  72. package/.server/server/plugins/postcode-lookup/test/__stubs__/uprn.js.map +1 -0
  73. package/.server/server/plugins/postcode-lookup/types.d.ts +204 -0
  74. package/.server/server/plugins/postcode-lookup/types.js +144 -0
  75. package/.server/server/plugins/postcode-lookup/types.js.map +1 -0
  76. package/.server/server/plugins/postcode-lookup/views/postcode-lookup-details.html +83 -0
  77. package/.server/server/routes/types.d.ts +7 -2
  78. package/.server/server/routes/types.js +6 -0
  79. package/.server/server/routes/types.js.map +1 -1
  80. package/.server/server/schemas/index.js +1 -1
  81. package/.server/server/schemas/index.js.map +1 -1
  82. package/.server/server/types.d.ts +1 -0
  83. package/.server/server/types.js.map +1 -1
  84. package/package.json +2 -2
  85. package/src/client/stylesheets/application.scss +14 -0
  86. package/src/config/index.ts +9 -1
  87. package/src/index.ts +5 -4
  88. package/src/server/constants.js +2 -0
  89. package/src/server/forms/components.json +7 -0
  90. package/src/server/forms/register-as-a-unicorn-breeder.yaml +18 -2
  91. package/src/server/plugins/engine/components/UkAddressField.test.ts +50 -27
  92. package/src/server/plugins/engine/components/UkAddressField.ts +91 -8
  93. package/src/server/plugins/engine/configureEnginePlugin.ts +5 -3
  94. package/src/server/plugins/engine/helpers.test.ts +2 -1
  95. package/src/server/plugins/engine/models/FormModel.ts +10 -2
  96. package/src/server/plugins/engine/options.js +2 -1
  97. package/src/server/plugins/engine/pageControllers/PageController.test.ts +2 -1
  98. package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +9 -4
  99. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +69 -1
  100. package/src/server/plugins/engine/pageControllers/SummaryPageController.test.ts +2 -1
  101. package/src/server/plugins/engine/plugin.ts +22 -4
  102. package/src/server/plugins/engine/routes/index.test.ts +317 -0
  103. package/src/server/plugins/engine/routes/index.ts +81 -8
  104. package/src/server/plugins/engine/routes/questions.test.ts +126 -15
  105. package/src/server/plugins/engine/routes/questions.ts +71 -57
  106. package/src/server/plugins/engine/routes/repeaters/item-delete.test.ts +83 -0
  107. package/src/server/plugins/engine/routes/repeaters/item-delete.ts +39 -33
  108. package/src/server/plugins/engine/routes/repeaters/summary.test.ts +75 -0
  109. package/src/server/plugins/engine/routes/repeaters/summary.ts +28 -22
  110. package/src/server/plugins/engine/types.ts +27 -8
  111. package/src/server/plugins/engine/validationHelpers.ts +48 -0
  112. package/src/server/plugins/engine/views/components/ukaddressfield.html +50 -6
  113. package/src/server/plugins/engine/vision.ts +6 -0
  114. package/src/server/plugins/postcode-lookup/index.js +21 -0
  115. package/src/server/plugins/postcode-lookup/models/index.js +549 -0
  116. package/src/server/plugins/postcode-lookup/routes/index.js +258 -0
  117. package/src/server/plugins/postcode-lookup/service.js +188 -0
  118. package/src/server/plugins/postcode-lookup/service.test.js +177 -0
  119. package/src/server/plugins/postcode-lookup/test/__stubs__/postcode.js +382 -0
  120. package/src/server/plugins/postcode-lookup/test/__stubs__/query.js +200 -0
  121. package/src/server/plugins/postcode-lookup/test/__stubs__/uprn.js +53 -0
  122. package/src/server/plugins/postcode-lookup/types.js +143 -0
  123. package/src/server/plugins/postcode-lookup/views/postcode-lookup-details.html +83 -0
  124. package/src/server/postcode-lookup.test.ts +64 -0
  125. package/src/server/routes/types.ts +11 -2
  126. package/src/server/schemas/index.ts +5 -7
  127. package/src/server/types.ts +1 -0
@@ -1 +1 @@
1
- {"version":3,"file":"summary.js","names":["slugSchema","Boom","Joi","RepeatPageController","redirectOrMakeHandler","actionSchema","crumbSchema","pathSchema","stateSchema","getHandler","request","h","params","page","context","notFound","path","makeGetListSummaryRouteHandler","postHandler","isForceAccess","makePostListSummaryRouteHandler","getRoutes","getRouteOptions","postRouteOptions","method","handler","options","validate","object","keys","slug","state","payload","crumb","action","required"],"sources":["../../../../../../src/server/plugins/engine/routes/repeaters/summary.ts"],"sourcesContent":["// List summary GET route\nimport { slugSchema } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type RouteOptions, type ServerRoute } from '@hapi/hapi'\nimport Joi from 'joi'\n\nimport { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { redirectOrMakeHandler } from '~/src/server/plugins/engine/routes/index.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n pathSchema,\n stateSchema\n} from '~/src/server/schemas/index.js'\n\nfunction getHandler(request: FormRequest, h: FormResponseToolkit) {\n const { params } = request\n\n return redirectOrMakeHandler(request, h, (page, context) => {\n if (!(page instanceof RepeatPageController)) {\n throw Boom.notFound(`No repeater page found for /${params.path}`)\n }\n\n return page.makeGetListSummaryRouteHandler()(request, context, h)\n })\n}\n\nfunction postHandler(request: FormRequestPayload, h: FormResponseToolkit) {\n const { params } = request\n\n return redirectOrMakeHandler(request, h, (page, context) => {\n const { isForceAccess } = context\n\n if (isForceAccess || !(page instanceof RepeatPageController)) {\n throw Boom.notFound(`No repeater page found for /${params.path}`)\n }\n\n return page.makePostListSummaryRouteHandler()(request, context, h)\n })\n}\n\nexport function getRoutes(\n getRouteOptions: RouteOptions<FormRequestRefs>,\n postRouteOptions: RouteOptions<FormRequestPayloadRefs>\n): (ServerRoute<FormRequestRefs> | ServerRoute<FormRequestPayloadRefs>)[] {\n return [\n {\n method: 'get',\n path: '/{slug}/{path}/summary',\n handler: getHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema\n })\n }\n }\n },\n\n {\n method: 'get',\n path: '/preview/{state}/{slug}/{path}/summary',\n handler: getHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema\n })\n }\n }\n },\n\n {\n method: 'post',\n path: '/{slug}/{path}/summary',\n handler: postHandler,\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .required()\n }\n }\n },\n\n {\n method: 'post',\n path: '/preview/{state}/{slug}/{path}/summary',\n handler: postHandler,\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .required()\n }\n }\n }\n ]\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"summary.js","names":["slugSchema","Boom","Joi","RepeatPageController","redirectOrMakeHandler","actionSchema","crumbSchema","pathSchema","stateSchema","getHandler","onRequest","request","h","params","page","context","notFound","path","makeGetListSummaryRouteHandler","postHandler","isForceAccess","makePostListSummaryRouteHandler","getRoutes","getRouteOptions","postRouteOptions","method","handler","options","validate","object","keys","slug","state","payload","crumb","action","required"],"sources":["../../../../../../src/server/plugins/engine/routes/repeaters/summary.ts"],"sourcesContent":["// List summary GET route\nimport { slugSchema } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type RouteOptions, type ServerRoute } from '@hapi/hapi'\nimport Joi from 'joi'\n\nimport { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { redirectOrMakeHandler } from '~/src/server/plugins/engine/routes/index.js'\nimport { type OnRequestCallback } from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n pathSchema,\n stateSchema\n} from '~/src/server/schemas/index.js'\n\nfunction getHandler(onRequest?: OnRequestCallback) {\n return async function (request: FormRequest, h: FormResponseToolkit) {\n const { params } = request\n\n return redirectOrMakeHandler(request, h, onRequest, (page, context) => {\n if (!(page instanceof RepeatPageController)) {\n throw Boom.notFound(`No repeater page found for /${params.path}`)\n }\n\n return page.makeGetListSummaryRouteHandler()(request, context, h)\n })\n }\n}\n\nfunction postHandler(onRequest?: OnRequestCallback) {\n return async function (request: FormRequestPayload, h: FormResponseToolkit) {\n const { params } = request\n\n return redirectOrMakeHandler(request, h, onRequest, (page, context) => {\n const { isForceAccess } = context\n\n if (isForceAccess || !(page instanceof RepeatPageController)) {\n throw Boom.notFound(`No repeater page found for /${params.path}`)\n }\n\n return page.makePostListSummaryRouteHandler()(request, context, h)\n })\n }\n}\n\nexport function getRoutes(\n getRouteOptions: RouteOptions<FormRequestRefs>,\n postRouteOptions: RouteOptions<FormRequestPayloadRefs>,\n onRequest?: OnRequestCallback\n): (ServerRoute<FormRequestRefs> | ServerRoute<FormRequestPayloadRefs>)[] {\n return [\n {\n method: 'get',\n path: '/{slug}/{path}/summary',\n handler: getHandler(onRequest),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema\n })\n }\n }\n },\n\n {\n method: 'get',\n path: '/preview/{state}/{slug}/{path}/summary',\n handler: getHandler(onRequest),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema\n })\n }\n }\n },\n\n {\n method: 'post',\n path: '/{slug}/{path}/summary',\n handler: postHandler(onRequest),\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .required()\n }\n }\n },\n\n {\n method: 'post',\n path: '/preview/{state}/{slug}/{path}/summary',\n handler: postHandler(onRequest),\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .required()\n }\n }\n }\n ]\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,4 +1,4 @@
1
- import { type ComponentDef, type Event, type FormDefinition, type FormMetadata, type FormVersionMetadata, type Item, type List, type Page } from '@defra/forms-model';
1
+ import { type ComponentDef, type Event, type FormVersionMetadata, type Item, type List, type Page, type UkAddressFieldComponent } from '@defra/forms-model';
2
2
  import { type PluginProperties, type Request, type ResponseObject } from '@hapi/hapi';
3
3
  import { type JoiExpression, type ValidationErrorItem } from 'joi';
4
4
  import { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js';
@@ -9,9 +9,10 @@ import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
9
9
  import { type DetailItemField } from '~/src/server/plugins/engine/models/types.js';
10
10
  import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js';
11
11
  import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js';
12
+ import { type QuestionPageController } from '~/src/server/plugins/engine/pageControllers/index.js';
12
13
  import { type FileStatus, type FormAdapterSubmissionSchemaVersion, type UploadStatus } from '~/src/server/plugins/engine/types/enums.js';
13
14
  import { type ViewContext } from '~/src/server/plugins/nunjucks/types.js';
14
- import { type FormAction, type FormParams, type FormRequest, type FormRequestPayload, type FormResponseToolkit, type FormStatus } from '~/src/server/routes/types.js';
15
+ import { type FormAction, type FormRequest, type FormRequestPayload, type FormResponseToolkit, type FormStatus } from '~/src/server/routes/types.js';
15
16
  import { type CacheService } from '~/src/server/services/cacheService.js';
16
17
  import { type RequestOptions } from '~/src/server/services/httpService.js';
17
18
  import { type Services } from '~/src/server/types.js';
@@ -261,8 +262,24 @@ export interface ErrorMessageTemplateList {
261
262
  advancedSettingsErrors: ErrorMessageTemplate[];
262
263
  }
263
264
  export type PreparePageEventRequestOptions = (options: RequestOptions, event: Event, page: PageControllerClass, context: FormContext) => void;
264
- export type OnRequestCallback = (request: AnyFormRequest, params: FormParams, definition: FormDefinition, metadata: FormMetadata) => void;
265
+ export type OnRequestCallback = (request: AnyFormRequest, h: FormResponseToolkit, context: FormContext) => ResponseObject | FormResponseToolkit['continue'] | Promise<ResponseObject | FormResponseToolkit['continue']>;
265
266
  export type SaveAndExitHandler = (request: FormRequestPayload, h: FormResponseToolkit, context: FormContext) => ResponseObject;
267
+ export interface ExternalArgs {
268
+ component: ComponentDef;
269
+ controller: QuestionPageController;
270
+ sourceUrl: string;
271
+ actionArgs: Record<string, string>;
272
+ }
273
+ export interface PostcodeLookupExternalArgs extends ExternalArgs {
274
+ component: UkAddressFieldComponent;
275
+ actionArgs: {
276
+ step: string;
277
+ };
278
+ }
279
+ export interface ExternalStateAppendage {
280
+ component: string;
281
+ data: FormStateValue | FormState;
282
+ }
266
283
  export interface PluginOptions {
267
284
  model?: FormModel;
268
285
  services?: Services;
@@ -280,6 +297,7 @@ export interface PluginOptions {
280
297
  preparePageEventRequestOptions?: PreparePageEventRequestOptions;
281
298
  onRequest?: OnRequestCallback;
282
299
  baseUrl: string;
300
+ ordnanceSurveyApiKey?: string;
283
301
  }
284
302
  export interface FormAdapterSubmissionMessageMeta {
285
303
  schemaVersion: FormAdapterSubmissionSchemaVersion;
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":["FileStatus","UploadStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormDefinition,\n type FormMetadata,\n type FormVersionMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport {\n type PluginProperties,\n type Request,\n type ResponseObject\n} from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel,\n type DatePartsState,\n type MonthYearState\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItemField } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n type FileStatus,\n type FormAdapterSubmissionSchemaVersion,\n type UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormParams,\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/cacheService.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport type AnyFormRequest = FormRequest | FormRequestPayload\nexport type AnyRequest = Request | AnyFormRequest\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n submittedVersionNumber?: number\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<\n FormRequest,\n 'app' | 'method' | 'params' | 'path' | 'query' | 'url' | 'server'\n >\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport {\n FileStatus,\n UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n allowSaveAndExit: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n allowSaveAndExit: boolean\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type GlobalFunction = (value: unknown) => unknown\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: AnyFormRequest,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport type SaveAndExitHandler = (\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n) => ResponseObject\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cache?: CacheService | string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndExit?: SaveAndExitHandler\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n}\n\nexport interface FormAdapterSubmissionMessageMeta {\n schemaVersion: FormAdapterSubmissionSchemaVersion\n timestamp: Date\n referenceNumber: string\n formName: string\n formId: string\n formSlug: string\n status: FormStatus\n isPreview: boolean\n notificationEmail: string\n versionMetadata?: FormVersionMetadata\n custom?: Record<string, unknown>\n}\n\nexport type FormAdapterSubmissionMessageMetaSerialised = Omit<\n FormAdapterSubmissionMessageMeta,\n 'schemaVersion' | 'timestamp' | 'status'\n> & {\n schemaVersion: number\n status: string\n timestamp: string\n}\nexport interface FormAdapterFile {\n fileName: string\n fileId: string\n userDownloadLink: string\n}\n\nexport interface FormAdapterSubmissionMessageResult {\n files: {\n main: string\n repeaters: Record<string, string>\n }\n}\n\n/**\n * A detail item specifically for files\n */\nexport type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {\n field: FileUploadField\n}\nexport type RichFormValue =\n | FormValue\n | FormPayload\n | DatePartsState\n | MonthYearState\n | UkAddressState\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue | null>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, FormAdapterFile[]>\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n result: FormAdapterSubmissionMessageResult\n}\n\nexport interface FormAdapterSubmissionMessage\n extends FormAdapterSubmissionMessagePayload {\n messageId: string\n recordCreatedAt: Date\n}\n\nexport interface FormAdapterSubmissionService {\n handleFormSubmission: (\n submissionMessage: FormAdapterSubmissionMessage\n ) => unknown\n}\n"],"mappings":"AAqDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAsGA,SACEA,UAAU,EACVC,YAAY;;AA+Nd;AACA;AACA","ignoreList":[]}
1
+ {"version":3,"file":"types.js","names":["FileStatus","UploadStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormVersionMetadata,\n type Item,\n type List,\n type Page,\n type UkAddressFieldComponent\n} from '@defra/forms-model'\nimport {\n type PluginProperties,\n type Request,\n type ResponseObject\n} from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel,\n type DatePartsState,\n type MonthYearState\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItemField } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { type QuestionPageController } from '~/src/server/plugins/engine/pageControllers/index.js'\nimport {\n type FileStatus,\n type FormAdapterSubmissionSchemaVersion,\n type UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/cacheService.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport type AnyFormRequest = FormRequest | FormRequestPayload\nexport type AnyRequest = Request | AnyFormRequest\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n submittedVersionNumber?: number\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<\n FormRequest,\n 'app' | 'method' | 'params' | 'path' | 'query' | 'url' | 'server'\n >\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport {\n FileStatus,\n UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n allowSaveAndExit: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n allowSaveAndExit: boolean\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type GlobalFunction = (value: unknown) => unknown\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: AnyFormRequest,\n h: FormResponseToolkit,\n context: FormContext\n) =>\n | ResponseObject\n | FormResponseToolkit['continue']\n | Promise<ResponseObject | FormResponseToolkit['continue']>\n\nexport type SaveAndExitHandler = (\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n) => ResponseObject\n\nexport interface ExternalArgs {\n component: ComponentDef\n controller: QuestionPageController\n sourceUrl: string\n actionArgs: Record<string, string>\n}\n\nexport interface PostcodeLookupExternalArgs extends ExternalArgs {\n component: UkAddressFieldComponent\n actionArgs: { step: string }\n}\n\nexport interface ExternalStateAppendage {\n component: string\n data: FormStateValue | FormState\n}\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cache?: CacheService | string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndExit?: SaveAndExitHandler\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n ordnanceSurveyApiKey?: string\n}\n\nexport interface FormAdapterSubmissionMessageMeta {\n schemaVersion: FormAdapterSubmissionSchemaVersion\n timestamp: Date\n referenceNumber: string\n formName: string\n formId: string\n formSlug: string\n status: FormStatus\n isPreview: boolean\n notificationEmail: string\n versionMetadata?: FormVersionMetadata\n custom?: Record<string, unknown>\n}\n\nexport type FormAdapterSubmissionMessageMetaSerialised = Omit<\n FormAdapterSubmissionMessageMeta,\n 'schemaVersion' | 'timestamp' | 'status'\n> & {\n schemaVersion: number\n status: string\n timestamp: string\n}\nexport interface FormAdapterFile {\n fileName: string\n fileId: string\n userDownloadLink: string\n}\n\nexport interface FormAdapterSubmissionMessageResult {\n files: {\n main: string\n repeaters: Record<string, string>\n }\n}\n\n/**\n * A detail item specifically for files\n */\nexport type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {\n field: FileUploadField\n}\nexport type RichFormValue =\n | FormValue\n | FormPayload\n | DatePartsState\n | MonthYearState\n | UkAddressState\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue | null>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, FormAdapterFile[]>\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n result: FormAdapterSubmissionMessageResult\n}\n\nexport interface FormAdapterSubmissionMessage\n extends FormAdapterSubmissionMessagePayload {\n messageId: string\n recordCreatedAt: Date\n}\n\nexport interface FormAdapterSubmissionService {\n handleFormSubmission: (\n submissionMessage: FormAdapterSubmissionMessage\n ) => unknown\n}\n"],"mappings":"AAoDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAsGA,SACEA,UAAU,EACVC,YAAY;;AAmPd;AACA;AACA","ignoreList":[]}
@@ -0,0 +1,15 @@
1
+ import { type ResponseObject } from '@hapi/hapi';
2
+ import { type FormRequestPayload, type FormResponseToolkit } from '~/src/server/plugins/engine/types/index.js';
3
+ import { type ExternalArgs } from '~/src/server/plugins/engine/types.js';
4
+ export declare function isExternalComponent(component: unknown): component is ExternalComponent;
5
+ export interface ExternalComponent {
6
+ dispatcher(request: FormRequestPayload, h: FormResponseToolkit, args: ExternalArgs): ResponseObject;
7
+ }
8
+ /**
9
+ * Returns internal and external components from a componentMap, regardless of error state.
10
+ * @returns An object containing internalComponents and externalComponents arrays
11
+ */
12
+ export declare function getComponentsByType(): {
13
+ internalComponents: Map<string, unknown>;
14
+ externalComponents: Map<string, ExternalComponent>;
15
+ };
@@ -0,0 +1,29 @@
1
+ import * as Components from "./components/index.js";
2
+ // Type guard for ExternalComponent
3
+ export function isExternalComponent(component) {
4
+ return typeof component.dispatcher === 'function';
5
+ }
6
+
7
+ // External components are guaranteed to have a dispatcher method
8
+
9
+ /**
10
+ * Returns internal and external components from a componentMap, regardless of error state.
11
+ * @returns An object containing internalComponents and externalComponents arrays
12
+ */
13
+ export function getComponentsByType() {
14
+ const internalComponents = new Map();
15
+ const externalComponents = new Map();
16
+ const componentMap = new Map(Object.entries(Components));
17
+ for (const [name, component] of componentMap.entries()) {
18
+ if (isExternalComponent(component)) {
19
+ externalComponents.set(name, component);
20
+ } else {
21
+ internalComponents.set(name, component);
22
+ }
23
+ }
24
+ return {
25
+ internalComponents,
26
+ externalComponents
27
+ };
28
+ }
29
+ //# sourceMappingURL=validationHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validationHelpers.js","names":["Components","isExternalComponent","component","dispatcher","getComponentsByType","internalComponents","Map","externalComponents","componentMap","Object","entries","name","set"],"sources":["../../../../src/server/plugins/engine/validationHelpers.ts"],"sourcesContent":["import { type ResponseObject } from '@hapi/hapi'\n\nimport * as Components from '~/src/server/plugins/engine/components/index.js'\nimport {\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/plugins/engine/types/index.js'\nimport { type ExternalArgs } from '~/src/server/plugins/engine/types.js'\n\n// Type guard for ExternalComponent\nexport function isExternalComponent(\n component: unknown\n): component is ExternalComponent {\n return typeof (component as ExternalComponent).dispatcher === 'function'\n}\n\n// External components are guaranteed to have a dispatcher method\nexport interface ExternalComponent {\n dispatcher(\n request: FormRequestPayload,\n h: FormResponseToolkit,\n args: ExternalArgs\n ): ResponseObject\n}\n\n/**\n * Returns internal and external components from a componentMap, regardless of error state.\n * @returns An object containing internalComponents and externalComponents arrays\n */\nexport function getComponentsByType(): {\n internalComponents: Map<string, unknown>\n externalComponents: Map<string, ExternalComponent>\n} {\n const internalComponents = new Map<string, unknown>()\n const externalComponents = new Map<string, ExternalComponent>()\n\n const componentMap = new Map<string, unknown>(Object.entries(Components))\n\n for (const [name, component] of componentMap.entries()) {\n if (isExternalComponent(component)) {\n externalComponents.set(name, component)\n } else {\n internalComponents.set(name, component)\n }\n }\n\n return { internalComponents, externalComponents }\n}\n"],"mappings":"AAEA,OAAO,KAAKA,UAAU;AAOtB;AACA,OAAO,SAASC,mBAAmBA,CACjCC,SAAkB,EACc;EAChC,OAAO,OAAQA,SAAS,CAAuBC,UAAU,KAAK,UAAU;AAC1E;;AAEA;;AASA;AACA;AACA;AACA;AACA,OAAO,SAASC,mBAAmBA,CAAA,EAGjC;EACA,MAAMC,kBAAkB,GAAG,IAAIC,GAAG,CAAkB,CAAC;EACrD,MAAMC,kBAAkB,GAAG,IAAID,GAAG,CAA4B,CAAC;EAE/D,MAAME,YAAY,GAAG,IAAIF,GAAG,CAAkBG,MAAM,CAACC,OAAO,CAACV,UAAU,CAAC,CAAC;EAEzE,KAAK,MAAM,CAACW,IAAI,EAAET,SAAS,CAAC,IAAIM,YAAY,CAACE,OAAO,CAAC,CAAC,EAAE;IACtD,IAAIT,mBAAmB,CAACC,SAAS,CAAC,EAAE;MAClCK,kBAAkB,CAACK,GAAG,CAACD,IAAI,EAAET,SAAS,CAAC;IACzC,CAAC,MAAM;MACLG,kBAAkB,CAACO,GAAG,CAACD,IAAI,EAAET,SAAS,CAAC;IACzC;EACF;EAEA,OAAO;IAAEG,kBAAkB;IAAEE;EAAmB,CAAC;AACnD","ignoreList":[]}
@@ -1,10 +1,18 @@
1
1
  {% from "partials/components.html" import componentList %}
2
2
  {% from "govuk/components/fieldset/macro.njk" import govukFieldset %}
3
3
  {% from "govuk/components/hint/macro.njk" import govukHint %}
4
+ {% from "govuk/components/button/macro.njk" import govukButton %}
5
+ {% from "govuk/components/inset-text/macro.njk" import govukInsetText %}
4
6
 
5
7
  {% macro UkAddressField(component) %}
6
8
  {% set fieldset = component.model.fieldset %}
7
- {% set addressFieldHtml = componentList(component.model.components) %}
9
+ {% set usePostcodeLookup = component.model.usePostcodeLookup %}
10
+
11
+ {% set addressFieldHtml %}
12
+ <div {{"hidden" if usePostcodeLookup }}>
13
+ {{ componentList(component.model.components) }}
14
+ </div>
15
+ {% endset %}
8
16
 
9
17
  {% if component.model.hint %}
10
18
  {% set addressHintHtml %}
@@ -17,9 +25,45 @@
17
25
  {% set addressFieldHtml = addressHintHtml + addressFieldHtml %}
18
26
  {% endif %}
19
27
 
20
- {{ govukFieldset({
21
- legend: fieldset.legend,
22
- attributes: fieldset.attributes,
23
- html: addressFieldHtml
24
- }) if fieldset else addressFieldHtml }}
28
+ <div id="{{component.model.name}}" class="govuk-form-group{{ ' govuk-form-group--error' if usePostcodeLookup and component.model.errors | length }}">
29
+ {{ govukFieldset({
30
+ legend: fieldset.legend,
31
+ attributes: fieldset.attributes,
32
+ html: addressFieldHtml
33
+ }) if fieldset else addressFieldHtml }}
34
+
35
+ {% if usePostcodeLookup %}
36
+ {% set value = component.model.value %}
37
+
38
+ {% if value %}
39
+ {% set insetHtml %}
40
+ <strong>Selected address:</strong>
41
+ <br><br>
42
+ {{ value }}
43
+ <br><br>
44
+ <p class="govuk-body">
45
+ <button class="govuk-link govuk-button--link govuk-!-margin-right-1" name="action"
46
+ value="external-{{component.model.name}}">Use a different address</button>
47
+ </p>
48
+ {% endset %}
49
+
50
+ {{ govukInsetText({
51
+ html: insetHtml,
52
+ classes: "govuk-!-margin-top-2"
53
+ }) }}
54
+ {% else %}
55
+ <div class="govuk-button-group govuk-!-margin-bottom-0">
56
+ {{ govukButton({
57
+ text: "Find an address",
58
+ attributes: {
59
+ name: "action",
60
+ value: "external-" + component.model.name
61
+ },
62
+ classes: "govuk-button--secondary govuk-!-margin-right-1 govuk-!-margin-bottom-0"
63
+ }) }}
64
+ <p class="govuk-body govuk-!-margin-bottom-0">or <button class="govuk-link govuk-button--link govuk-!-margin-right-1 govuk-!-margin-bottom-0" name="action" value="external-{{component.model.name}}--step:manual">enter address manually</button></p>
65
+ </div>
66
+ {% endif %}
67
+ {% endif %}
68
+ </div>
25
69
  {% endmacro %}
@@ -5,11 +5,13 @@ import vision from '@hapi/vision';
5
5
  import nunjucks from 'nunjucks';
6
6
  import resolvePkg from 'resolve';
7
7
  import { VIEW_PATH, context, prepareNunjucksEnvironment } from "./index.js";
8
+ import { VIEW_PATH as POSTCODE_LOOKUP_VIEW_PATH } from "../postcode-lookup/index.js";
8
9
  export async function registerVision(server, pluginOptions) {
9
10
  const packageRoot = findPackageRoot();
10
11
  const govukFrontendPath = dirname(resolvePkg.sync('govuk-frontend/package.json'));
11
12
  const viewPathResolved = join(packageRoot, VIEW_PATH);
12
- const paths = [...pluginOptions.nunjucks.paths, viewPathResolved, join(govukFrontendPath, 'dist')];
13
+ const postcodeLookupPathResolved = join(packageRoot, POSTCODE_LOOKUP_VIEW_PATH);
14
+ const paths = [...pluginOptions.nunjucks.paths, viewPathResolved, postcodeLookupPathResolved, join(govukFrontendPath, 'dist')];
13
15
  await server.register({
14
16
  plugin: vision,
15
17
  options: {
@@ -1 +1 @@
1
- {"version":3,"file":"vision.js","names":["existsSync","dirname","join","fileURLToPath","vision","nunjucks","resolvePkg","VIEW_PATH","context","prepareNunjucksEnvironment","registerVision","server","pluginOptions","packageRoot","findPackageRoot","govukFrontendPath","sync","viewPathResolved","paths","register","plugin","options","engines","html","compile","path","compileOptions","template","environment","render","prepare","next","configure","currentFileName","import","meta","url","currentDirectoryName","dir","Error"],"sources":["../../../../src/server/plugins/engine/vision.ts"],"sourcesContent":["import { existsSync } from 'fs'\nimport { dirname, join } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport { type Server } from '@hapi/hapi'\nimport vision from '@hapi/vision'\nimport nunjucks, { type Environment } from 'nunjucks'\nimport resolvePkg from 'resolve'\n\nimport {\n VIEW_PATH,\n context,\n prepareNunjucksEnvironment\n} from '~/src/server/plugins/engine/index.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\n\nexport async function registerVision(\n server: Server,\n pluginOptions: PluginOptions\n) {\n const packageRoot = findPackageRoot()\n const govukFrontendPath = dirname(\n resolvePkg.sync('govuk-frontend/package.json')\n )\n\n const viewPathResolved = join(packageRoot, VIEW_PATH)\n\n const paths = [\n ...pluginOptions.nunjucks.paths,\n viewPathResolved,\n join(govukFrontendPath, 'dist')\n ]\n\n await server.register({\n plugin: vision,\n options: {\n engines: {\n html: {\n compile: (\n path: string,\n compileOptions: { environment: Environment }\n ) => {\n const template = nunjucks.compile(path, compileOptions.environment)\n\n return (context: object | undefined) => {\n return template.render(context)\n }\n },\n prepare: (\n options: EngineConfigurationObject,\n next: (err?: Error) => void\n ) => {\n // Nunjucks also needs an additional path configuration\n // to use the templates and macros from `govuk-frontend`\n const environment = nunjucks.configure(paths)\n\n // Applies custom filters and globals for nunjucks\n // that are required by the `forms-engine-plugin`\n prepareNunjucksEnvironment(environment, pluginOptions)\n\n options.compileOptions.environment = environment\n\n next()\n }\n }\n },\n path: paths,\n // Provides global context used with all templates\n context\n }\n })\n}\n\ninterface CompileOptions {\n environment: Environment\n}\n\nexport interface EngineConfigurationObject {\n compileOptions: CompileOptions\n}\n\nexport function findPackageRoot() {\n const currentFileName = fileURLToPath(import.meta.url)\n const currentDirectoryName = dirname(currentFileName)\n\n let dir = currentDirectoryName\n while (dir !== '/') {\n if (existsSync(join(dir, 'package.json'))) {\n return dir\n }\n dir = dirname(dir)\n }\n\n throw new Error('package.json not found in parent directories')\n}\n"],"mappings":"AAAA,SAASA,UAAU,QAAQ,IAAI;AAC/B,SAASC,OAAO,EAAEC,IAAI,QAAQ,MAAM;AACpC,SAASC,aAAa,QAAQ,KAAK;AAGnC,OAAOC,MAAM,MAAM,cAAc;AACjC,OAAOC,QAAQ,MAA4B,UAAU;AACrD,OAAOC,UAAU,MAAM,SAAS;AAEhC,SACEC,SAAS,EACTC,OAAO,EACPC,0BAA0B;AAI5B,OAAO,eAAeC,cAAcA,CAClCC,MAAc,EACdC,aAA4B,EAC5B;EACA,MAAMC,WAAW,GAAGC,eAAe,CAAC,CAAC;EACrC,MAAMC,iBAAiB,GAAGd,OAAO,CAC/BK,UAAU,CAACU,IAAI,CAAC,6BAA6B,CAC/C,CAAC;EAED,MAAMC,gBAAgB,GAAGf,IAAI,CAACW,WAAW,EAAEN,SAAS,CAAC;EAErD,MAAMW,KAAK,GAAG,CACZ,GAAGN,aAAa,CAACP,QAAQ,CAACa,KAAK,EAC/BD,gBAAgB,EAChBf,IAAI,CAACa,iBAAiB,EAAE,MAAM,CAAC,CAChC;EAED,MAAMJ,MAAM,CAACQ,QAAQ,CAAC;IACpBC,MAAM,EAAEhB,MAAM;IACdiB,OAAO,EAAE;MACPC,OAAO,EAAE;QACPC,IAAI,EAAE;UACJC,OAAO,EAAEA,CACPC,IAAY,EACZC,cAA4C,KACzC;YACH,MAAMC,QAAQ,GAAGtB,QAAQ,CAACmB,OAAO,CAACC,IAAI,EAAEC,cAAc,CAACE,WAAW,CAAC;YAEnE,OAAQpB,OAA2B,IAAK;cACtC,OAAOmB,QAAQ,CAACE,MAAM,CAACrB,OAAO,CAAC;YACjC,CAAC;UACH,CAAC;UACDsB,OAAO,EAAEA,CACPT,OAAkC,EAClCU,IAA2B,KACxB;YACH;YACA;YACA,MAAMH,WAAW,GAAGvB,QAAQ,CAAC2B,SAAS,CAACd,KAAK,CAAC;;YAE7C;YACA;YACAT,0BAA0B,CAACmB,WAAW,EAAEhB,aAAa,CAAC;YAEtDS,OAAO,CAACK,cAAc,CAACE,WAAW,GAAGA,WAAW;YAEhDG,IAAI,CAAC,CAAC;UACR;QACF;MACF,CAAC;MACDN,IAAI,EAAEP,KAAK;MACX;MACAV;IACF;EACF,CAAC,CAAC;AACJ;AAUA,OAAO,SAASM,eAAeA,CAAA,EAAG;EAChC,MAAMmB,eAAe,GAAG9B,aAAa,CAAC+B,MAAM,CAACC,IAAI,CAACC,GAAG,CAAC;EACtD,MAAMC,oBAAoB,GAAGpC,OAAO,CAACgC,eAAe,CAAC;EAErD,IAAIK,GAAG,GAAGD,oBAAoB;EAC9B,OAAOC,GAAG,KAAK,GAAG,EAAE;IAClB,IAAItC,UAAU,CAACE,IAAI,CAACoC,GAAG,EAAE,cAAc,CAAC,CAAC,EAAE;MACzC,OAAOA,GAAG;IACZ;IACAA,GAAG,GAAGrC,OAAO,CAACqC,GAAG,CAAC;EACpB;EAEA,MAAM,IAAIC,KAAK,CAAC,8CAA8C,CAAC;AACjE","ignoreList":[]}
1
+ {"version":3,"file":"vision.js","names":["existsSync","dirname","join","fileURLToPath","vision","nunjucks","resolvePkg","VIEW_PATH","context","prepareNunjucksEnvironment","POSTCODE_LOOKUP_VIEW_PATH","registerVision","server","pluginOptions","packageRoot","findPackageRoot","govukFrontendPath","sync","viewPathResolved","postcodeLookupPathResolved","paths","register","plugin","options","engines","html","compile","path","compileOptions","template","environment","render","prepare","next","configure","currentFileName","import","meta","url","currentDirectoryName","dir","Error"],"sources":["../../../../src/server/plugins/engine/vision.ts"],"sourcesContent":["import { existsSync } from 'fs'\nimport { dirname, join } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport { type Server } from '@hapi/hapi'\nimport vision from '@hapi/vision'\nimport nunjucks, { type Environment } from 'nunjucks'\nimport resolvePkg from 'resolve'\n\nimport {\n VIEW_PATH,\n context,\n prepareNunjucksEnvironment\n} from '~/src/server/plugins/engine/index.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport { VIEW_PATH as POSTCODE_LOOKUP_VIEW_PATH } from '~/src/server/plugins/postcode-lookup/index.js'\n\nexport async function registerVision(\n server: Server,\n pluginOptions: PluginOptions\n) {\n const packageRoot = findPackageRoot()\n const govukFrontendPath = dirname(\n resolvePkg.sync('govuk-frontend/package.json')\n )\n\n const viewPathResolved = join(packageRoot, VIEW_PATH)\n const postcodeLookupPathResolved = join(\n packageRoot,\n POSTCODE_LOOKUP_VIEW_PATH\n )\n\n const paths = [\n ...pluginOptions.nunjucks.paths,\n viewPathResolved,\n postcodeLookupPathResolved,\n join(govukFrontendPath, 'dist')\n ]\n\n await server.register({\n plugin: vision,\n options: {\n engines: {\n html: {\n compile: (\n path: string,\n compileOptions: { environment: Environment }\n ) => {\n const template = nunjucks.compile(path, compileOptions.environment)\n\n return (context: object | undefined) => {\n return template.render(context)\n }\n },\n prepare: (\n options: EngineConfigurationObject,\n next: (err?: Error) => void\n ) => {\n // Nunjucks also needs an additional path configuration\n // to use the templates and macros from `govuk-frontend`\n const environment = nunjucks.configure(paths)\n\n // Applies custom filters and globals for nunjucks\n // that are required by the `forms-engine-plugin`\n prepareNunjucksEnvironment(environment, pluginOptions)\n\n options.compileOptions.environment = environment\n\n next()\n }\n }\n },\n path: paths,\n // Provides global context used with all templates\n context\n }\n })\n}\n\ninterface CompileOptions {\n environment: Environment\n}\n\nexport interface EngineConfigurationObject {\n compileOptions: CompileOptions\n}\n\nexport function findPackageRoot() {\n const currentFileName = fileURLToPath(import.meta.url)\n const currentDirectoryName = dirname(currentFileName)\n\n let dir = currentDirectoryName\n while (dir !== '/') {\n if (existsSync(join(dir, 'package.json'))) {\n return dir\n }\n dir = dirname(dir)\n }\n\n throw new Error('package.json not found in parent directories')\n}\n"],"mappings":"AAAA,SAASA,UAAU,QAAQ,IAAI;AAC/B,SAASC,OAAO,EAAEC,IAAI,QAAQ,MAAM;AACpC,SAASC,aAAa,QAAQ,KAAK;AAGnC,OAAOC,MAAM,MAAM,cAAc;AACjC,OAAOC,QAAQ,MAA4B,UAAU;AACrD,OAAOC,UAAU,MAAM,SAAS;AAEhC,SACEC,SAAS,EACTC,OAAO,EACPC,0BAA0B;AAG5B,SAASF,SAAS,IAAIG,yBAAyB;AAE/C,OAAO,eAAeC,cAAcA,CAClCC,MAAc,EACdC,aAA4B,EAC5B;EACA,MAAMC,WAAW,GAAGC,eAAe,CAAC,CAAC;EACrC,MAAMC,iBAAiB,GAAGf,OAAO,CAC/BK,UAAU,CAACW,IAAI,CAAC,6BAA6B,CAC/C,CAAC;EAED,MAAMC,gBAAgB,GAAGhB,IAAI,CAACY,WAAW,EAAEP,SAAS,CAAC;EACrD,MAAMY,0BAA0B,GAAGjB,IAAI,CACrCY,WAAW,EACXJ,yBACF,CAAC;EAED,MAAMU,KAAK,GAAG,CACZ,GAAGP,aAAa,CAACR,QAAQ,CAACe,KAAK,EAC/BF,gBAAgB,EAChBC,0BAA0B,EAC1BjB,IAAI,CAACc,iBAAiB,EAAE,MAAM,CAAC,CAChC;EAED,MAAMJ,MAAM,CAACS,QAAQ,CAAC;IACpBC,MAAM,EAAElB,MAAM;IACdmB,OAAO,EAAE;MACPC,OAAO,EAAE;QACPC,IAAI,EAAE;UACJC,OAAO,EAAEA,CACPC,IAAY,EACZC,cAA4C,KACzC;YACH,MAAMC,QAAQ,GAAGxB,QAAQ,CAACqB,OAAO,CAACC,IAAI,EAAEC,cAAc,CAACE,WAAW,CAAC;YAEnE,OAAQtB,OAA2B,IAAK;cACtC,OAAOqB,QAAQ,CAACE,MAAM,CAACvB,OAAO,CAAC;YACjC,CAAC;UACH,CAAC;UACDwB,OAAO,EAAEA,CACPT,OAAkC,EAClCU,IAA2B,KACxB;YACH;YACA;YACA,MAAMH,WAAW,GAAGzB,QAAQ,CAAC6B,SAAS,CAACd,KAAK,CAAC;;YAE7C;YACA;YACAX,0BAA0B,CAACqB,WAAW,EAAEjB,aAAa,CAAC;YAEtDU,OAAO,CAACK,cAAc,CAACE,WAAW,GAAGA,WAAW;YAEhDG,IAAI,CAAC,CAAC;UACR;QACF;MACF,CAAC;MACDN,IAAI,EAAEP,KAAK;MACX;MACAZ;IACF;EACF,CAAC,CAAC;AACJ;AAUA,OAAO,SAASO,eAAeA,CAAA,EAAG;EAChC,MAAMoB,eAAe,GAAGhC,aAAa,CAACiC,MAAM,CAACC,IAAI,CAACC,GAAG,CAAC;EACtD,MAAMC,oBAAoB,GAAGtC,OAAO,CAACkC,eAAe,CAAC;EAErD,IAAIK,GAAG,GAAGD,oBAAoB;EAC9B,OAAOC,GAAG,KAAK,GAAG,EAAE;IAClB,IAAIxC,UAAU,CAACE,IAAI,CAACsC,GAAG,EAAE,cAAc,CAAC,CAAC,EAAE;MACzC,OAAOA,GAAG;IACZ;IACAA,GAAG,GAAGvC,OAAO,CAACuC,GAAG,CAAC;EACpB;EAEA,MAAM,IAAIC,KAAK,CAAC,8CAA8C,CAAC;AACjE","ignoreList":[]}
@@ -0,0 +1,8 @@
1
+ export const VIEW_PATH: "src/server/plugins/postcode-lookup/views";
2
+ export namespace postcodeLookupPlugin {
3
+ let name: string;
4
+ let dependencies: string[];
5
+ let multiple: false;
6
+ function register(server: import("@hapi/hapi").Server<import("@hapi/hapi").ServerApplicationState>, options: PostcodeLookupConfiguration): void;
7
+ }
8
+ import type { PostcodeLookupConfiguration } from '~/src/server/plugins/postcode-lookup/types.js';
@@ -0,0 +1,21 @@
1
+ import { getRoutes } from "./routes/index.js";
2
+ export const VIEW_PATH = 'src/server/plugins/postcode-lookup/views';
3
+
4
+ /**
5
+ * @satisfies {NamedPlugin<PostcodeLookupConfiguration>}
6
+ */
7
+ export const postcodeLookupPlugin = {
8
+ name: '@defra/forms-engine-plugin/postcode-lookup',
9
+ dependencies: ['@hapi/vision'],
10
+ multiple: false,
11
+ register(server, options) {
12
+ // @ts-expect-error - Request typing
13
+ server.route(getRoutes(options));
14
+ }
15
+ };
16
+
17
+ /**
18
+ * @import { NamedPlugin } from '@hapi/hapi'
19
+ * @import { PostcodeLookupConfiguration } from '~/src/server/plugins/postcode-lookup/types.js'
20
+ */
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["getRoutes","VIEW_PATH","postcodeLookupPlugin","name","dependencies","multiple","register","server","options","route"],"sources":["../../../../src/server/plugins/postcode-lookup/index.js"],"sourcesContent":["import { getRoutes } from '~/src/server/plugins/postcode-lookup/routes/index.js'\n\nexport const VIEW_PATH = 'src/server/plugins/postcode-lookup/views'\n\n/**\n * @satisfies {NamedPlugin<PostcodeLookupConfiguration>}\n */\nexport const postcodeLookupPlugin = {\n name: '@defra/forms-engine-plugin/postcode-lookup',\n dependencies: ['@hapi/vision'],\n multiple: false,\n register(server, options) {\n // @ts-expect-error - Request typing\n server.route(getRoutes(options))\n }\n}\n\n/**\n * @import { NamedPlugin } from '@hapi/hapi'\n * @import { PostcodeLookupConfiguration } from '~/src/server/plugins/postcode-lookup/types.js'\n */\n"],"mappings":"AAAA,SAASA,SAAS;AAElB,OAAO,MAAMC,SAAS,GAAG,0CAA0C;;AAEnE;AACA;AACA;AACA,OAAO,MAAMC,oBAAoB,GAAG;EAClCC,IAAI,EAAE,4CAA4C;EAClDC,YAAY,EAAE,CAAC,cAAc,CAAC;EAC9BC,QAAQ,EAAE,KAAK;EACfC,QAAQA,CAACC,MAAM,EAAEC,OAAO,EAAE;IACxB;IACAD,MAAM,CAACE,KAAK,CAACT,SAAS,CAACQ,OAAO,CAAC,CAAC;EAClC;AACF,CAAC;;AAED;AACA;AACA;AACA","ignoreList":[]}
@@ -0,0 +1,255 @@
1
+ /**
2
+ * The postcode lookup details form view model
3
+ * @param {PostcodeLookupSessionData} data
4
+ * @param {PostcodeLookupDetailsData} [payload]
5
+ * @param {Error} [err]
6
+ */
7
+ export function detailsViewModel(data: PostcodeLookupSessionData, payload?: PostcodeLookupDetailsData, err?: Error): {
8
+ step: string;
9
+ showTitle: boolean;
10
+ name: string;
11
+ serviceUrl: string;
12
+ pageTitle: string;
13
+ backLink: {
14
+ href: string;
15
+ };
16
+ errors: {
17
+ text: string;
18
+ href: string;
19
+ }[] | undefined;
20
+ fields: {
21
+ postcodeQuery: {
22
+ id: string;
23
+ name: string;
24
+ label: {
25
+ text: string;
26
+ };
27
+ hint: {
28
+ text: string;
29
+ };
30
+ value: string | undefined;
31
+ errorMessage: {
32
+ text: string;
33
+ } | undefined;
34
+ };
35
+ buildingNameQuery: {
36
+ id: string;
37
+ name: string;
38
+ label: {
39
+ text: string;
40
+ };
41
+ hint: {
42
+ text: string;
43
+ };
44
+ value: string | undefined;
45
+ errorMessage: {
46
+ text: string;
47
+ } | undefined;
48
+ };
49
+ };
50
+ buttons: {
51
+ continueButton: {
52
+ text: string;
53
+ classes: string;
54
+ };
55
+ manualLink: {
56
+ text: string;
57
+ href: string;
58
+ };
59
+ };
60
+ };
61
+ /**
62
+ * The postcode lookup select form view model
63
+ * @param {{ session: PostcodeLookupSessionData, apiKey: string }} data
64
+ * @param {PostcodeLookupSelectPayload} [payload]
65
+ * @param {Error} [err]
66
+ */
67
+ export function selectViewModel(data: {
68
+ session: PostcodeLookupSessionData;
69
+ apiKey: string;
70
+ }, payload?: PostcodeLookupSelectPayload, err?: Error): Promise<{
71
+ step: string;
72
+ showTitle: boolean;
73
+ name: string;
74
+ serviceUrl: string;
75
+ pageTitle: string;
76
+ backLink: {
77
+ href: string;
78
+ };
79
+ errors: {
80
+ text: string;
81
+ href: string;
82
+ }[] | undefined;
83
+ searchAgainLink: {
84
+ text: string;
85
+ href: string;
86
+ };
87
+ fields: {
88
+ postcodeQuery: {
89
+ id: string;
90
+ name: string;
91
+ type: string;
92
+ value: string;
93
+ };
94
+ buildingNameQuery: {
95
+ id: string;
96
+ name: string;
97
+ type: string;
98
+ value: string;
99
+ };
100
+ uprn: {
101
+ id: string;
102
+ name: string;
103
+ label: {
104
+ text: string;
105
+ } | undefined;
106
+ value: string | undefined;
107
+ errorMessage: {
108
+ text: string;
109
+ } | undefined;
110
+ items: {
111
+ text: string;
112
+ value: string;
113
+ }[] | undefined;
114
+ type: string | undefined;
115
+ };
116
+ };
117
+ details: PostcodeLookupDetailsData;
118
+ addressCount: number;
119
+ singleAddress: Address | undefined;
120
+ hasAddresses: boolean;
121
+ hasMultipleAddresses: boolean;
122
+ buttons: {
123
+ continueButton: {
124
+ href: string | undefined;
125
+ text: string;
126
+ classes: string;
127
+ };
128
+ manualLink: {
129
+ text: string;
130
+ href: string;
131
+ };
132
+ };
133
+ }>;
134
+ /**
135
+ * The postcode lookup manual form view model
136
+ * @param {PostcodeLookupSessionData} data
137
+ * @param {PostcodeLookupManualPayload} [payload]
138
+ * @param {Error} [err]
139
+ */
140
+ export function manualViewModel(data: PostcodeLookupSessionData, payload?: PostcodeLookupManualPayload, err?: Error): {
141
+ step: string;
142
+ showTitle: boolean;
143
+ name: string;
144
+ serviceUrl: string;
145
+ pageTitle: string;
146
+ backLink: {
147
+ href: string;
148
+ };
149
+ errors: {
150
+ text: string;
151
+ href: string;
152
+ }[] | undefined;
153
+ hint: "" | {
154
+ text: string;
155
+ } | undefined;
156
+ fields: {
157
+ addressLine1: {
158
+ id: string;
159
+ name: string;
160
+ label: {
161
+ text: string;
162
+ };
163
+ value: string | undefined;
164
+ errorMessage: {
165
+ text: string;
166
+ } | undefined;
167
+ };
168
+ addressLine2: {
169
+ id: string;
170
+ name: string;
171
+ label: {
172
+ text: string;
173
+ };
174
+ value: string | undefined;
175
+ errorMessage: {
176
+ text: string;
177
+ } | undefined;
178
+ };
179
+ town: {
180
+ id: string;
181
+ name: string;
182
+ label: {
183
+ text: string;
184
+ };
185
+ classes: string;
186
+ value: string | undefined;
187
+ errorMessage: {
188
+ text: string;
189
+ } | undefined;
190
+ };
191
+ county: {
192
+ id: string;
193
+ name: string;
194
+ label: {
195
+ text: string;
196
+ };
197
+ value: string | undefined;
198
+ errorMessage: {
199
+ text: string;
200
+ } | undefined;
201
+ };
202
+ postcode: {
203
+ id: string;
204
+ name: string;
205
+ label: {
206
+ text: string;
207
+ };
208
+ classes: string;
209
+ value: string | undefined;
210
+ errorMessage: {
211
+ text: string;
212
+ } | undefined;
213
+ };
214
+ };
215
+ buttons: {
216
+ continueButton: {
217
+ text: string;
218
+ classes: string;
219
+ };
220
+ detailsLink: {
221
+ text: string;
222
+ href: string;
223
+ };
224
+ };
225
+ };
226
+ export namespace steps {
227
+ let details: string;
228
+ let select: string;
229
+ let manual: string;
230
+ }
231
+ export const JOURNEY_BASE_URL: "/postcode-lookup";
232
+ export const stepSchema: Joi.StringSchema<string>;
233
+ /**
234
+ * Postcode lookup details form payload schema
235
+ * @type {ObjectSchema<PostcodeLookupDetailsPayload>}
236
+ */
237
+ export const detailsPayloadSchema: Joi.ObjectSchema<PostcodeLookupDetailsPayload>;
238
+ /**
239
+ * Postcode lookup select form payload schema
240
+ * @type {ObjectSchema<PostcodeLookupSelectPayload>}
241
+ */
242
+ export const selectPayloadSchema: Joi.ObjectSchema<PostcodeLookupSelectPayload>;
243
+ /**
244
+ * Postcode lookup manual form payload schema
245
+ * @type {ObjectSchema<PostcodeLookupManualPayload>}
246
+ */
247
+ export const manualPayloadSchema: Joi.ObjectSchema<PostcodeLookupManualPayload>;
248
+ export type OptionalValidationErrorItem = Joi.ValidationErrorItem | undefined;
249
+ import type { PostcodeLookupSessionData } from '~/src/server/plugins/postcode-lookup/types.js';
250
+ import type { PostcodeLookupDetailsData } from '~/src/server/plugins/postcode-lookup/types.js';
251
+ import type { PostcodeLookupSelectPayload } from '~/src/server/plugins/postcode-lookup/types.js';
252
+ import type { Address } from '~/src/server/plugins/postcode-lookup/types.js';
253
+ import type { PostcodeLookupManualPayload } from '~/src/server/plugins/postcode-lookup/types.js';
254
+ import Joi from 'joi';
255
+ import type { PostcodeLookupDetailsPayload } from '~/src/server/plugins/postcode-lookup/types.js';