@defra/forms-engine-plugin 4.20.1 → 4.21.0

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 (23) hide show
  1. package/.server/server/plugins/engine/form-availability.d.ts +2 -2
  2. package/.server/server/plugins/engine/form-availability.js +3 -2
  3. package/.server/server/plugins/engine/form-availability.js.map +1 -1
  4. package/.server/server/plugins/engine/{beta/form-context.d.ts → form-context.d.ts} +6 -6
  5. package/.server/server/plugins/engine/{beta/form-context.js → form-context.js} +9 -9
  6. package/.server/server/plugins/engine/form-context.js.map +1 -0
  7. package/.server/server/plugins/engine/index.d.ts +1 -1
  8. package/.server/server/plugins/engine/index.js +1 -1
  9. package/.server/server/plugins/engine/index.js.map +1 -1
  10. package/.server/server/plugins/engine/models/unavailable-view-model.d.ts +2 -2
  11. package/.server/server/plugins/engine/models/unavailable-view-model.js +2 -14
  12. package/.server/server/plugins/engine/models/unavailable-view-model.js.map +1 -1
  13. package/.server/server/plugins/engine/routes/index.js +1 -1
  14. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  15. package/.server/server/plugins/engine/views/unavailable.html +22 -8
  16. package/package.json +1 -1
  17. package/src/server/plugins/engine/form-availability.ts +11 -3
  18. package/src/server/plugins/engine/{beta/form-context.ts → form-context.ts} +12 -12
  19. package/src/server/plugins/engine/index.ts +1 -1
  20. package/src/server/plugins/engine/models/unavailable-view-model.ts +4 -21
  21. package/src/server/plugins/engine/routes/index.ts +1 -1
  22. package/src/server/plugins/engine/views/unavailable.html +22 -8
  23. package/.server/server/plugins/engine/beta/form-context.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { type FormMetadata } from '@defra/forms-model';
1
+ import { FormStatus, type FormMetadata } from '@defra/forms-model';
2
2
  import Boom from '@hapi/boom';
3
3
  export interface OfflineBoomData {
4
4
  offline: true;
@@ -9,7 +9,7 @@ export interface OfflineBoomData {
9
9
  * unavailable-response extension catches the marker and renders the
10
10
  * "Sorry, this form is unavailable" view at HTTP 200.
11
11
  */
12
- export declare function assertFormAvailable(metadata: FormMetadata): void;
12
+ export declare function assertFormAvailable(metadata: FormMetadata, formState: FormStatus, isPreview: boolean): void;
13
13
  /** Type guard for the offline Boom marker. */
14
14
  export declare function isOfflineBoom(err: unknown): err is Boom.Boom<OfflineBoomData> & {
15
15
  data: OfflineBoomData;
@@ -1,11 +1,12 @@
1
+ import { FormStatus } from '@defra/forms-model';
1
2
  import Boom from '@hapi/boom';
2
3
  /**
3
4
  * Throws when the form has been taken offline. The plugin's
4
5
  * unavailable-response extension catches the marker and renders the
5
6
  * "Sorry, this form is unavailable" view at HTTP 200.
6
7
  */
7
- export function assertFormAvailable(metadata) {
8
- if (metadata.offline === true) {
8
+ export function assertFormAvailable(metadata, formState, isPreview) {
9
+ if (metadata.offline === true && formState === FormStatus.Live && !isPreview) {
9
10
  const data = {
10
11
  offline: true,
11
12
  metadata
@@ -1 +1 @@
1
- {"version":3,"file":"form-availability.js","names":["Boom","assertFormAvailable","metadata","offline","data","boomify","Error","slug","statusCode","isOfflineBoom","err","isBoom"],"sources":["../../../../src/server/plugins/engine/form-availability.ts"],"sourcesContent":["import { type FormMetadata } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\n\nexport interface OfflineBoomData {\n offline: true\n metadata: FormMetadata\n}\n\n/**\n * Throws when the form has been taken offline. The plugin's\n * unavailable-response extension catches the marker and renders the\n * \"Sorry, this form is unavailable\" view at HTTP 200.\n */\nexport function assertFormAvailable(metadata: FormMetadata): void {\n if (metadata.offline === true) {\n const data: OfflineBoomData = { offline: true, metadata }\n throw Boom.boomify(new Error(`Form ${metadata.slug} is offline`), {\n statusCode: 503,\n data\n })\n }\n}\n\n/** Type guard for the offline Boom marker. */\nexport function isOfflineBoom(\n err: unknown\n): err is Boom.Boom<OfflineBoomData> & { data: OfflineBoomData } {\n if (!Boom.isBoom(err)) return false\n const data = err.data as Partial<OfflineBoomData> | null | undefined\n return data?.offline === true && !!data.metadata\n}\n"],"mappings":"AACA,OAAOA,IAAI,MAAM,YAAY;AAO7B;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,mBAAmBA,CAACC,QAAsB,EAAQ;EAChE,IAAIA,QAAQ,CAACC,OAAO,KAAK,IAAI,EAAE;IAC7B,MAAMC,IAAqB,GAAG;MAAED,OAAO,EAAE,IAAI;MAAED;IAAS,CAAC;IACzD,MAAMF,IAAI,CAACK,OAAO,CAAC,IAAIC,KAAK,CAAC,QAAQJ,QAAQ,CAACK,IAAI,aAAa,CAAC,EAAE;MAChEC,UAAU,EAAE,GAAG;MACfJ;IACF,CAAC,CAAC;EACJ;AACF;;AAEA;AACA,OAAO,SAASK,aAAaA,CAC3BC,GAAY,EACmD;EAC/D,IAAI,CAACV,IAAI,CAACW,MAAM,CAACD,GAAG,CAAC,EAAE,OAAO,KAAK;EACnC,MAAMN,IAAI,GAAGM,GAAG,CAACN,IAAmD;EACpE,OAAOA,IAAI,EAAED,OAAO,KAAK,IAAI,IAAI,CAAC,CAACC,IAAI,CAACF,QAAQ;AAClD","ignoreList":[]}
1
+ {"version":3,"file":"form-availability.js","names":["FormStatus","Boom","assertFormAvailable","metadata","formState","isPreview","offline","Live","data","boomify","Error","slug","statusCode","isOfflineBoom","err","isBoom"],"sources":["../../../../src/server/plugins/engine/form-availability.ts"],"sourcesContent":["import { FormStatus, type FormMetadata } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\n\nexport interface OfflineBoomData {\n offline: true\n metadata: FormMetadata\n}\n\n/**\n * Throws when the form has been taken offline. The plugin's\n * unavailable-response extension catches the marker and renders the\n * \"Sorry, this form is unavailable\" view at HTTP 200.\n */\nexport function assertFormAvailable(\n metadata: FormMetadata,\n formState: FormStatus,\n isPreview: boolean\n): void {\n if (\n metadata.offline === true &&\n formState === FormStatus.Live &&\n !isPreview\n ) {\n const data: OfflineBoomData = { offline: true, metadata }\n throw Boom.boomify(new Error(`Form ${metadata.slug} is offline`), {\n statusCode: 503,\n data\n })\n }\n}\n\n/** Type guard for the offline Boom marker. */\nexport function isOfflineBoom(\n err: unknown\n): err is Boom.Boom<OfflineBoomData> & { data: OfflineBoomData } {\n if (!Boom.isBoom(err)) return false\n const data = err.data as Partial<OfflineBoomData> | null | undefined\n return data?.offline === true && !!data.metadata\n}\n"],"mappings":"AAAA,SAASA,UAAU,QAA2B,oBAAoB;AAClE,OAAOC,IAAI,MAAM,YAAY;AAO7B;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,mBAAmBA,CACjCC,QAAsB,EACtBC,SAAqB,EACrBC,SAAkB,EACZ;EACN,IACEF,QAAQ,CAACG,OAAO,KAAK,IAAI,IACzBF,SAAS,KAAKJ,UAAU,CAACO,IAAI,IAC7B,CAACF,SAAS,EACV;IACA,MAAMG,IAAqB,GAAG;MAAEF,OAAO,EAAE,IAAI;MAAEH;IAAS,CAAC;IACzD,MAAMF,IAAI,CAACQ,OAAO,CAAC,IAAIC,KAAK,CAAC,QAAQP,QAAQ,CAACQ,IAAI,aAAa,CAAC,EAAE;MAChEC,UAAU,EAAE,GAAG;MACfJ;IACF,CAAC,CAAC;EACJ;AACF;;AAEA;AACA,OAAO,SAASK,aAAaA,CAC3BC,GAAY,EACmD;EAC/D,IAAI,CAACb,IAAI,CAACc,MAAM,CAACD,GAAG,CAAC,EAAE,OAAO,KAAK;EACnC,MAAMN,IAAI,GAAGM,GAAG,CAACN,IAAmD;EACpE,OAAOA,IAAI,EAAEF,OAAO,KAAK,IAAI,IAAI,CAAC,CAACE,IAAI,CAACL,QAAQ;AAClD","ignoreList":[]}
@@ -1,9 +1,9 @@
1
1
  import { type Request, type Server } from '@hapi/hapi';
2
- import { FormModel } from '../../../../server/plugins/engine/models/index.js';
3
- import { type PageController } from '../../../../server/plugins/engine/pageControllers/PageController.js';
4
- import { type FormContext, type FormSubmissionError } from '../../../../server/plugins/engine/types.js';
5
- import { FormStatus } from '../../../../server/routes/types.js';
6
- import { type Services } from '../../../../server/types.js';
2
+ import { FormModel } from '../../../server/plugins/engine/models/index.js';
3
+ import { type PageController } from '../../../server/plugins/engine/pageControllers/PageController.js';
4
+ import { type FormContext, type FormSubmissionError } from '../../../server/plugins/engine/types.js';
5
+ import { FormStatus } from '../../../server/routes/types.js';
6
+ import { type Services } from '../../../server/types.js';
7
7
  type JourneyState = FormStatus | 'preview';
8
8
  export interface FormModelOptions {
9
9
  services?: Services;
@@ -20,5 +20,5 @@ export interface FormContextOptions extends FormModelOptions {
20
20
  export declare function getFormModel(slug: string, state: JourneyState, options?: FormModelOptions): Promise<FormModel>;
21
21
  export declare function getFormContext({ server, yar }: Pick<Request, 'server' | 'yar'>, slug: string, state?: JourneyState, options?: FormContextOptions): Promise<FormContext>;
22
22
  export declare function resolveFormModel(server: Server, slug: string, state: JourneyState, options?: FormModelOptions): Promise<FormModel>;
23
- export declare function getFirstJourneyPage(context?: Pick<FormContext, 'relevantPages'>): import("../../../../server/plugins/engine/pageControllers/index.js").StartPageController | import("../../../../server/plugins/engine/pageControllers/index.js").QuestionPageController | import("../../../../server/plugins/engine/pageControllers/index.js").RepeatPageController | undefined;
23
+ export declare function getFirstJourneyPage(context?: Pick<FormContext, 'relevantPages'>): import("../../../server/plugins/engine/pageControllers/index.js").StartPageController | import("../../../server/plugins/engine/pageControllers/index.js").QuestionPageController | import("../../../server/plugins/engine/pageControllers/index.js").RepeatPageController | undefined;
24
24
  export {};
@@ -1,12 +1,12 @@
1
1
  import Boom from '@hapi/boom';
2
2
  import { isEqual } from 'date-fns';
3
- import { PREVIEW_PATH_PREFIX } from "../../../constants.js";
4
- import { assertFormAvailable } from "../form-availability.js";
5
- import { checkEmailAddressForLiveFormSubmission, getCacheService } from "../helpers.js";
6
- import { FormModel } from "../models/index.js";
7
- import { TerminalPageController } from "../pageControllers/index.js";
8
- import * as defaultServices from "../services/index.js";
9
- import { FormStatus } from "../../../routes/types.js";
3
+ import { PREVIEW_PATH_PREFIX } from "../../constants.js";
4
+ import { assertFormAvailable } from "./form-availability.js";
5
+ import { checkEmailAddressForLiveFormSubmission, getCacheService } from "./helpers.js";
6
+ import { FormModel } from "./models/index.js";
7
+ import { TerminalPageController } from "./pageControllers/index.js";
8
+ import * as defaultServices from "./services/index.js";
9
+ import { FormStatus } from "../../routes/types.js";
10
10
  export async function getFormModel(slug, state, options = {}) {
11
11
  const services = options.services ?? defaultServices;
12
12
  const {
@@ -15,7 +15,7 @@ export async function getFormModel(slug, state, options = {}) {
15
15
  const isPreview = isPreviewState(state, options);
16
16
  const formState = resolveState(state);
17
17
  const metadata = await formsService.getFormMetadata(slug);
18
- assertFormAvailable(metadata);
18
+ assertFormAvailable(metadata, formState, isPreview);
19
19
  const definition = await formsService.getFormDefinition(metadata.id, formState);
20
20
  if (!definition) {
21
21
  throw Boom.notFound(`No definition found for form metadata ${metadata.id} (${slug}) ${state}`);
@@ -61,9 +61,9 @@ export async function resolveFormModel(server, slug, state, options = {}) {
61
61
  formsService
62
62
  } = services;
63
63
  const metadata = await formsService.getFormMetadata(slug);
64
- assertFormAvailable(metadata);
65
64
  const formState = resolveState(state);
66
65
  const isPreview = options.isPreview ?? isPreviewState(state, options);
66
+ assertFormAvailable(metadata, formState, isPreview);
67
67
  const stateMetadata = metadata[formState];
68
68
  if (!stateMetadata) {
69
69
  throw Boom.notFound(`No '${formState}' state for form metadata ${metadata.id}`);
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-context.js","names":["Boom","isEqual","PREVIEW_PATH_PREFIX","assertFormAvailable","checkEmailAddressForLiveFormSubmission","getCacheService","FormModel","TerminalPageController","defaultServices","FormStatus","getFormModel","slug","state","options","services","formsService","isPreview","isPreviewState","formState","resolveState","metadata","getFormMetadata","definition","getFormDefinition","id","notFound","basePath","buildBasePath","routePrefix","ordnanceSurveyApiKey","formId","controllers","getFormContext","server","yar","Live","formModel","resolveFormModel","cacheService","summaryRequest","app","method","params","path","query","url","URL","cachedState","getState","$$__referenceNumber","errors","stateMetadata","models","Map","cache","cacheKey","entry","get","updatedAt","notificationEmail","realm","modifiers","route","prefix","model","set","base","replace","startsWith","slice","getFirstJourneyPage","context","relevantPages","undefined","lastPageReached","at","penultimatePageReached"],"sources":["../../../../src/server/plugins/engine/form-context.ts"],"sourcesContent":["import Boom from '@hapi/boom'\nimport { type Request, type Server } from '@hapi/hapi'\nimport { isEqual } from 'date-fns'\n\nimport { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'\nimport { assertFormAvailable } from '~/src/server/plugins/engine/form-availability.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n getCacheService\n} from '~/src/server/plugins/engine/helpers.js'\nimport { FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { TerminalPageController } from '~/src/server/plugins/engine/pageControllers/index.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type AnyRequest,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormStatus } from '~/src/server/routes/types.js'\nimport { type Services } from '~/src/server/types.js'\n\ntype JourneyState = FormStatus | 'preview'\n\nexport interface FormModelOptions {\n services?: Services\n controllers?: Record<string, typeof PageController>\n basePath?: string\n ordnanceSurveyApiKey?: string\n formId?: string\n routePrefix?: string\n isPreview?: boolean\n}\n\nexport interface FormContextOptions extends FormModelOptions {\n errors?: FormSubmissionError[]\n}\n\ntype SummaryRequest = FormContextRequest & {\n yar: Request['yar']\n}\n\nexport async function getFormModel(\n slug: string,\n state: JourneyState,\n options: FormModelOptions = {}\n) {\n const services = options.services ?? defaultServices\n const { formsService } = services\n const isPreview = isPreviewState(state, options)\n const formState = resolveState(state)\n\n const metadata = await formsService.getFormMetadata(slug)\n assertFormAvailable(metadata, formState, isPreview)\n\n const definition = await formsService.getFormDefinition(\n metadata.id,\n formState\n )\n\n if (!definition) {\n throw Boom.notFound(\n `No definition found for form metadata ${metadata.id} (${slug}) ${state}`\n )\n }\n\n return new FormModel(\n definition,\n {\n basePath:\n options.basePath ??\n buildBasePath(options.routePrefix ?? '', slug, formState, isPreview),\n ordnanceSurveyApiKey: options.ordnanceSurveyApiKey,\n formId: options.formId ?? metadata.id\n },\n services,\n options.controllers\n )\n}\n\nexport async function getFormContext(\n { server, yar }: Pick<Request, 'server' | 'yar'>,\n slug: string,\n state: JourneyState = FormStatus.Live,\n options: FormContextOptions = {}\n): Promise<FormContext> {\n const formModel = await resolveFormModel(server, slug, state, options)\n\n const cacheService = getCacheService(server)\n\n const summaryRequest: SummaryRequest = {\n app: {},\n method: 'get',\n params: {\n path: 'summary',\n slug,\n ...(isPreviewState(state, options) && {\n state: resolveState(state)\n })\n },\n path: `/${formModel.basePath}/summary`,\n query: {},\n url: new URL(\n `/${formModel.basePath}/summary`,\n 'https://form-context.local'\n ),\n server,\n yar\n }\n\n const cachedState = await cacheService.getState(\n summaryRequest as unknown as AnyRequest\n )\n\n const formState = {\n ...cachedState,\n $$__referenceNumber: cachedState.$$__referenceNumber\n } as unknown as FormSubmissionState\n\n return formModel.getFormContext(\n summaryRequest,\n formState,\n options.errors ?? []\n )\n}\n\nexport async function resolveFormModel(\n server: Server,\n slug: string,\n state: JourneyState,\n options: FormModelOptions = {}\n) {\n const services = options.services ?? defaultServices\n const { formsService } = services\n\n const metadata = await formsService.getFormMetadata(slug)\n const formState = resolveState(state)\n const isPreview = options.isPreview ?? isPreviewState(state, options)\n assertFormAvailable(metadata, formState, isPreview)\n const stateMetadata = metadata[formState]\n\n if (!stateMetadata) {\n throw Boom.notFound(\n `No '${formState}' state for form metadata ${metadata.id}`\n )\n }\n\n // The models cache is created lazily per server instance\n\n server.app.models ??= new Map<string, { model: FormModel; updatedAt: Date }>()\n\n const cache = server.app.models\n\n const cacheKey = `${metadata.id}_${formState}_${isPreview}`\n let entry = cache.get(cacheKey)\n\n if (!entry || !isEqual(entry.updatedAt, stateMetadata.updatedAt)) {\n const definition = await formsService.getFormDefinition(\n metadata.id,\n formState\n )\n\n if (!definition) {\n throw Boom.notFound(\n `No definition found for form metadata ${metadata.id} (${slug}) ${state}`\n )\n }\n\n checkEmailAddressForLiveFormSubmission(\n metadata.notificationEmail,\n isPreview\n )\n\n const routePrefix =\n options.routePrefix ?? server.realm.modifiers.route.prefix\n\n const model = new FormModel(\n definition,\n {\n basePath:\n options.basePath ??\n buildBasePath(routePrefix, slug, formState, isPreview),\n ordnanceSurveyApiKey: options.ordnanceSurveyApiKey,\n formId: options.formId ?? metadata.id\n },\n services,\n options.controllers\n )\n\n entry = { model, updatedAt: stateMetadata.updatedAt }\n cache.set(cacheKey, entry)\n }\n\n return entry.model\n}\n\nfunction buildBasePath(\n routePrefix: string,\n slug: string,\n state: FormStatus,\n isPreview: boolean\n) {\n const base = (\n isPreview\n ? `${routePrefix}${PREVIEW_PATH_PREFIX}/${state}/${slug}`\n : `${routePrefix}/${slug}`\n ).replace(/\\/{2,}/g, '/')\n\n return base.startsWith('/') ? base.slice(1) : base\n}\n\nexport function getFirstJourneyPage(\n context?: Pick<FormContext, 'relevantPages'>\n) {\n if (!context?.relevantPages) {\n return undefined\n }\n\n const lastPageReached = context.relevantPages.at(-1)\n const penultimatePageReached = context.relevantPages.at(-2)\n\n if (\n lastPageReached instanceof TerminalPageController &&\n penultimatePageReached\n ) {\n return penultimatePageReached\n }\n\n return lastPageReached\n}\n\nfunction resolveState(state: JourneyState): FormStatus {\n return state === 'preview' ? FormStatus.Live : state\n}\n\nfunction isPreviewState(\n state: JourneyState,\n options: FormModelOptions = {}\n): boolean {\n return options.isPreview ?? state === 'preview'\n}\n"],"mappings":"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","ignoreList":[]}
@@ -3,7 +3,7 @@ import { plugin } from '../../../server/plugins/engine/plugin.js';
3
3
  import { type PluginOptions } from '../../../server/plugins/engine/types.js';
4
4
  export { getPageHref } from '../../../server/plugins/engine/helpers.js';
5
5
  export { context } from '../../../server/plugins/nunjucks/context.js';
6
- export { getFirstJourneyPage, getFormContext, getFormModel, resolveFormModel } from '../../../server/plugins/engine/beta/form-context.js';
6
+ export { getFirstJourneyPage, getFormContext, getFormModel, resolveFormModel } from '../../../server/plugins/engine/form-context.js';
7
7
  export * from '../../../server/plugins/engine/form-availability.js';
8
8
  export declare const VIEW_PATH = "src/server/plugins/engine/views";
9
9
  export declare const PLUGIN_PATH = "node_modules/@defra/forms-engine-plugin";
@@ -5,7 +5,7 @@ import { checkComponentTemplates, checkErrorTemplates, evaluate, govukRebrand }
5
5
  import * as filters from "../nunjucks/filters/index.js";
6
6
  export { getPageHref } from "./helpers.js";
7
7
  export { context } from "../nunjucks/context.js";
8
- export { getFirstJourneyPage, getFormContext, getFormModel, resolveFormModel } from "./beta/form-context.js";
8
+ export { getFirstJourneyPage, getFormContext, getFormModel, resolveFormModel } from "./form-context.js";
9
9
  export * from "./form-availability.js";
10
10
  const globals = {
11
11
  checkComponentTemplates,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["markdownToHtml","engine","plugin","checkComponentTemplates","checkErrorTemplates","evaluate","govukRebrand","filters","getPageHref","context","getFirstJourneyPage","getFormContext","getFormModel","resolveFormModel","globals","VIEW_PATH","PLUGIN_PATH","STATE_NOT_YET_VALIDATED","CURRENT_PAGE_PATH_KEY","MAGIC_LINK_GROUP_ID","prepareNunjucksEnvironment","env","pluginOptions","name","nunjucksFilter","Object","entries","addFilter","text","startingHeaderLevel","baseUrl","nunjucksGlobal","addGlobal","filter","registerFilter","forEach","fn"],"sources":["../../../../src/server/plugins/engine/index.ts"],"sourcesContent":["import { markdownToHtml } from '@defra/forms-model'\nimport { type Environment } from 'nunjucks'\n\nimport { engine } from '~/src/server/plugins/engine/helpers.js'\nimport { plugin } from '~/src/server/plugins/engine/plugin.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport {\n checkComponentTemplates,\n checkErrorTemplates,\n evaluate,\n govukRebrand\n} from '~/src/server/plugins/nunjucks/environment.js'\nimport * as filters from '~/src/server/plugins/nunjucks/filters/index.js'\n\nexport { getPageHref } from '~/src/server/plugins/engine/helpers.js'\nexport { context } from '~/src/server/plugins/nunjucks/context.js'\nexport {\n getFirstJourneyPage,\n getFormContext,\n getFormModel,\n resolveFormModel\n} from '~/src/server/plugins/engine/beta/form-context.js'\nexport * from '~/src/server/plugins/engine/form-availability.js'\n\nconst globals = {\n checkComponentTemplates,\n checkErrorTemplates,\n evaluate,\n govukRebrand\n}\n\nexport const VIEW_PATH = 'src/server/plugins/engine/views'\nexport const PLUGIN_PATH = 'node_modules/@defra/forms-engine-plugin'\n\nexport const STATE_NOT_YET_VALIDATED = '__stateNotYetValidated'\nexport const CURRENT_PAGE_PATH_KEY = '__currentPagePath'\nexport const MAGIC_LINK_GROUP_ID = '__magicLinkGroupId'\n\nexport const prepareNunjucksEnvironment = function (\n env: Environment,\n pluginOptions: PluginOptions\n) {\n for (const [name, nunjucksFilter] of Object.entries(filters)) {\n env.addFilter(name, nunjucksFilter)\n }\n\n env.addFilter('markdown', (text: string, startingHeaderLevel?: number) =>\n markdownToHtml(text, {\n baseUrl: pluginOptions.baseUrl,\n startingHeaderLevel\n })\n )\n\n for (const [name, nunjucksGlobal] of Object.entries(globals)) {\n env.addGlobal(name, nunjucksGlobal)\n }\n\n // Apply any additional filters to both the liquid and nunjucks engines\n if (pluginOptions.filters) {\n for (const [name, filter] of Object.entries(pluginOptions.filters)) {\n env.addFilter(name, filter)\n engine.registerFilter(name, filter)\n }\n }\n\n // Apply any additional globals to nunjucks engines\n if (pluginOptions.globals) {\n Object.entries(pluginOptions.globals).forEach(([name, fn]) => {\n env.addGlobal(name, fn)\n })\n }\n}\n\nexport default plugin\n"],"mappings":"AAAA,SAASA,cAAc,QAAQ,oBAAoB;AAGnD,SAASC,MAAM;AACf,SAASC,MAAM;AAEf,SACEC,uBAAuB,EACvBC,mBAAmB,EACnBC,QAAQ,EACRC,YAAY;AAEd,OAAO,KAAKC,OAAO;AAEnB,SAASC,WAAW;AACpB,SAASC,OAAO;AAChB,SACEC,mBAAmB,EACnBC,cAAc,EACdC,YAAY,EACZC,gBAAgB;AAElB;AAEA,MAAMC,OAAO,GAAG;EACdX,uBAAuB;EACvBC,mBAAmB;EACnBC,QAAQ;EACRC;AACF,CAAC;AAED,OAAO,MAAMS,SAAS,GAAG,iCAAiC;AAC1D,OAAO,MAAMC,WAAW,GAAG,yCAAyC;AAEpE,OAAO,MAAMC,uBAAuB,GAAG,wBAAwB;AAC/D,OAAO,MAAMC,qBAAqB,GAAG,mBAAmB;AACxD,OAAO,MAAMC,mBAAmB,GAAG,oBAAoB;AAEvD,OAAO,MAAMC,0BAA0B,GAAG,SAAAA,CACxCC,GAAgB,EAChBC,aAA4B,EAC5B;EACA,KAAK,MAAM,CAACC,IAAI,EAAEC,cAAc,CAAC,IAAIC,MAAM,CAACC,OAAO,CAACnB,OAAO,CAAC,EAAE;IAC5Dc,GAAG,CAACM,SAAS,CAACJ,IAAI,EAAEC,cAAc,CAAC;EACrC;EAEAH,GAAG,CAACM,SAAS,CAAC,UAAU,EAAE,CAACC,IAAY,EAAEC,mBAA4B,KACnE7B,cAAc,CAAC4B,IAAI,EAAE;IACnBE,OAAO,EAAER,aAAa,CAACQ,OAAO;IAC9BD;EACF,CAAC,CACH,CAAC;EAED,KAAK,MAAM,CAACN,IAAI,EAAEQ,cAAc,CAAC,IAAIN,MAAM,CAACC,OAAO,CAACZ,OAAO,CAAC,EAAE;IAC5DO,GAAG,CAACW,SAAS,CAACT,IAAI,EAAEQ,cAAc,CAAC;EACrC;;EAEA;EACA,IAAIT,aAAa,CAACf,OAAO,EAAE;IACzB,KAAK,MAAM,CAACgB,IAAI,EAAEU,MAAM,CAAC,IAAIR,MAAM,CAACC,OAAO,CAACJ,aAAa,CAACf,OAAO,CAAC,EAAE;MAClEc,GAAG,CAACM,SAAS,CAACJ,IAAI,EAAEU,MAAM,CAAC;MAC3BhC,MAAM,CAACiC,cAAc,CAACX,IAAI,EAAEU,MAAM,CAAC;IACrC;EACF;;EAEA;EACA,IAAIX,aAAa,CAACR,OAAO,EAAE;IACzBW,MAAM,CAACC,OAAO,CAACJ,aAAa,CAACR,OAAO,CAAC,CAACqB,OAAO,CAAC,CAAC,CAACZ,IAAI,EAAEa,EAAE,CAAC,KAAK;MAC5Df,GAAG,CAACW,SAAS,CAACT,IAAI,EAAEa,EAAE,CAAC;IACzB,CAAC,CAAC;EACJ;AACF,CAAC;AAED,eAAelC,MAAM","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["markdownToHtml","engine","plugin","checkComponentTemplates","checkErrorTemplates","evaluate","govukRebrand","filters","getPageHref","context","getFirstJourneyPage","getFormContext","getFormModel","resolveFormModel","globals","VIEW_PATH","PLUGIN_PATH","STATE_NOT_YET_VALIDATED","CURRENT_PAGE_PATH_KEY","MAGIC_LINK_GROUP_ID","prepareNunjucksEnvironment","env","pluginOptions","name","nunjucksFilter","Object","entries","addFilter","text","startingHeaderLevel","baseUrl","nunjucksGlobal","addGlobal","filter","registerFilter","forEach","fn"],"sources":["../../../../src/server/plugins/engine/index.ts"],"sourcesContent":["import { markdownToHtml } from '@defra/forms-model'\nimport { type Environment } from 'nunjucks'\n\nimport { engine } from '~/src/server/plugins/engine/helpers.js'\nimport { plugin } from '~/src/server/plugins/engine/plugin.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport {\n checkComponentTemplates,\n checkErrorTemplates,\n evaluate,\n govukRebrand\n} from '~/src/server/plugins/nunjucks/environment.js'\nimport * as filters from '~/src/server/plugins/nunjucks/filters/index.js'\n\nexport { getPageHref } from '~/src/server/plugins/engine/helpers.js'\nexport { context } from '~/src/server/plugins/nunjucks/context.js'\nexport {\n getFirstJourneyPage,\n getFormContext,\n getFormModel,\n resolveFormModel\n} from '~/src/server/plugins/engine/form-context.js'\nexport * from '~/src/server/plugins/engine/form-availability.js'\n\nconst globals = {\n checkComponentTemplates,\n checkErrorTemplates,\n evaluate,\n govukRebrand\n}\n\nexport const VIEW_PATH = 'src/server/plugins/engine/views'\nexport const PLUGIN_PATH = 'node_modules/@defra/forms-engine-plugin'\n\nexport const STATE_NOT_YET_VALIDATED = '__stateNotYetValidated'\nexport const CURRENT_PAGE_PATH_KEY = '__currentPagePath'\nexport const MAGIC_LINK_GROUP_ID = '__magicLinkGroupId'\n\nexport const prepareNunjucksEnvironment = function (\n env: Environment,\n pluginOptions: PluginOptions\n) {\n for (const [name, nunjucksFilter] of Object.entries(filters)) {\n env.addFilter(name, nunjucksFilter)\n }\n\n env.addFilter('markdown', (text: string, startingHeaderLevel?: number) =>\n markdownToHtml(text, {\n baseUrl: pluginOptions.baseUrl,\n startingHeaderLevel\n })\n )\n\n for (const [name, nunjucksGlobal] of Object.entries(globals)) {\n env.addGlobal(name, nunjucksGlobal)\n }\n\n // Apply any additional filters to both the liquid and nunjucks engines\n if (pluginOptions.filters) {\n for (const [name, filter] of Object.entries(pluginOptions.filters)) {\n env.addFilter(name, filter)\n engine.registerFilter(name, filter)\n }\n }\n\n // Apply any additional globals to nunjucks engines\n if (pluginOptions.globals) {\n Object.entries(pluginOptions.globals).forEach(([name, fn]) => {\n env.addGlobal(name, fn)\n })\n }\n}\n\nexport default plugin\n"],"mappings":"AAAA,SAASA,cAAc,QAAQ,oBAAoB;AAGnD,SAASC,MAAM;AACf,SAASC,MAAM;AAEf,SACEC,uBAAuB,EACvBC,mBAAmB,EACnBC,QAAQ,EACRC,YAAY;AAEd,OAAO,KAAKC,OAAO;AAEnB,SAASC,WAAW;AACpB,SAASC,OAAO;AAChB,SACEC,mBAAmB,EACnBC,cAAc,EACdC,YAAY,EACZC,gBAAgB;AAElB;AAEA,MAAMC,OAAO,GAAG;EACdX,uBAAuB;EACvBC,mBAAmB;EACnBC,QAAQ;EACRC;AACF,CAAC;AAED,OAAO,MAAMS,SAAS,GAAG,iCAAiC;AAC1D,OAAO,MAAMC,WAAW,GAAG,yCAAyC;AAEpE,OAAO,MAAMC,uBAAuB,GAAG,wBAAwB;AAC/D,OAAO,MAAMC,qBAAqB,GAAG,mBAAmB;AACxD,OAAO,MAAMC,mBAAmB,GAAG,oBAAoB;AAEvD,OAAO,MAAMC,0BAA0B,GAAG,SAAAA,CACxCC,GAAgB,EAChBC,aAA4B,EAC5B;EACA,KAAK,MAAM,CAACC,IAAI,EAAEC,cAAc,CAAC,IAAIC,MAAM,CAACC,OAAO,CAACnB,OAAO,CAAC,EAAE;IAC5Dc,GAAG,CAACM,SAAS,CAACJ,IAAI,EAAEC,cAAc,CAAC;EACrC;EAEAH,GAAG,CAACM,SAAS,CAAC,UAAU,EAAE,CAACC,IAAY,EAAEC,mBAA4B,KACnE7B,cAAc,CAAC4B,IAAI,EAAE;IACnBE,OAAO,EAAER,aAAa,CAACQ,OAAO;IAC9BD;EACF,CAAC,CACH,CAAC;EAED,KAAK,MAAM,CAACN,IAAI,EAAEQ,cAAc,CAAC,IAAIN,MAAM,CAACC,OAAO,CAACZ,OAAO,CAAC,EAAE;IAC5DO,GAAG,CAACW,SAAS,CAACT,IAAI,EAAEQ,cAAc,CAAC;EACrC;;EAEA;EACA,IAAIT,aAAa,CAACf,OAAO,EAAE;IACzB,KAAK,MAAM,CAACgB,IAAI,EAAEU,MAAM,CAAC,IAAIR,MAAM,CAACC,OAAO,CAACJ,aAAa,CAACf,OAAO,CAAC,EAAE;MAClEc,GAAG,CAACM,SAAS,CAACJ,IAAI,EAAEU,MAAM,CAAC;MAC3BhC,MAAM,CAACiC,cAAc,CAACX,IAAI,EAAEU,MAAM,CAAC;IACrC;EACF;;EAEA;EACA,IAAIX,aAAa,CAACR,OAAO,EAAE;IACzBW,MAAM,CAACC,OAAO,CAACJ,aAAa,CAACR,OAAO,CAAC,CAACqB,OAAO,CAAC,CAAC,CAACZ,IAAI,EAAEa,EAAE,CAAC,KAAK;MAC5Df,GAAG,CAACW,SAAS,CAACT,IAAI,EAAEa,EAAE,CAAC;IACzB,CAAC,CAAC;EACJ;AACF,CAAC;AAED,eAAelC,MAAM","ignoreList":[]}
@@ -1,8 +1,8 @@
1
- import { type FormMetadata } from '@defra/forms-model';
1
+ import { type FormMetadata, type FormMetadataContact } from '@defra/forms-model';
2
2
  export interface UnavailableViewModel {
3
3
  pageTitle: string;
4
4
  formTitle: string;
5
5
  organisationName: string;
6
- phoneLines?: string[];
6
+ contact?: FormMetadataContact;
7
7
  }
8
8
  export declare function unavailableViewModel(metadata: FormMetadata): UnavailableViewModel;
@@ -1,21 +1,9 @@
1
- /**
2
- * Defra organisations carry an abbreviation suffix on the enum value, e.g.
3
- * "Rural Payments Agency – RPA". The unavailable page reads cleanly without it.
4
- */
5
- function stripOrgSuffix(organisation) {
6
- return organisation.split(' – ')[0];
7
- }
8
- function splitPhoneLines(phone) {
9
- if (!phone) return undefined;
10
- const lines = phone.split('\n').map(line => line.trim()).filter(line => line.length > 0);
11
- return lines.length > 0 ? lines : undefined;
12
- }
13
1
  export function unavailableViewModel(metadata) {
14
2
  return {
15
3
  pageTitle: 'Sorry, this form is unavailable',
16
4
  formTitle: metadata.title,
17
- organisationName: stripOrgSuffix(metadata.organisation),
18
- phoneLines: splitPhoneLines(metadata.contact?.phone)
5
+ organisationName: metadata.organisation,
6
+ contact: metadata.contact
19
7
  };
20
8
  }
21
9
  //# sourceMappingURL=unavailable-view-model.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"unavailable-view-model.js","names":["stripOrgSuffix","organisation","split","splitPhoneLines","phone","undefined","lines","map","line","trim","filter","length","unavailableViewModel","metadata","pageTitle","formTitle","title","organisationName","phoneLines","contact"],"sources":["../../../../../src/server/plugins/engine/models/unavailable-view-model.ts"],"sourcesContent":["import { type FormMetadata } from '@defra/forms-model'\n\nexport interface UnavailableViewModel {\n pageTitle: string\n formTitle: string\n organisationName: string\n phoneLines?: string[]\n}\n\n/**\n * Defra organisations carry an abbreviation suffix on the enum value, e.g.\n * \"Rural Payments Agency – RPA\". The unavailable page reads cleanly without it.\n */\nfunction stripOrgSuffix(organisation: string) {\n return organisation.split(' – ')[0]\n}\n\nfunction splitPhoneLines(phone: string | undefined) {\n if (!phone) return undefined\n const lines = phone\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line.length > 0)\n return lines.length > 0 ? lines : undefined\n}\n\nexport function unavailableViewModel(\n metadata: FormMetadata\n): UnavailableViewModel {\n return {\n pageTitle: 'Sorry, this form is unavailable',\n formTitle: metadata.title,\n organisationName: stripOrgSuffix(metadata.organisation),\n phoneLines: splitPhoneLines(metadata.contact?.phone)\n }\n}\n"],"mappings":"AASA;AACA;AACA;AACA;AACA,SAASA,cAAcA,CAACC,YAAoB,EAAE;EAC5C,OAAOA,YAAY,CAACC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACrC;AAEA,SAASC,eAAeA,CAACC,KAAyB,EAAE;EAClD,IAAI,CAACA,KAAK,EAAE,OAAOC,SAAS;EAC5B,MAAMC,KAAK,GAAGF,KAAK,CAChBF,KAAK,CAAC,IAAI,CAAC,CACXK,GAAG,CAAEC,IAAI,IAAKA,IAAI,CAACC,IAAI,CAAC,CAAC,CAAC,CAC1BC,MAAM,CAAEF,IAAI,IAAKA,IAAI,CAACG,MAAM,GAAG,CAAC,CAAC;EACpC,OAAOL,KAAK,CAACK,MAAM,GAAG,CAAC,GAAGL,KAAK,GAAGD,SAAS;AAC7C;AAEA,OAAO,SAASO,oBAAoBA,CAClCC,QAAsB,EACA;EACtB,OAAO;IACLC,SAAS,EAAE,iCAAiC;IAC5CC,SAAS,EAAEF,QAAQ,CAACG,KAAK;IACzBC,gBAAgB,EAAEjB,cAAc,CAACa,QAAQ,CAACZ,YAAY,CAAC;IACvDiB,UAAU,EAAEf,eAAe,CAACU,QAAQ,CAACM,OAAO,EAAEf,KAAK;EACrD,CAAC;AACH","ignoreList":[]}
1
+ {"version":3,"file":"unavailable-view-model.js","names":["unavailableViewModel","metadata","pageTitle","formTitle","title","organisationName","organisation","contact"],"sources":["../../../../../src/server/plugins/engine/models/unavailable-view-model.ts"],"sourcesContent":["import { type FormMetadata, type FormMetadataContact } from '@defra/forms-model'\n\nexport interface UnavailableViewModel {\n pageTitle: string\n formTitle: string\n organisationName: string\n contact?: FormMetadataContact\n}\n\nexport function unavailableViewModel(\n metadata: FormMetadata\n): UnavailableViewModel {\n return {\n pageTitle: 'Sorry, this form is unavailable',\n formTitle: metadata.title,\n organisationName: metadata.organisation,\n contact: metadata.contact\n }\n}\n"],"mappings":"AASA,OAAO,SAASA,oBAAoBA,CAClCC,QAAsB,EACA;EACtB,OAAO;IACLC,SAAS,EAAE,iCAAiC;IAC5CC,SAAS,EAAEF,QAAQ,CAACG,KAAK;IACzBC,gBAAgB,EAAEJ,QAAQ,CAACK,YAAY;IACvCC,OAAO,EAAEN,QAAQ,CAACM;EACpB,CAAC;AACH","ignoreList":[]}
@@ -1,8 +1,8 @@
1
1
  import { isPaymentPage } from '@defra/forms-model';
2
2
  import Boom from '@hapi/boom';
3
3
  import { EXTERNAL_STATE_APPENDAGE, EXTERNAL_STATE_PAYLOAD } from "../../../constants.js";
4
- import { resolveFormModel } from "../beta/form-context.js";
5
4
  import { FormComponent, isFormState } from "../components/FormComponent.js";
5
+ import { resolveFormModel } from "../form-context.js";
6
6
  import { checkFormStatus, findPage, getCacheService, getPage, getStartPath, proceed } from "../helpers.js";
7
7
  import { checkSaveAndExitRepeater, copyNotYetValidatedState } from "../pageControllers/helpers/state.js";
8
8
  import { generateUniqueReference } from "../referenceNumbers.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["isPaymentPage","Boom","EXTERNAL_STATE_APPENDAGE","EXTERNAL_STATE_PAYLOAD","resolveFormModel","FormComponent","isFormState","checkFormStatus","findPage","getCacheService","getPage","getStartPath","proceed","checkSaveAndExitRepeater","copyNotYetValidatedState","generateUniqueReference","defaultServices","redirectOrMakeHandler","request","h","onRequest","makeHandler","app","params","model","notFound","path","cacheService","server","page","state","getState","$$__referenceNumber","prefix","def","metadata","referenceNumberPrefix","badImplementation","referenceNumber","mergeState","importExternalComponentState","flash","getFlash","context","getFormContext","errors","relevantPath","getRelevantPath","summaryPath","getSummaryPath","result","continue","resumeInRepeaterUrl","startsWith","pageDef","isForceAccess","redirectTo","next","length","query","returnUrl","getHref","externalComponentData","yar","Array","isArray","typedStateAppendage","componentName","component","stateAppendage","data","componentMap","get","Error","TypeError","isStateValid","isState","componentState","isAppendageStateSingleObject","Object","fromEntries","entries","map","key","value","savedState","payload","stashedPayload","localState","getStateFromValidForm","makeLoadFormPreHandler","options","realm","modifiers","route","services","controllers","ordnanceSurveyApiKey","handler","slug","isPreview","formState","routePrefix","dispatchHandler","servicePath","basePath"],"sources":["../../../../../src/server/plugins/engine/routes/index.ts"],"sourcesContent":["import { isPaymentPage } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type ResponseObject,\n type ResponseToolkit,\n type Server\n} from '@hapi/hapi'\n\nimport {\n EXTERNAL_STATE_APPENDAGE,\n EXTERNAL_STATE_PAYLOAD\n} from '~/src/server/constants.js'\nimport { resolveFormModel } from '~/src/server/plugins/engine/beta/form-context.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n checkFormStatus,\n findPage,\n getCacheService,\n getPage,\n getStartPath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n checkSaveAndExitRepeater,\n copyNotYetValidatedState\n} from '~/src/server/plugins/engine/pageControllers/helpers/state.js'\nimport { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type AnyFormRequest,\n type ExternalStateAppendage,\n type FormContext,\n type FormPayload,\n type FormSubmissionState,\n type OnRequestCallback,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport async function redirectOrMakeHandler(\n request: AnyFormRequest,\n h: FormResponseToolkit,\n onRequest: OnRequestCallback | undefined,\n makeHandler: (\n page: PageControllerClass,\n context: FormContext\n ) => ResponseObject | Promise<ResponseObject>\n) {\n const { app, params } = request\n const { model } = app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n const cacheService = getCacheService(request.server)\n const page = getPage(model, request)\n let state = await page.getState(request)\n\n if (!state.$$__referenceNumber) {\n const prefix = model.def.metadata?.referenceNumberPrefix ?? ''\n\n if (typeof prefix !== 'string') {\n throw Boom.badImplementation(\n 'Reference number prefix must be a string or undefined'\n )\n }\n\n const referenceNumber = generateUniqueReference(prefix)\n state = await page.mergeState(request, state, {\n $$__referenceNumber: referenceNumber\n })\n }\n\n state = await importExternalComponentState(request, page, state)\n\n const flash = cacheService.getFlash(request)\n const context = model.getFormContext(request, state, flash?.errors)\n\n await copyNotYetValidatedState(request, context)\n\n const relevantPath = page.getRelevantPath(request, context)\n const summaryPath = page.getSummaryPath()\n\n // Call the onRequest callback if it has been supplied\n if (onRequest) {\n const result = await onRequest(request, h, context)\n if (result !== h.continue) {\n return result\n }\n }\n\n // Check whether save-and-exit should resume from within a repeater\n const resumeInRepeaterUrl = checkSaveAndExitRepeater(context, model)\n if (resumeInRepeaterUrl) {\n return proceed(request, h, resumeInRepeaterUrl)\n }\n\n // Return handler for relevant pages, payment pages, or preview URL direct access.\n // Payment pages are skipped in the normal page walk but must render when the user\n // is redirected there from CYA \"Pay and submit\".\n if (\n relevantPath.startsWith(page.path) ||\n isPaymentPage(page.pageDef) ||\n context.isForceAccess\n ) {\n return makeHandler(page, context)\n }\n\n // Redirect back to last relevant page\n const redirectTo = findPage(model, relevantPath)\n // Set the return URL unless an exit page\n if (redirectTo?.next.length) {\n request.query.returnUrl = page.getHref(summaryPath)\n }\n\n return proceed(request, h, page.getHref(relevantPath))\n}\n\nasync function importExternalComponentState(\n request: AnyFormRequest,\n page: PageControllerClass,\n state: FormSubmissionState\n): Promise<FormSubmissionState> {\n const externalComponentData = request.yar.flash(EXTERNAL_STATE_APPENDAGE)\n\n if (Array.isArray(externalComponentData)) {\n return state\n }\n\n const typedStateAppendage = externalComponentData as ExternalStateAppendage\n const componentName = typedStateAppendage.component\n const stateAppendage = typedStateAppendage.data\n\n const component = request.app.model?.componentMap.get(componentName)\n\n if (!component) {\n throw new Error(`Component ${componentName} not found in form`)\n }\n\n if (!(component instanceof FormComponent)) {\n throw new TypeError(\n `Component ${componentName} is not a FormComponent and does not support isState`\n )\n }\n\n const isStateValid = component.isState(stateAppendage)\n\n if (!isStateValid) {\n throw new Error(`State for component ${componentName} is invalid`)\n }\n\n // Create state structure from appendage state\n // Some components use a record structure with properties of the format of '<compName>__<fieldName>'\n // e.g. UKAddressField\n // Some components use a single object structure e.g. PaymentField\n const componentState =\n isFormState(stateAppendage) && !component.isAppendageStateSingleObject\n ? Object.fromEntries(\n Object.entries(stateAppendage).map(([key, value]) => [\n `${componentName}__${key}`,\n value\n ])\n )\n : { [componentName]: stateAppendage }\n\n // Save the external component state directly (already has correct key format)\n const savedState = await page.mergeState(request, state, componentState)\n\n // Merge any stashed payload into the local state\n const payload = request.yar.flash(EXTERNAL_STATE_PAYLOAD)\n const stashedPayload = Array.isArray(payload) ? {} : (payload as FormPayload)\n\n const localState = page.getStateFromValidForm(request, savedState, {\n ...stashedPayload,\n ...componentState\n } as FormPayload)\n\n return { ...savedState, ...localState }\n}\n\nexport function makeLoadFormPreHandler(server: Server, options: PluginOptions) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hapi types are wrong\n const prefix = server.realm.modifiers.route.prefix ?? ''\n\n const {\n services = defaultServices,\n controllers,\n ordnanceSurveyApiKey\n } = options\n\n async function handler(request: AnyFormRequest, h: ResponseToolkit) {\n if (server.app.model) {\n request.app.model = server.app.model\n\n return h.continue\n }\n\n const { params } = request\n const { slug } = params\n const { isPreview, state: formState } = checkFormStatus(params)\n\n const model = await resolveFormModel(server, slug, formState, {\n services,\n controllers,\n ordnanceSurveyApiKey,\n routePrefix: prefix,\n isPreview\n })\n\n request.app.model = model\n\n return h.continue\n }\n\n return handler\n}\n\nexport function dispatchHandler(request: FormRequest, h: FormResponseToolkit) {\n const { model } = request.app\n\n const servicePath = model ? `/${model.basePath}` : ''\n return proceed(request, h, `${servicePath}${getStartPath(model)}`)\n}\n"],"mappings":"AAAA,SAASA,aAAa,QAAQ,oBAAoB;AAClD,OAAOC,IAAI,MAAM,YAAY;AAO7B,SACEC,wBAAwB,EACxBC,sBAAsB;AAExB,SAASC,gBAAgB;AACzB,SACEC,aAAa,EACbC,WAAW;AAEb,SACEC,eAAe,EACfC,QAAQ,EACRC,eAAe,EACfC,OAAO,EACPC,YAAY,EACZC,OAAO;AAGT,SACEC,wBAAwB,EACxBC,wBAAwB;AAE1B,SAASC,uBAAuB;AAChC,OAAO,KAAKC,eAAe;AAe3B,OAAO,eAAeC,qBAAqBA,CACzCC,OAAuB,EACvBC,CAAsB,EACtBC,SAAwC,EACxCC,WAG6C,EAC7C;EACA,MAAM;IAAEC,GAAG;IAAEC;EAAO,CAAC,GAAGL,OAAO;EAC/B,MAAM;IAAEM;EAAM,CAAC,GAAGF,GAAG;EAErB,IAAI,CAACE,KAAK,EAAE;IACV,MAAMvB,IAAI,CAACwB,QAAQ,CAAC,uBAAuBF,MAAM,CAACG,IAAI,EAAE,CAAC;EAC3D;EAEA,MAAMC,YAAY,GAAGlB,eAAe,CAACS,OAAO,CAACU,MAAM,CAAC;EACpD,MAAMC,IAAI,GAAGnB,OAAO,CAACc,KAAK,EAAEN,OAAO,CAAC;EACpC,IAAIY,KAAK,GAAG,MAAMD,IAAI,CAACE,QAAQ,CAACb,OAAO,CAAC;EAExC,IAAI,CAACY,KAAK,CAACE,mBAAmB,EAAE;IAC9B,MAAMC,MAAM,GAAGT,KAAK,CAACU,GAAG,CAACC,QAAQ,EAAEC,qBAAqB,IAAI,EAAE;IAE9D,IAAI,OAAOH,MAAM,KAAK,QAAQ,EAAE;MAC9B,MAAMhC,IAAI,CAACoC,iBAAiB,CAC1B,uDACF,CAAC;IACH;IAEA,MAAMC,eAAe,GAAGvB,uBAAuB,CAACkB,MAAM,CAAC;IACvDH,KAAK,GAAG,MAAMD,IAAI,CAACU,UAAU,CAACrB,OAAO,EAAEY,KAAK,EAAE;MAC5CE,mBAAmB,EAAEM;IACvB,CAAC,CAAC;EACJ;EAEAR,KAAK,GAAG,MAAMU,4BAA4B,CAACtB,OAAO,EAAEW,IAAI,EAAEC,KAAK,CAAC;EAEhE,MAAMW,KAAK,GAAGd,YAAY,CAACe,QAAQ,CAACxB,OAAO,CAAC;EAC5C,MAAMyB,OAAO,GAAGnB,KAAK,CAACoB,cAAc,CAAC1B,OAAO,EAAEY,KAAK,EAAEW,KAAK,EAAEI,MAAM,CAAC;EAEnE,MAAM/B,wBAAwB,CAACI,OAAO,EAAEyB,OAAO,CAAC;EAEhD,MAAMG,YAAY,GAAGjB,IAAI,CAACkB,eAAe,CAAC7B,OAAO,EAAEyB,OAAO,CAAC;EAC3D,MAAMK,WAAW,GAAGnB,IAAI,CAACoB,cAAc,CAAC,CAAC;;EAEzC;EACA,IAAI7B,SAAS,EAAE;IACb,MAAM8B,MAAM,GAAG,MAAM9B,SAAS,CAACF,OAAO,EAAEC,CAAC,EAAEwB,OAAO,CAAC;IACnD,IAAIO,MAAM,KAAK/B,CAAC,CAACgC,QAAQ,EAAE;MACzB,OAAOD,MAAM;IACf;EACF;;EAEA;EACA,MAAME,mBAAmB,GAAGvC,wBAAwB,CAAC8B,OAAO,EAAEnB,KAAK,CAAC;EACpE,IAAI4B,mBAAmB,EAAE;IACvB,OAAOxC,OAAO,CAACM,OAAO,EAAEC,CAAC,EAAEiC,mBAAmB,CAAC;EACjD;;EAEA;EACA;EACA;EACA,IACEN,YAAY,CAACO,UAAU,CAACxB,IAAI,CAACH,IAAI,CAAC,IAClC1B,aAAa,CAAC6B,IAAI,CAACyB,OAAO,CAAC,IAC3BX,OAAO,CAACY,aAAa,EACrB;IACA,OAAOlC,WAAW,CAACQ,IAAI,EAAEc,OAAO,CAAC;EACnC;;EAEA;EACA,MAAMa,UAAU,GAAGhD,QAAQ,CAACgB,KAAK,EAAEsB,YAAY,CAAC;EAChD;EACA,IAAIU,UAAU,EAAEC,IAAI,CAACC,MAAM,EAAE;IAC3BxC,OAAO,CAACyC,KAAK,CAACC,SAAS,GAAG/B,IAAI,CAACgC,OAAO,CAACb,WAAW,CAAC;EACrD;EAEA,OAAOpC,OAAO,CAACM,OAAO,EAAEC,CAAC,EAAEU,IAAI,CAACgC,OAAO,CAACf,YAAY,CAAC,CAAC;AACxD;AAEA,eAAeN,4BAA4BA,CACzCtB,OAAuB,EACvBW,IAAyB,EACzBC,KAA0B,EACI;EAC9B,MAAMgC,qBAAqB,GAAG5C,OAAO,CAAC6C,GAAG,CAACtB,KAAK,CAACvC,wBAAwB,CAAC;EAEzE,IAAI8D,KAAK,CAACC,OAAO,CAACH,qBAAqB,CAAC,EAAE;IACxC,OAAOhC,KAAK;EACd;EAEA,MAAMoC,mBAAmB,GAAGJ,qBAA+C;EAC3E,MAAMK,aAAa,GAAGD,mBAAmB,CAACE,SAAS;EACnD,MAAMC,cAAc,GAAGH,mBAAmB,CAACI,IAAI;EAE/C,MAAMF,SAAS,GAAGlD,OAAO,CAACI,GAAG,CAACE,KAAK,EAAE+C,YAAY,CAACC,GAAG,CAACL,aAAa,CAAC;EAEpE,IAAI,CAACC,SAAS,EAAE;IACd,MAAM,IAAIK,KAAK,CAAC,aAAaN,aAAa,oBAAoB,CAAC;EACjE;EAEA,IAAI,EAAEC,SAAS,YAAY/D,aAAa,CAAC,EAAE;IACzC,MAAM,IAAIqE,SAAS,CACjB,aAAaP,aAAa,sDAC5B,CAAC;EACH;EAEA,MAAMQ,YAAY,GAAGP,SAAS,CAACQ,OAAO,CAACP,cAAc,CAAC;EAEtD,IAAI,CAACM,YAAY,EAAE;IACjB,MAAM,IAAIF,KAAK,CAAC,uBAAuBN,aAAa,aAAa,CAAC;EACpE;;EAEA;EACA;EACA;EACA;EACA,MAAMU,cAAc,GAClBvE,WAAW,CAAC+D,cAAc,CAAC,IAAI,CAACD,SAAS,CAACU,4BAA4B,GAClEC,MAAM,CAACC,WAAW,CAChBD,MAAM,CAACE,OAAO,CAACZ,cAAc,CAAC,CAACa,GAAG,CAAC,CAAC,CAACC,GAAG,EAAEC,KAAK,CAAC,KAAK,CACnD,GAAGjB,aAAa,KAAKgB,GAAG,EAAE,EAC1BC,KAAK,CACN,CACH,CAAC,GACD;IAAE,CAACjB,aAAa,GAAGE;EAAe,CAAC;;EAEzC;EACA,MAAMgB,UAAU,GAAG,MAAMxD,IAAI,CAACU,UAAU,CAACrB,OAAO,EAAEY,KAAK,EAAE+C,cAAc,CAAC;;EAExE;EACA,MAAMS,OAAO,GAAGpE,OAAO,CAAC6C,GAAG,CAACtB,KAAK,CAACtC,sBAAsB,CAAC;EACzD,MAAMoF,cAAc,GAAGvB,KAAK,CAACC,OAAO,CAACqB,OAAO,CAAC,GAAG,CAAC,CAAC,GAAIA,OAAuB;EAE7E,MAAME,UAAU,GAAG3D,IAAI,CAAC4D,qBAAqB,CAACvE,OAAO,EAAEmE,UAAU,EAAE;IACjE,GAAGE,cAAc;IACjB,GAAGV;EACL,CAAgB,CAAC;EAEjB,OAAO;IAAE,GAAGQ,UAAU;IAAE,GAAGG;EAAW,CAAC;AACzC;AAEA,OAAO,SAASE,sBAAsBA,CAAC9D,MAAc,EAAE+D,OAAsB,EAAE;EAC7E;EACA,MAAM1D,MAAM,GAAGL,MAAM,CAACgE,KAAK,CAACC,SAAS,CAACC,KAAK,CAAC7D,MAAM,IAAI,EAAE;EAExD,MAAM;IACJ8D,QAAQ,GAAG/E,eAAe;IAC1BgF,WAAW;IACXC;EACF,CAAC,GAAGN,OAAO;EAEX,eAAeO,OAAOA,CAAChF,OAAuB,EAAEC,CAAkB,EAAE;IAClE,IAAIS,MAAM,CAACN,GAAG,CAACE,KAAK,EAAE;MACpBN,OAAO,CAACI,GAAG,CAACE,KAAK,GAAGI,MAAM,CAACN,GAAG,CAACE,KAAK;MAEpC,OAAOL,CAAC,CAACgC,QAAQ;IACnB;IAEA,MAAM;MAAE5B;IAAO,CAAC,GAAGL,OAAO;IAC1B,MAAM;MAAEiF;IAAK,CAAC,GAAG5E,MAAM;IACvB,MAAM;MAAE6E,SAAS;MAAEtE,KAAK,EAAEuE;IAAU,CAAC,GAAG9F,eAAe,CAACgB,MAAM,CAAC;IAE/D,MAAMC,KAAK,GAAG,MAAMpB,gBAAgB,CAACwB,MAAM,EAAEuE,IAAI,EAAEE,SAAS,EAAE;MAC5DN,QAAQ;MACRC,WAAW;MACXC,oBAAoB;MACpBK,WAAW,EAAErE,MAAM;MACnBmE;IACF,CAAC,CAAC;IAEFlF,OAAO,CAACI,GAAG,CAACE,KAAK,GAAGA,KAAK;IAEzB,OAAOL,CAAC,CAACgC,QAAQ;EACnB;EAEA,OAAO+C,OAAO;AAChB;AAEA,OAAO,SAASK,eAAeA,CAACrF,OAAoB,EAAEC,CAAsB,EAAE;EAC5E,MAAM;IAAEK;EAAM,CAAC,GAAGN,OAAO,CAACI,GAAG;EAE7B,MAAMkF,WAAW,GAAGhF,KAAK,GAAG,IAAIA,KAAK,CAACiF,QAAQ,EAAE,GAAG,EAAE;EACrD,OAAO7F,OAAO,CAACM,OAAO,EAAEC,CAAC,EAAE,GAAGqF,WAAW,GAAG7F,YAAY,CAACa,KAAK,CAAC,EAAE,CAAC;AACpE","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["isPaymentPage","Boom","EXTERNAL_STATE_APPENDAGE","EXTERNAL_STATE_PAYLOAD","FormComponent","isFormState","resolveFormModel","checkFormStatus","findPage","getCacheService","getPage","getStartPath","proceed","checkSaveAndExitRepeater","copyNotYetValidatedState","generateUniqueReference","defaultServices","redirectOrMakeHandler","request","h","onRequest","makeHandler","app","params","model","notFound","path","cacheService","server","page","state","getState","$$__referenceNumber","prefix","def","metadata","referenceNumberPrefix","badImplementation","referenceNumber","mergeState","importExternalComponentState","flash","getFlash","context","getFormContext","errors","relevantPath","getRelevantPath","summaryPath","getSummaryPath","result","continue","resumeInRepeaterUrl","startsWith","pageDef","isForceAccess","redirectTo","next","length","query","returnUrl","getHref","externalComponentData","yar","Array","isArray","typedStateAppendage","componentName","component","stateAppendage","data","componentMap","get","Error","TypeError","isStateValid","isState","componentState","isAppendageStateSingleObject","Object","fromEntries","entries","map","key","value","savedState","payload","stashedPayload","localState","getStateFromValidForm","makeLoadFormPreHandler","options","realm","modifiers","route","services","controllers","ordnanceSurveyApiKey","handler","slug","isPreview","formState","routePrefix","dispatchHandler","servicePath","basePath"],"sources":["../../../../../src/server/plugins/engine/routes/index.ts"],"sourcesContent":["import { isPaymentPage } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type ResponseObject,\n type ResponseToolkit,\n type Server\n} from '@hapi/hapi'\n\nimport {\n EXTERNAL_STATE_APPENDAGE,\n EXTERNAL_STATE_PAYLOAD\n} from '~/src/server/constants.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { resolveFormModel } from '~/src/server/plugins/engine/form-context.js'\nimport {\n checkFormStatus,\n findPage,\n getCacheService,\n getPage,\n getStartPath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n checkSaveAndExitRepeater,\n copyNotYetValidatedState\n} from '~/src/server/plugins/engine/pageControllers/helpers/state.js'\nimport { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type AnyFormRequest,\n type ExternalStateAppendage,\n type FormContext,\n type FormPayload,\n type FormSubmissionState,\n type OnRequestCallback,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport async function redirectOrMakeHandler(\n request: AnyFormRequest,\n h: FormResponseToolkit,\n onRequest: OnRequestCallback | undefined,\n makeHandler: (\n page: PageControllerClass,\n context: FormContext\n ) => ResponseObject | Promise<ResponseObject>\n) {\n const { app, params } = request\n const { model } = app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n const cacheService = getCacheService(request.server)\n const page = getPage(model, request)\n let state = await page.getState(request)\n\n if (!state.$$__referenceNumber) {\n const prefix = model.def.metadata?.referenceNumberPrefix ?? ''\n\n if (typeof prefix !== 'string') {\n throw Boom.badImplementation(\n 'Reference number prefix must be a string or undefined'\n )\n }\n\n const referenceNumber = generateUniqueReference(prefix)\n state = await page.mergeState(request, state, {\n $$__referenceNumber: referenceNumber\n })\n }\n\n state = await importExternalComponentState(request, page, state)\n\n const flash = cacheService.getFlash(request)\n const context = model.getFormContext(request, state, flash?.errors)\n\n await copyNotYetValidatedState(request, context)\n\n const relevantPath = page.getRelevantPath(request, context)\n const summaryPath = page.getSummaryPath()\n\n // Call the onRequest callback if it has been supplied\n if (onRequest) {\n const result = await onRequest(request, h, context)\n if (result !== h.continue) {\n return result\n }\n }\n\n // Check whether save-and-exit should resume from within a repeater\n const resumeInRepeaterUrl = checkSaveAndExitRepeater(context, model)\n if (resumeInRepeaterUrl) {\n return proceed(request, h, resumeInRepeaterUrl)\n }\n\n // Return handler for relevant pages, payment pages, or preview URL direct access.\n // Payment pages are skipped in the normal page walk but must render when the user\n // is redirected there from CYA \"Pay and submit\".\n if (\n relevantPath.startsWith(page.path) ||\n isPaymentPage(page.pageDef) ||\n context.isForceAccess\n ) {\n return makeHandler(page, context)\n }\n\n // Redirect back to last relevant page\n const redirectTo = findPage(model, relevantPath)\n // Set the return URL unless an exit page\n if (redirectTo?.next.length) {\n request.query.returnUrl = page.getHref(summaryPath)\n }\n\n return proceed(request, h, page.getHref(relevantPath))\n}\n\nasync function importExternalComponentState(\n request: AnyFormRequest,\n page: PageControllerClass,\n state: FormSubmissionState\n): Promise<FormSubmissionState> {\n const externalComponentData = request.yar.flash(EXTERNAL_STATE_APPENDAGE)\n\n if (Array.isArray(externalComponentData)) {\n return state\n }\n\n const typedStateAppendage = externalComponentData as ExternalStateAppendage\n const componentName = typedStateAppendage.component\n const stateAppendage = typedStateAppendage.data\n\n const component = request.app.model?.componentMap.get(componentName)\n\n if (!component) {\n throw new Error(`Component ${componentName} not found in form`)\n }\n\n if (!(component instanceof FormComponent)) {\n throw new TypeError(\n `Component ${componentName} is not a FormComponent and does not support isState`\n )\n }\n\n const isStateValid = component.isState(stateAppendage)\n\n if (!isStateValid) {\n throw new Error(`State for component ${componentName} is invalid`)\n }\n\n // Create state structure from appendage state\n // Some components use a record structure with properties of the format of '<compName>__<fieldName>'\n // e.g. UKAddressField\n // Some components use a single object structure e.g. PaymentField\n const componentState =\n isFormState(stateAppendage) && !component.isAppendageStateSingleObject\n ? Object.fromEntries(\n Object.entries(stateAppendage).map(([key, value]) => [\n `${componentName}__${key}`,\n value\n ])\n )\n : { [componentName]: stateAppendage }\n\n // Save the external component state directly (already has correct key format)\n const savedState = await page.mergeState(request, state, componentState)\n\n // Merge any stashed payload into the local state\n const payload = request.yar.flash(EXTERNAL_STATE_PAYLOAD)\n const stashedPayload = Array.isArray(payload) ? {} : (payload as FormPayload)\n\n const localState = page.getStateFromValidForm(request, savedState, {\n ...stashedPayload,\n ...componentState\n } as FormPayload)\n\n return { ...savedState, ...localState }\n}\n\nexport function makeLoadFormPreHandler(server: Server, options: PluginOptions) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hapi types are wrong\n const prefix = server.realm.modifiers.route.prefix ?? ''\n\n const {\n services = defaultServices,\n controllers,\n ordnanceSurveyApiKey\n } = options\n\n async function handler(request: AnyFormRequest, h: ResponseToolkit) {\n if (server.app.model) {\n request.app.model = server.app.model\n\n return h.continue\n }\n\n const { params } = request\n const { slug } = params\n const { isPreview, state: formState } = checkFormStatus(params)\n\n const model = await resolveFormModel(server, slug, formState, {\n services,\n controllers,\n ordnanceSurveyApiKey,\n routePrefix: prefix,\n isPreview\n })\n\n request.app.model = model\n\n return h.continue\n }\n\n return handler\n}\n\nexport function dispatchHandler(request: FormRequest, h: FormResponseToolkit) {\n const { model } = request.app\n\n const servicePath = model ? `/${model.basePath}` : ''\n return proceed(request, h, `${servicePath}${getStartPath(model)}`)\n}\n"],"mappings":"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","ignoreList":[]}
@@ -4,17 +4,31 @@
4
4
  <div class="govuk-grid-row">
5
5
  <div class="govuk-grid-column-two-thirds">
6
6
  <h1 class="govuk-heading-l">Sorry, this form is unavailable</h1>
7
- <p class="govuk-body">'{{ formTitle }}' has been archived and is no longer available.</p>
8
- <p class="govuk-body">Contact the {{ organisationName }}.</p>
7
+ <p class="govuk-body">'{{ formTitle }}' is no longer available.</p>
8
+ <h2 class="govuk-heading-m">Contact details for {{ organisationName }}</h2>
9
9
 
10
- {% if phoneLines %}
11
- <ul class="govuk-list govuk-list--bullet">
12
- {% for line in phoneLines %}
13
- <li>{{ line }}</li>
14
- {% endfor %}
15
- </ul>
10
+ {% if contact.phone %}
11
+ <h3 class="govuk-heading-s">Telephone</h3>
12
+ <div class="app-prose-scope">
13
+ {{ contact.phone | markdown(3) | safe }}
14
+ </div>
16
15
  <p class="govuk-body"><a href="https://www.gov.uk/call-charges" class="govuk-link govuk-link--no-visited-state">Find out about call charges</a></p>
17
16
  {% endif %}
17
+
18
+ {% if contact.email %}
19
+ <h3 class="govuk-heading-s">Email</h3>
20
+ <ul class="govuk-list">
21
+ <li><a class="govuk-link govuk-link--no-visited-state" href="mailto:{{ contact.email.address }}">{{ contact.email.address }}</a></li>
22
+ <li>{{ contact.email.responseTime }}</li>
23
+ </ul>
24
+ {% endif %}
25
+
26
+ {% if contact.online %}
27
+ <h3 class="govuk-heading-s">Online contact form</h3>
28
+ <ul class="govuk-list">
29
+ <li><a class="govuk-link govuk-link--no-visited-state" href="{{ contact.online.url }}">{{ contact.online.text }}</a></li>
30
+ </ul>
31
+ {% endif %}
18
32
  </div>
19
33
  </div>
20
34
  {% endblock %}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "4.20.1",
3
+ "version": "4.21.0",
4
4
  "description": "Defra forms engine",
5
5
  "type": "module",
6
6
  "files": [
@@ -1,4 +1,4 @@
1
- import { type FormMetadata } from '@defra/forms-model'
1
+ import { FormStatus, type FormMetadata } from '@defra/forms-model'
2
2
  import Boom from '@hapi/boom'
3
3
 
4
4
  export interface OfflineBoomData {
@@ -11,8 +11,16 @@ export interface OfflineBoomData {
11
11
  * unavailable-response extension catches the marker and renders the
12
12
  * "Sorry, this form is unavailable" view at HTTP 200.
13
13
  */
14
- export function assertFormAvailable(metadata: FormMetadata): void {
15
- if (metadata.offline === true) {
14
+ export function assertFormAvailable(
15
+ metadata: FormMetadata,
16
+ formState: FormStatus,
17
+ isPreview: boolean
18
+ ): void {
19
+ if (
20
+ metadata.offline === true &&
21
+ formState === FormStatus.Live &&
22
+ !isPreview
23
+ ) {
16
24
  const data: OfflineBoomData = { offline: true, metadata }
17
25
  throw Boom.boomify(new Error(`Form ${metadata.slug} is offline`), {
18
26
  statusCode: 503,
@@ -2,25 +2,25 @@ import Boom from '@hapi/boom'
2
2
  import { type Request, type Server } from '@hapi/hapi'
3
3
  import { isEqual } from 'date-fns'
4
4
 
5
- import { PREVIEW_PATH_PREFIX } from '../../../constants.js'
6
- import { assertFormAvailable } from '../form-availability.js'
5
+ import { PREVIEW_PATH_PREFIX } from '../../constants.js'
6
+ import { assertFormAvailable } from './form-availability.js'
7
7
  import {
8
8
  checkEmailAddressForLiveFormSubmission,
9
9
  getCacheService
10
- } from '../helpers.js'
11
- import { FormModel } from '../models/index.js'
12
- import { type PageController } from '../pageControllers/PageController.js'
13
- import { TerminalPageController } from '../pageControllers/index.js'
14
- import * as defaultServices from '../services/index.js'
10
+ } from './helpers.js'
11
+ import { FormModel } from './models/index.js'
12
+ import { type PageController } from './pageControllers/PageController.js'
13
+ import { TerminalPageController } from './pageControllers/index.js'
14
+ import * as defaultServices from './services/index.js'
15
15
  import {
16
16
  type AnyRequest,
17
17
  type FormContext,
18
18
  type FormContextRequest,
19
19
  type FormSubmissionError,
20
20
  type FormSubmissionState
21
- } from '../types.js'
22
- import { FormStatus } from '../../../routes/types.js'
23
- import { type Services } from '../../../types.js'
21
+ } from './types.js'
22
+ import { FormStatus } from '../../routes/types.js'
23
+ import { type Services } from '../../types.js'
24
24
 
25
25
  type JourneyState = FormStatus | 'preview'
26
26
 
@@ -53,7 +53,7 @@ export async function getFormModel(
53
53
  const formState = resolveState(state)
54
54
 
55
55
  const metadata = await formsService.getFormMetadata(slug)
56
- assertFormAvailable(metadata)
56
+ assertFormAvailable(metadata, formState, isPreview)
57
57
 
58
58
  const definition = await formsService.getFormDefinition(
59
59
  metadata.id,
@@ -136,9 +136,9 @@ export async function resolveFormModel(
136
136
  const { formsService } = services
137
137
 
138
138
  const metadata = await formsService.getFormMetadata(slug)
139
- assertFormAvailable(metadata)
140
139
  const formState = resolveState(state)
141
140
  const isPreview = options.isPreview ?? isPreviewState(state, options)
141
+ assertFormAvailable(metadata, formState, isPreview)
142
142
  const stateMetadata = metadata[formState]
143
143
 
144
144
  if (!stateMetadata) {
@@ -19,7 +19,7 @@ export {
19
19
  getFormContext,
20
20
  getFormModel,
21
21
  resolveFormModel
22
- } from './beta/form-context.js'
22
+ } from './form-context.js'
23
23
  export * from './form-availability.js'
24
24
 
25
25
  const globals = {
@@ -1,27 +1,10 @@
1
- import { type FormMetadata } from '@defra/forms-model'
1
+ import { type FormMetadata, type FormMetadataContact } from '@defra/forms-model'
2
2
 
3
3
  export interface UnavailableViewModel {
4
4
  pageTitle: string
5
5
  formTitle: string
6
6
  organisationName: string
7
- phoneLines?: string[]
8
- }
9
-
10
- /**
11
- * Defra organisations carry an abbreviation suffix on the enum value, e.g.
12
- * "Rural Payments Agency – RPA". The unavailable page reads cleanly without it.
13
- */
14
- function stripOrgSuffix(organisation: string) {
15
- return organisation.split(' – ')[0]
16
- }
17
-
18
- function splitPhoneLines(phone: string | undefined) {
19
- if (!phone) return undefined
20
- const lines = phone
21
- .split('\n')
22
- .map((line) => line.trim())
23
- .filter((line) => line.length > 0)
24
- return lines.length > 0 ? lines : undefined
7
+ contact?: FormMetadataContact
25
8
  }
26
9
 
27
10
  export function unavailableViewModel(
@@ -30,7 +13,7 @@ export function unavailableViewModel(
30
13
  return {
31
14
  pageTitle: 'Sorry, this form is unavailable',
32
15
  formTitle: metadata.title,
33
- organisationName: stripOrgSuffix(metadata.organisation),
34
- phoneLines: splitPhoneLines(metadata.contact?.phone)
16
+ organisationName: metadata.organisation,
17
+ contact: metadata.contact
35
18
  }
36
19
  }
@@ -10,11 +10,11 @@ import {
10
10
  EXTERNAL_STATE_APPENDAGE,
11
11
  EXTERNAL_STATE_PAYLOAD
12
12
  } from '../../../constants.js'
13
- import { resolveFormModel } from '../beta/form-context.js'
14
13
  import {
15
14
  FormComponent,
16
15
  isFormState
17
16
  } from '../components/FormComponent.js'
17
+ import { resolveFormModel } from '../form-context.js'
18
18
  import {
19
19
  checkFormStatus,
20
20
  findPage,
@@ -4,17 +4,31 @@
4
4
  <div class="govuk-grid-row">
5
5
  <div class="govuk-grid-column-two-thirds">
6
6
  <h1 class="govuk-heading-l">Sorry, this form is unavailable</h1>
7
- <p class="govuk-body">'{{ formTitle }}' has been archived and is no longer available.</p>
8
- <p class="govuk-body">Contact the {{ organisationName }}.</p>
7
+ <p class="govuk-body">'{{ formTitle }}' is no longer available.</p>
8
+ <h2 class="govuk-heading-m">Contact details for {{ organisationName }}</h2>
9
9
 
10
- {% if phoneLines %}
11
- <ul class="govuk-list govuk-list--bullet">
12
- {% for line in phoneLines %}
13
- <li>{{ line }}</li>
14
- {% endfor %}
15
- </ul>
10
+ {% if contact.phone %}
11
+ <h3 class="govuk-heading-s">Telephone</h3>
12
+ <div class="app-prose-scope">
13
+ {{ contact.phone | markdown(3) | safe }}
14
+ </div>
16
15
  <p class="govuk-body"><a href="https://www.gov.uk/call-charges" class="govuk-link govuk-link--no-visited-state">Find out about call charges</a></p>
17
16
  {% endif %}
17
+
18
+ {% if contact.email %}
19
+ <h3 class="govuk-heading-s">Email</h3>
20
+ <ul class="govuk-list">
21
+ <li><a class="govuk-link govuk-link--no-visited-state" href="mailto:{{ contact.email.address }}">{{ contact.email.address }}</a></li>
22
+ <li>{{ contact.email.responseTime }}</li>
23
+ </ul>
24
+ {% endif %}
25
+
26
+ {% if contact.online %}
27
+ <h3 class="govuk-heading-s">Online contact form</h3>
28
+ <ul class="govuk-list">
29
+ <li><a class="govuk-link govuk-link--no-visited-state" href="{{ contact.online.url }}">{{ contact.online.text }}</a></li>
30
+ </ul>
31
+ {% endif %}
18
32
  </div>
19
33
  </div>
20
34
  {% endblock %}
@@ -1 +0,0 @@
1
- {"version":3,"file":"form-context.js","names":["Boom","isEqual","PREVIEW_PATH_PREFIX","assertFormAvailable","checkEmailAddressForLiveFormSubmission","getCacheService","FormModel","TerminalPageController","defaultServices","FormStatus","getFormModel","slug","state","options","services","formsService","isPreview","isPreviewState","formState","resolveState","metadata","getFormMetadata","definition","getFormDefinition","id","notFound","basePath","buildBasePath","routePrefix","ordnanceSurveyApiKey","formId","controllers","getFormContext","server","yar","Live","formModel","resolveFormModel","cacheService","summaryRequest","app","method","params","path","query","url","URL","cachedState","getState","$$__referenceNumber","errors","stateMetadata","models","Map","cache","cacheKey","entry","get","updatedAt","notificationEmail","realm","modifiers","route","prefix","model","set","base","replace","startsWith","slice","getFirstJourneyPage","context","relevantPages","undefined","lastPageReached","at","penultimatePageReached"],"sources":["../../../../../src/server/plugins/engine/beta/form-context.ts"],"sourcesContent":["import Boom from '@hapi/boom'\nimport { type Request, type Server } from '@hapi/hapi'\nimport { isEqual } from 'date-fns'\n\nimport { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'\nimport { assertFormAvailable } from '~/src/server/plugins/engine/form-availability.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n getCacheService\n} from '~/src/server/plugins/engine/helpers.js'\nimport { FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { TerminalPageController } from '~/src/server/plugins/engine/pageControllers/index.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type AnyRequest,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormStatus } from '~/src/server/routes/types.js'\nimport { type Services } from '~/src/server/types.js'\n\ntype JourneyState = FormStatus | 'preview'\n\nexport interface FormModelOptions {\n services?: Services\n controllers?: Record<string, typeof PageController>\n basePath?: string\n ordnanceSurveyApiKey?: string\n formId?: string\n routePrefix?: string\n isPreview?: boolean\n}\n\nexport interface FormContextOptions extends FormModelOptions {\n errors?: FormSubmissionError[]\n}\n\ntype SummaryRequest = FormContextRequest & {\n yar: Request['yar']\n}\n\nexport async function getFormModel(\n slug: string,\n state: JourneyState,\n options: FormModelOptions = {}\n) {\n const services = options.services ?? defaultServices\n const { formsService } = services\n const isPreview = isPreviewState(state, options)\n const formState = resolveState(state)\n\n const metadata = await formsService.getFormMetadata(slug)\n assertFormAvailable(metadata)\n\n const definition = await formsService.getFormDefinition(\n metadata.id,\n formState\n )\n\n if (!definition) {\n throw Boom.notFound(\n `No definition found for form metadata ${metadata.id} (${slug}) ${state}`\n )\n }\n\n return new FormModel(\n definition,\n {\n basePath:\n options.basePath ??\n buildBasePath(options.routePrefix ?? '', slug, formState, isPreview),\n ordnanceSurveyApiKey: options.ordnanceSurveyApiKey,\n formId: options.formId ?? metadata.id\n },\n services,\n options.controllers\n )\n}\n\nexport async function getFormContext(\n { server, yar }: Pick<Request, 'server' | 'yar'>,\n slug: string,\n state: JourneyState = FormStatus.Live,\n options: FormContextOptions = {}\n): Promise<FormContext> {\n const formModel = await resolveFormModel(server, slug, state, options)\n\n const cacheService = getCacheService(server)\n\n const summaryRequest: SummaryRequest = {\n app: {},\n method: 'get',\n params: {\n path: 'summary',\n slug,\n ...(isPreviewState(state, options) && {\n state: resolveState(state)\n })\n },\n path: `/${formModel.basePath}/summary`,\n query: {},\n url: new URL(\n `/${formModel.basePath}/summary`,\n 'https://form-context.local'\n ),\n server,\n yar\n }\n\n const cachedState = await cacheService.getState(\n summaryRequest as unknown as AnyRequest\n )\n\n const formState = {\n ...cachedState,\n $$__referenceNumber: cachedState.$$__referenceNumber\n } as unknown as FormSubmissionState\n\n return formModel.getFormContext(\n summaryRequest,\n formState,\n options.errors ?? []\n )\n}\n\nexport async function resolveFormModel(\n server: Server,\n slug: string,\n state: JourneyState,\n options: FormModelOptions = {}\n) {\n const services = options.services ?? defaultServices\n const { formsService } = services\n\n const metadata = await formsService.getFormMetadata(slug)\n assertFormAvailable(metadata)\n const formState = resolveState(state)\n const isPreview = options.isPreview ?? isPreviewState(state, options)\n const stateMetadata = metadata[formState]\n\n if (!stateMetadata) {\n throw Boom.notFound(\n `No '${formState}' state for form metadata ${metadata.id}`\n )\n }\n\n // The models cache is created lazily per server instance\n\n server.app.models ??= new Map<string, { model: FormModel; updatedAt: Date }>()\n\n const cache = server.app.models\n\n const cacheKey = `${metadata.id}_${formState}_${isPreview}`\n let entry = cache.get(cacheKey)\n\n if (!entry || !isEqual(entry.updatedAt, stateMetadata.updatedAt)) {\n const definition = await formsService.getFormDefinition(\n metadata.id,\n formState\n )\n\n if (!definition) {\n throw Boom.notFound(\n `No definition found for form metadata ${metadata.id} (${slug}) ${state}`\n )\n }\n\n checkEmailAddressForLiveFormSubmission(\n metadata.notificationEmail,\n isPreview\n )\n\n const routePrefix =\n options.routePrefix ?? server.realm.modifiers.route.prefix\n\n const model = new FormModel(\n definition,\n {\n basePath:\n options.basePath ??\n buildBasePath(routePrefix, slug, formState, isPreview),\n ordnanceSurveyApiKey: options.ordnanceSurveyApiKey,\n formId: options.formId ?? metadata.id\n },\n services,\n options.controllers\n )\n\n entry = { model, updatedAt: stateMetadata.updatedAt }\n cache.set(cacheKey, entry)\n }\n\n return entry.model\n}\n\nfunction buildBasePath(\n routePrefix: string,\n slug: string,\n state: FormStatus,\n isPreview: boolean\n) {\n const base = (\n isPreview\n ? `${routePrefix}${PREVIEW_PATH_PREFIX}/${state}/${slug}`\n : `${routePrefix}/${slug}`\n ).replace(/\\/{2,}/g, '/')\n\n return base.startsWith('/') ? base.slice(1) : base\n}\n\nexport function getFirstJourneyPage(\n context?: Pick<FormContext, 'relevantPages'>\n) {\n if (!context?.relevantPages) {\n return undefined\n }\n\n const lastPageReached = context.relevantPages.at(-1)\n const penultimatePageReached = context.relevantPages.at(-2)\n\n if (\n lastPageReached instanceof TerminalPageController &&\n penultimatePageReached\n ) {\n return penultimatePageReached\n }\n\n return lastPageReached\n}\n\nfunction resolveState(state: JourneyState): FormStatus {\n return state === 'preview' ? FormStatus.Live : state\n}\n\nfunction isPreviewState(\n state: JourneyState,\n options: FormModelOptions = {}\n): boolean {\n return options.isPreview ?? state === 'preview'\n}\n"],"mappings":"AAAA,OAAOA,IAAI,MAAM,YAAY;AAE7B,SAASC,OAAO,QAAQ,UAAU;AAElC,SAASC,mBAAmB;AAC5B,SAASC,mBAAmB;AAC5B,SACEC,sCAAsC,EACtCC,eAAe;AAEjB,SAASC,SAAS;AAElB,SAASC,sBAAsB;AAC/B,OAAO,KAAKC,eAAe;AAQ3B,SAASC,UAAU;AAuBnB,OAAO,eAAeC,YAAYA,CAChCC,IAAY,EACZC,KAAmB,EACnBC,OAAyB,GAAG,CAAC,CAAC,EAC9B;EACA,MAAMC,QAAQ,GAAGD,OAAO,CAACC,QAAQ,IAAIN,eAAe;EACpD,MAAM;IAAEO;EAAa,CAAC,GAAGD,QAAQ;EACjC,MAAME,SAAS,GAAGC,cAAc,CAACL,KAAK,EAAEC,OAAO,CAAC;EAChD,MAAMK,SAAS,GAAGC,YAAY,CAACP,KAAK,CAAC;EAErC,MAAMQ,QAAQ,GAAG,MAAML,YAAY,CAACM,eAAe,CAACV,IAAI,CAAC;EACzDR,mBAAmB,CAACiB,QAAQ,CAAC;EAE7B,MAAME,UAAU,GAAG,MAAMP,YAAY,CAACQ,iBAAiB,CACrDH,QAAQ,CAACI,EAAE,EACXN,SACF,CAAC;EAED,IAAI,CAACI,UAAU,EAAE;IACf,MAAMtB,IAAI,CAACyB,QAAQ,CACjB,yCAAyCL,QAAQ,CAACI,EAAE,KAAKb,IAAI,KAAKC,KAAK,EACzE,CAAC;EACH;EAEA,OAAO,IAAIN,SAAS,CAClBgB,UAAU,EACV;IACEI,QAAQ,EACNb,OAAO,CAACa,QAAQ,IAChBC,aAAa,CAACd,OAAO,CAACe,WAAW,IAAI,EAAE,EAAEjB,IAAI,EAAEO,SAAS,EAAEF,SAAS,CAAC;IACtEa,oBAAoB,EAAEhB,OAAO,CAACgB,oBAAoB;IAClDC,MAAM,EAAEjB,OAAO,CAACiB,MAAM,IAAIV,QAAQ,CAACI;EACrC,CAAC,EACDV,QAAQ,EACRD,OAAO,CAACkB,WACV,CAAC;AACH;AAEA,OAAO,eAAeC,cAAcA,CAClC;EAAEC,MAAM;EAAEC;AAAqC,CAAC,EAChDvB,IAAY,EACZC,KAAmB,GAAGH,UAAU,CAAC0B,IAAI,EACrCtB,OAA2B,GAAG,CAAC,CAAC,EACV;EACtB,MAAMuB,SAAS,GAAG,MAAMC,gBAAgB,CAACJ,MAAM,EAAEtB,IAAI,EAAEC,KAAK,EAAEC,OAAO,CAAC;EAEtE,MAAMyB,YAAY,GAAGjC,eAAe,CAAC4B,MAAM,CAAC;EAE5C,MAAMM,cAA8B,GAAG;IACrCC,GAAG,EAAE,CAAC,CAAC;IACPC,MAAM,EAAE,KAAK;IACbC,MAAM,EAAE;MACNC,IAAI,EAAE,SAAS;MACfhC,IAAI;MACJ,IAAIM,cAAc,CAACL,KAAK,EAAEC,OAAO,CAAC,IAAI;QACpCD,KAAK,EAAEO,YAAY,CAACP,KAAK;MAC3B,CAAC;IACH,CAAC;IACD+B,IAAI,EAAE,IAAIP,SAAS,CAACV,QAAQ,UAAU;IACtCkB,KAAK,EAAE,CAAC,CAAC;IACTC,GAAG,EAAE,IAAIC,GAAG,CACV,IAAIV,SAAS,CAACV,QAAQ,UAAU,EAChC,4BACF,CAAC;IACDO,MAAM;IACNC;EACF,CAAC;EAED,MAAMa,WAAW,GAAG,MAAMT,YAAY,CAACU,QAAQ,CAC7CT,cACF,CAAC;EAED,MAAMrB,SAAS,GAAG;IAChB,GAAG6B,WAAW;IACdE,mBAAmB,EAAEF,WAAW,CAACE;EACnC,CAAmC;EAEnC,OAAOb,SAAS,CAACJ,cAAc,CAC7BO,cAAc,EACdrB,SAAS,EACTL,OAAO,CAACqC,MAAM,IAAI,EACpB,CAAC;AACH;AAEA,OAAO,eAAeb,gBAAgBA,CACpCJ,MAAc,EACdtB,IAAY,EACZC,KAAmB,EACnBC,OAAyB,GAAG,CAAC,CAAC,EAC9B;EACA,MAAMC,QAAQ,GAAGD,OAAO,CAACC,QAAQ,IAAIN,eAAe;EACpD,MAAM;IAAEO;EAAa,CAAC,GAAGD,QAAQ;EAEjC,MAAMM,QAAQ,GAAG,MAAML,YAAY,CAACM,eAAe,CAACV,IAAI,CAAC;EACzDR,mBAAmB,CAACiB,QAAQ,CAAC;EAC7B,MAAMF,SAAS,GAAGC,YAAY,CAACP,KAAK,CAAC;EACrC,MAAMI,SAAS,GAAGH,OAAO,CAACG,SAAS,IAAIC,cAAc,CAACL,KAAK,EAAEC,OAAO,CAAC;EACrE,MAAMsC,aAAa,GAAG/B,QAAQ,CAACF,SAAS,CAAC;EAEzC,IAAI,CAACiC,aAAa,EAAE;IAClB,MAAMnD,IAAI,CAACyB,QAAQ,CACjB,OAAOP,SAAS,6BAA6BE,QAAQ,CAACI,EAAE,EAC1D,CAAC;EACH;;EAEA;;EAEAS,MAAM,CAACO,GAAG,CAACY,MAAM,KAAK,IAAIC,GAAG,CAAgD,CAAC;EAE9E,MAAMC,KAAK,GAAGrB,MAAM,CAACO,GAAG,CAACY,MAAM;EAE/B,MAAMG,QAAQ,GAAG,GAAGnC,QAAQ,CAACI,EAAE,IAAIN,SAAS,IAAIF,SAAS,EAAE;EAC3D,IAAIwC,KAAK,GAAGF,KAAK,CAACG,GAAG,CAACF,QAAQ,CAAC;EAE/B,IAAI,CAACC,KAAK,IAAI,CAACvD,OAAO,CAACuD,KAAK,CAACE,SAAS,EAAEP,aAAa,CAACO,SAAS,CAAC,EAAE;IAChE,MAAMpC,UAAU,GAAG,MAAMP,YAAY,CAACQ,iBAAiB,CACrDH,QAAQ,CAACI,EAAE,EACXN,SACF,CAAC;IAED,IAAI,CAACI,UAAU,EAAE;MACf,MAAMtB,IAAI,CAACyB,QAAQ,CACjB,yCAAyCL,QAAQ,CAACI,EAAE,KAAKb,IAAI,KAAKC,KAAK,EACzE,CAAC;IACH;IAEAR,sCAAsC,CACpCgB,QAAQ,CAACuC,iBAAiB,EAC1B3C,SACF,CAAC;IAED,MAAMY,WAAW,GACff,OAAO,CAACe,WAAW,IAAIK,MAAM,CAAC2B,KAAK,CAACC,SAAS,CAACC,KAAK,CAACC,MAAM;IAE5D,MAAMC,KAAK,GAAG,IAAI1D,SAAS,CACzBgB,UAAU,EACV;MACEI,QAAQ,EACNb,OAAO,CAACa,QAAQ,IAChBC,aAAa,CAACC,WAAW,EAAEjB,IAAI,EAAEO,SAAS,EAAEF,SAAS,CAAC;MACxDa,oBAAoB,EAAEhB,OAAO,CAACgB,oBAAoB;MAClDC,MAAM,EAAEjB,OAAO,CAACiB,MAAM,IAAIV,QAAQ,CAACI;IACrC,CAAC,EACDV,QAAQ,EACRD,OAAO,CAACkB,WACV,CAAC;IAEDyB,KAAK,GAAG;MAAEQ,KAAK;MAAEN,SAAS,EAAEP,aAAa,CAACO;IAAU,CAAC;IACrDJ,KAAK,CAACW,GAAG,CAACV,QAAQ,EAAEC,KAAK,CAAC;EAC5B;EAEA,OAAOA,KAAK,CAACQ,KAAK;AACpB;AAEA,SAASrC,aAAaA,CACpBC,WAAmB,EACnBjB,IAAY,EACZC,KAAiB,EACjBI,SAAkB,EAClB;EACA,MAAMkD,IAAI,GAAG,CACXlD,SAAS,GACL,GAAGY,WAAW,GAAG1B,mBAAmB,IAAIU,KAAK,IAAID,IAAI,EAAE,GACvD,GAAGiB,WAAW,IAAIjB,IAAI,EAAE,EAC5BwD,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;EAEzB,OAAOD,IAAI,CAACE,UAAU,CAAC,GAAG,CAAC,GAAGF,IAAI,CAACG,KAAK,CAAC,CAAC,CAAC,GAAGH,IAAI;AACpD;AAEA,OAAO,SAASI,mBAAmBA,CACjCC,OAA4C,EAC5C;EACA,IAAI,CAACA,OAAO,EAAEC,aAAa,EAAE;IAC3B,OAAOC,SAAS;EAClB;EAEA,MAAMC,eAAe,GAAGH,OAAO,CAACC,aAAa,CAACG,EAAE,CAAC,CAAC,CAAC,CAAC;EACpD,MAAMC,sBAAsB,GAAGL,OAAO,CAACC,aAAa,CAACG,EAAE,CAAC,CAAC,CAAC,CAAC;EAE3D,IACED,eAAe,YAAYnE,sBAAsB,IACjDqE,sBAAsB,EACtB;IACA,OAAOA,sBAAsB;EAC/B;EAEA,OAAOF,eAAe;AACxB;AAEA,SAASvD,YAAYA,CAACP,KAAmB,EAAc;EACrD,OAAOA,KAAK,KAAK,SAAS,GAAGH,UAAU,CAAC0B,IAAI,GAAGvB,KAAK;AACtD;AAEA,SAASK,cAAcA,CACrBL,KAAmB,EACnBC,OAAyB,GAAG,CAAC,CAAC,EACrB;EACT,OAAOA,OAAO,CAACG,SAAS,IAAIJ,KAAK,KAAK,SAAS;AACjD","ignoreList":[]}