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

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 (66) hide show
  1. package/.public/javascripts/application.min.js +1 -1
  2. package/.public/javascripts/application.min.js.map +1 -1
  3. package/.public/javascripts/shared.min.js +1 -1
  4. package/.public/javascripts/shared.min.js.map +1 -1
  5. package/.server/client/javascripts/file-upload.js +5 -3
  6. package/.server/client/javascripts/file-upload.js.map +1 -1
  7. package/.server/server/plugins/engine/components/AutocompleteField.d.ts +1 -0
  8. package/.server/server/plugins/engine/components/DatePartsField.d.ts +1 -0
  9. package/.server/server/plugins/engine/components/DeclarationField.d.ts +1 -0
  10. package/.server/server/plugins/engine/components/Details.d.ts +1 -0
  11. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +2 -0
  12. package/.server/server/plugins/engine/components/EmailAddressField.d.ts +1 -0
  13. package/.server/server/plugins/engine/components/FileUploadField.js +2 -2
  14. package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
  15. package/.server/server/plugins/engine/components/FormComponent.d.ts +1 -0
  16. package/.server/server/plugins/engine/components/GeospatialField.d.ts +1 -0
  17. package/.server/server/plugins/engine/components/Html.d.ts +1 -0
  18. package/.server/server/plugins/engine/components/InsetText.d.ts +1 -0
  19. package/.server/server/plugins/engine/components/LatLongField.d.ts +2 -0
  20. package/.server/server/plugins/engine/components/List.d.ts +1 -0
  21. package/.server/server/plugins/engine/components/ListFormComponent.d.ts +1 -0
  22. package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +2 -0
  23. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +2 -0
  24. package/.server/server/plugins/engine/components/Markdown.d.ts +1 -0
  25. package/.server/server/plugins/engine/components/MonthYearField.d.ts +1 -0
  26. package/.server/server/plugins/engine/components/MultilineTextField.d.ts +1 -0
  27. package/.server/server/plugins/engine/components/NotificationBanner.d.ts +1 -0
  28. package/.server/server/plugins/engine/components/NumberField.d.ts +1 -0
  29. package/.server/server/plugins/engine/components/PaymentField.d.ts +2 -0
  30. package/.server/server/plugins/engine/components/PaymentField.js +8 -1
  31. package/.server/server/plugins/engine/components/PaymentField.js.map +1 -1
  32. package/.server/server/plugins/engine/components/SelectField.d.ts +1 -0
  33. package/.server/server/plugins/engine/components/SelectionControlField.d.ts +1 -0
  34. package/.server/server/plugins/engine/components/TelephoneNumberField.d.ts +1 -0
  35. package/.server/server/plugins/engine/components/UkAddressField.d.ts +1 -0
  36. package/.server/server/plugins/engine/components/types.d.ts +1 -0
  37. package/.server/server/plugins/engine/components/types.js.map +1 -1
  38. package/.server/server/plugins/engine/i18n/createFormTranslator.js +7 -0
  39. package/.server/server/plugins/engine/i18n/createFormTranslator.js.map +1 -1
  40. package/.server/server/plugins/engine/i18n/translations/cy.json +53 -3
  41. package/.server/server/plugins/engine/i18n/translations/en-GB.json +24 -2
  42. package/.server/server/plugins/engine/i18n/types.d.ts +1 -0
  43. package/.server/server/plugins/engine/i18n/types.js.map +1 -1
  44. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +20 -7
  45. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  46. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +5 -1
  47. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  48. package/.server/server/plugins/engine/views/components/fileuploadfield.html +1 -1
  49. package/.server/server/plugins/engine/views/components/multilinetextfield.html +12 -1
  50. package/.server/server/plugins/engine/views/components/paymentfield.html +1 -1
  51. package/.server/server/plugins/engine/views/partials/preview-banner.html +3 -1
  52. package/package.json +1 -1
  53. package/src/client/javascripts/file-upload.js +10 -3
  54. package/src/server/plugins/engine/components/FileUploadField.ts +2 -2
  55. package/src/server/plugins/engine/components/PaymentField.ts +6 -1
  56. package/src/server/plugins/engine/components/types.ts +1 -0
  57. package/src/server/plugins/engine/i18n/createFormTranslator.ts +21 -1
  58. package/src/server/plugins/engine/i18n/translations/cy.json +53 -3
  59. package/src/server/plugins/engine/i18n/translations/en-GB.json +24 -2
  60. package/src/server/plugins/engine/i18n/types.ts +1 -0
  61. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +16 -6
  62. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +8 -3
  63. package/src/server/plugins/engine/views/components/fileuploadfield.html +1 -1
  64. package/src/server/plugins/engine/views/components/multilinetextfield.html +12 -1
  65. package/src/server/plugins/engine/views/components/paymentfield.html +1 -1
  66. package/src/server/plugins/engine/views/partials/preview-banner.html +3 -1
@@ -1 +1 @@
1
- {"version":3,"file":"PaymentField.js","names":["randomUUID","StatusCodes","joi","logger","COMPONENT_STATE_ERROR","FormComponent","createError","getPluginOptions","PaymentErrorTypes","PaymentPreAuthError","PaymentSubmissionError","createPaymentService","formatCurrency","resolveLanguage","PaymentField","isAppendageStateSingleObject","constructor","def","props","options","paymentStateSchema","object","paymentId","string","required","reference","amount","number","description","uuid","formId","isLivePayment","boolean","preAuth","status","valid","createdAt","isoDate","unknown","label","formSchema","stateSchema","getPaymentStateFromState","state","value","name","isPaymentState","undefined","getDisplayStringFromState","_translator","getViewModel","context","payload","viewModel","paymentState","Array","isArray","isState","getFormValue","getContextValueFromState","getAllPossibleErrors","baseErrors","type","template","advancedSettingsErrors","resolveAmount","model","conditionalAmounts","length","condition","conditions","warn","fn","dispatcher","request","h","args","componentName","component","controller","language","t","createTranslator","getState","baseUrl","server","summaryUrl","basePath","existingPaymentState","redirect","code","SEE_OTHER","isLive","isPreview","formsService","services","paymentService","$$__referenceNumber","resolvedAmount","slug","payCallbackUrl","paymentPageUrl","sourceUrl","prefilledEmail","userConfirmationEmail","userConfirmationEmailAddress","amountInPence","Math","round","payment","createPayment","message","govukError","yar","flash","url","pathname","search","sessionData","returnUrl","failureUrl","set","paymentUrl","onSubmit","_metadata","PaymentIncomplete","capture","getPaymentStatus","checkPaymentAmount","markPaymentCaptured","PaymentExpired","captured","capturePayment","updatedState","Date","toISOString","page","currentState","mergeState"],"sources":["../../../../../src/server/plugins/engine/components/PaymentField.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\n\nimport {\n type FormMetadata,\n type PaymentFieldComponent\n} from '@defra/forms-model'\nimport { StatusCodes } from 'http-status-codes'\nimport joi, { type ObjectSchema } from 'joi'\n\nimport { logger } from '~/src/server/common/helpers/logging/logger.js'\nimport { COMPONENT_STATE_ERROR } from '~/src/server/constants.js'\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type PaymentState } from '~/src/server/plugins/engine/components/PaymentField.types.js'\nimport { type RenderContext } from '~/src/server/plugins/engine/components/types.js'\nimport {\n createError,\n getPluginOptions\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n PaymentErrorTypes,\n PaymentPreAuthError,\n PaymentSubmissionError\n} from '~/src/server/plugins/engine/pageControllers/errors.js'\nimport {\n type FormContext,\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/plugins/engine/types/index.js'\nimport {\n type ErrorMessageTemplateList,\n type FormState,\n type FormStateValue,\n type FormSubmissionState,\n type PaymentExternalArgs\n} from '~/src/server/plugins/engine/types.js'\nimport {\n createPaymentService,\n formatCurrency\n} from '~/src/server/plugins/payment/helper.js'\nimport { resolveLanguage } from '~/src/server/utils/utils.js'\n\nexport class PaymentField extends FormComponent {\n declare options: PaymentFieldComponent['options']\n declare formSchema: ObjectSchema\n declare stateSchema: ObjectSchema\n isAppendageStateSingleObject = true\n\n constructor(\n def: PaymentFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n this.options = def.options\n\n const paymentStateSchema = joi\n .object({\n paymentId: joi.string().required(),\n reference: joi.string().required(),\n amount: joi.number().required(),\n description: joi.string().required(),\n uuid: joi.string().uuid().required(),\n formId: joi.string().required(),\n isLivePayment: joi.boolean().required(),\n preAuth: joi\n .object({\n status: joi\n .string()\n .valid('success', 'failed', 'started')\n .required(),\n createdAt: joi.string().isoDate().required()\n })\n .required()\n })\n .unknown(true)\n .label(this.label)\n\n this.formSchema = paymentStateSchema\n // 'required()' forces the payment page to be invalid until we have valid payment state\n // i.e. the user will automatically be directed back to the payment page\n // if they attempt to access future pages when no payment entered yet\n this.stateSchema = paymentStateSchema.required()\n }\n\n /**\n * Gets the PaymentState from form submission state\n */\n getPaymentStateFromState(\n state: FormSubmissionState\n ): PaymentState | undefined {\n const value = state[this.name]\n return this.isPaymentState(value) ? value : undefined\n }\n\n getDisplayStringFromState(\n state: FormSubmissionState,\n _translator: Translator\n ): string {\n const value = this.getPaymentStateFromState(state)\n\n if (!value) {\n return ''\n }\n\n return `${formatCurrency(value.amount)} - ${value.description}`\n }\n\n getViewModel(context: RenderContext) {\n const { payload } = context\n const viewModel = super.getViewModel(context)\n\n // Payload is pre-populated from state if a payment has already been made\n const paymentState = this.isPaymentState(payload[this.name] as unknown)\n ? (payload[this.name] as unknown as PaymentState)\n : undefined\n\n // Use payment state amount if pre-authorized, otherwise use default.\n // The page controller overrides this with the resolved conditional amount\n // using the full form state (which getViewModel doesn't have access to).\n const amount = paymentState?.amount ?? this.options.amount\n\n return {\n ...viewModel,\n amount: formatCurrency(amount),\n description: this.options.description,\n paymentState\n }\n }\n\n /**\n * Type guard to check if value is PaymentState\n */\n isPaymentState(value: unknown): value is PaymentState {\n return PaymentField.isPaymentState(value)\n }\n\n /**\n * Static type guard to check if value is PaymentState\n */\n static isPaymentState(value: unknown): value is PaymentState {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return false\n }\n\n const state = value as PaymentState\n return (\n typeof state.paymentId === 'string' &&\n typeof state.amount === 'number' &&\n typeof state.description === 'string'\n )\n }\n\n /**\n * Override base isState to validate PaymentState\n */\n isState(value?: FormStateValue | FormState): value is FormState {\n return this.isPaymentState(value)\n }\n\n getFormValue(value?: FormStateValue | FormState) {\n return this.isPaymentState(value)\n ? (value as unknown as NonNullable<FormStateValue>)\n : undefined\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n return this.isPaymentState(state)\n ? `Reference: ${state.reference}\\nAmount: ${formatCurrency(state.amount)}`\n : ''\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return PaymentField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n {\n type: 'paymentRequired',\n template: 'Complete the payment to continue'\n }\n ],\n advancedSettingsErrors: []\n }\n }\n\n /**\n * Resolves the payment amount from conditional amounts configuration.\n * Evaluates conditions in order; first true condition wins.\n * Falls back to the default options.amount.\n */\n static resolveAmount(\n options: PaymentFieldComponent['options'],\n model: FormModel,\n state: FormState\n ): number {\n const { conditionalAmounts } = options\n\n if (!conditionalAmounts?.length) {\n return options.amount\n }\n\n for (const { condition, amount } of conditionalAmounts) {\n if (!model.conditions[condition]) {\n logger.warn(\n `[payment] Condition '${condition}' not found in form conditions. Skipping.`\n )\n continue\n }\n if (model.conditions[condition].fn(state)) {\n return amount\n }\n }\n\n return options.amount\n }\n\n /**\n * Dispatcher for external redirect to GOV.UK Pay\n */\n static async dispatcher(\n request: FormRequestPayload,\n h: FormResponseToolkit,\n args: PaymentExternalArgs\n ): Promise<unknown> {\n const { options, name: componentName } = args.component\n const { model } = args.controller\n const language = resolveLanguage(request)\n const { t } = model.createTranslator(language)\n const state = await args.controller.getState(request)\n const { baseUrl } = getPluginOptions(request.server)\n const summaryUrl = `${baseUrl}/${model.basePath}/summary`\n\n const existingPaymentState = state[componentName]\n if (\n PaymentField.isPaymentState(existingPaymentState) &&\n existingPaymentState.preAuth?.status === 'success'\n ) {\n return h.redirect(summaryUrl).code(StatusCodes.SEE_OTHER)\n }\n\n const isLivePayment = args.isLive && !args.isPreview\n const formId = args.controller.model.formId\n const formsService = model.services.formsService\n const paymentService = await createPaymentService(\n isLivePayment,\n formId,\n formsService\n )\n\n const uuid = randomUUID()\n\n const reference = state.$$__referenceNumber as string\n const resolvedAmount = PaymentField.resolveAmount(options, model, state)\n\n // Zero-amount safety net (page skip should prevent this, but defensive)\n if (resolvedAmount === 0) {\n return h.redirect(summaryUrl).code(StatusCodes.SEE_OTHER)\n }\n\n const description = options.description\n\n const slug = `/${model.basePath}`\n\n const payCallbackUrl = `${baseUrl}/payment-callback?uuid=${uuid}`\n const paymentPageUrl = args.sourceUrl\n\n let prefilledEmail: string | undefined\n const userConfirmationEmail = state.userConfirmationEmailAddress\n if (typeof userConfirmationEmail === 'string' && userConfirmationEmail) {\n prefilledEmail = userConfirmationEmail\n }\n\n const amountInPence = Math.round(resolvedAmount * 100)\n const payment = await paymentService.createPayment(\n amountInPence,\n description,\n payCallbackUrl,\n reference,\n isLivePayment,\n { formId, slug },\n prefilledEmail\n )\n\n if (!payment) {\n const message = isLivePayment\n ? t('components.paymentField.cannotTakePayment')\n : t('components.paymentField.testApiKey')\n const govukError = createError(componentName, message)\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n return h\n .redirect(`${request.url.pathname}${request.url.search}`)\n .code(StatusCodes.SEE_OTHER)\n }\n\n const sessionData: PaymentSessionData = {\n uuid,\n formId,\n reference,\n amount: resolvedAmount,\n description,\n paymentId: payment.paymentId,\n componentName,\n returnUrl: summaryUrl,\n failureUrl: paymentPageUrl,\n isLivePayment\n }\n\n request.yar.set(`payment-${uuid}`, sessionData)\n\n return h.redirect(payment.paymentUrl).code(StatusCodes.SEE_OTHER)\n }\n\n /**\n * Called on form submission to capture the payment\n * @see https://docs.payments.service.gov.uk/delayed_capture/#delay-taking-a-payment\n */\n async onSubmit(\n request: FormRequestPayload,\n _metadata: FormMetadata,\n context: FormContext\n ): Promise<void> {\n const language = resolveLanguage(request)\n const { t } = this.model.createTranslator(language)\n\n // Zero-amount bypass — no capture needed\n const resolvedAmount = PaymentField.resolveAmount(\n this.options,\n this.model,\n context.state\n )\n if (resolvedAmount === 0) {\n return\n }\n\n const paymentState = this.getPaymentStateFromState(context.state)\n\n if (!paymentState) {\n throw new PaymentPreAuthError(\n this,\n t('components.paymentField.completePayment'),\n true,\n PaymentErrorTypes.PaymentIncomplete\n )\n }\n\n if (paymentState.capture?.status === 'success') {\n return\n }\n\n const { paymentId, isLivePayment, formId } = paymentState\n const formsService = this.model.services.formsService\n const paymentService = await createPaymentService(\n isLivePayment,\n formId,\n formsService\n )\n\n /**\n * @see https://docs.payments.service.gov.uk/api_reference/#payment-status-lifecycle\n */\n const status = await paymentService.getPaymentStatus(\n paymentId,\n isLivePayment\n )\n\n PaymentSubmissionError.checkPaymentAmount(\n status.amount,\n resolvedAmount,\n this,\n t\n )\n\n if (status.state.status === 'success') {\n await this.markPaymentCaptured(request, paymentState)\n return\n }\n\n if (status.state.status !== 'capturable') {\n throw new PaymentPreAuthError(\n this,\n t('components.paymentField.paymentExpired'),\n true,\n PaymentErrorTypes.PaymentExpired\n )\n }\n\n const captured = await paymentService.capturePayment(\n paymentId,\n status.amount\n )\n\n if (!captured) {\n throw new PaymentPreAuthError(\n this,\n t('components.paymentField.submissionFailed'),\n false\n )\n }\n\n await this.markPaymentCaptured(request, paymentState)\n }\n\n /**\n * Updates payment state to mark capture as successful\n * This ensures we don't try to re-capture on submission retry\n */\n private async markPaymentCaptured(\n request: FormRequestPayload,\n paymentState: PaymentState\n ): Promise<void> {\n const updatedState: PaymentState = {\n ...paymentState,\n capture: {\n status: 'success',\n createdAt: new Date().toISOString()\n }\n }\n\n if (this.page) {\n const currentState = await this.page.getState(request)\n await this.page.mergeState(request, currentState, {\n [this.name]: updatedState\n })\n }\n }\n}\n\n/**\n * Session data stored when dispatching to GOV.UK Pay\n */\nexport interface PaymentSessionData {\n uuid: string\n formId: string\n reference: string\n amount: number\n description: string\n paymentId: string\n componentName: string\n returnUrl: string\n failureUrl: string\n isLivePayment: boolean\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"PaymentField.js","names":["randomUUID","StatusCodes","joi","logger","COMPONENT_STATE_ERROR","FormComponent","createError","getPluginOptions","PaymentErrorTypes","PaymentPreAuthError","PaymentSubmissionError","createPaymentService","formatCurrency","resolveLanguage","PaymentField","isAppendageStateSingleObject","constructor","def","props","options","paymentStateSchema","object","paymentId","string","required","reference","amount","number","description","uuid","formId","isLivePayment","boolean","preAuth","status","valid","createdAt","isoDate","unknown","label","formSchema","stateSchema","getPaymentStateFromState","state","value","name","isPaymentState","undefined","getDisplayStringFromState","_translator","getViewModel","context","payload","viewModel","translator","tComponent","paymentState","Array","isArray","isState","getFormValue","getContextValueFromState","getAllPossibleErrors","baseErrors","type","template","advancedSettingsErrors","resolveAmount","model","conditionalAmounts","length","condition","conditions","warn","fn","dispatcher","request","h","args","componentName","component","controller","language","t","createTranslator","getState","baseUrl","server","summaryUrl","basePath","existingPaymentState","redirect","code","SEE_OTHER","isLive","isPreview","formsService","services","paymentService","$$__referenceNumber","resolvedAmount","slug","payCallbackUrl","paymentPageUrl","sourceUrl","prefilledEmail","userConfirmationEmail","userConfirmationEmailAddress","amountInPence","Math","round","payment","createPayment","message","govukError","yar","flash","url","pathname","search","sessionData","returnUrl","failureUrl","set","paymentUrl","onSubmit","_metadata","PaymentIncomplete","capture","getPaymentStatus","checkPaymentAmount","markPaymentCaptured","PaymentExpired","captured","capturePayment","updatedState","Date","toISOString","page","currentState","mergeState"],"sources":["../../../../../src/server/plugins/engine/components/PaymentField.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\n\nimport {\n type FormMetadata,\n type PaymentFieldComponent\n} from '@defra/forms-model'\nimport { StatusCodes } from 'http-status-codes'\nimport joi, { type ObjectSchema } from 'joi'\n\nimport { logger } from '~/src/server/common/helpers/logging/logger.js'\nimport { COMPONENT_STATE_ERROR } from '~/src/server/constants.js'\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type PaymentState } from '~/src/server/plugins/engine/components/PaymentField.types.js'\nimport { type RenderContext } from '~/src/server/plugins/engine/components/types.js'\nimport {\n createError,\n getPluginOptions\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n PaymentErrorTypes,\n PaymentPreAuthError,\n PaymentSubmissionError\n} from '~/src/server/plugins/engine/pageControllers/errors.js'\nimport {\n type FormContext,\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/plugins/engine/types/index.js'\nimport {\n type ErrorMessageTemplateList,\n type FormState,\n type FormStateValue,\n type FormSubmissionState,\n type PaymentExternalArgs\n} from '~/src/server/plugins/engine/types.js'\nimport {\n createPaymentService,\n formatCurrency\n} from '~/src/server/plugins/payment/helper.js'\nimport { resolveLanguage } from '~/src/server/utils/utils.js'\n\nexport class PaymentField extends FormComponent {\n declare options: PaymentFieldComponent['options']\n declare formSchema: ObjectSchema\n declare stateSchema: ObjectSchema\n declare def: PaymentFieldComponent\n isAppendageStateSingleObject = true\n\n constructor(\n def: PaymentFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n this.options = def.options\n this.def = def\n\n const paymentStateSchema = joi\n .object({\n paymentId: joi.string().required(),\n reference: joi.string().required(),\n amount: joi.number().required(),\n description: joi.string().required(),\n uuid: joi.string().uuid().required(),\n formId: joi.string().required(),\n isLivePayment: joi.boolean().required(),\n preAuth: joi\n .object({\n status: joi\n .string()\n .valid('success', 'failed', 'started')\n .required(),\n createdAt: joi.string().isoDate().required()\n })\n .required()\n })\n .unknown(true)\n .label(this.label)\n\n this.formSchema = paymentStateSchema\n // 'required()' forces the payment page to be invalid until we have valid payment state\n // i.e. the user will automatically be directed back to the payment page\n // if they attempt to access future pages when no payment entered yet\n this.stateSchema = paymentStateSchema.required()\n }\n\n /**\n * Gets the PaymentState from form submission state\n */\n getPaymentStateFromState(\n state: FormSubmissionState\n ): PaymentState | undefined {\n const value = state[this.name]\n return this.isPaymentState(value) ? value : undefined\n }\n\n getDisplayStringFromState(\n state: FormSubmissionState,\n _translator: Translator\n ): string {\n const value = this.getPaymentStateFromState(state)\n\n if (!value) {\n return ''\n }\n\n return `${formatCurrency(value.amount)} - ${value.description}`\n }\n\n getViewModel(context: RenderContext) {\n const { payload } = context\n const viewModel = super.getViewModel(context)\n const { translator } = context\n const { tComponent } = translator\n\n // Payload is pre-populated from state if a payment has already been made\n const paymentState = this.isPaymentState(payload[this.name] as unknown)\n ? (payload[this.name] as unknown as PaymentState)\n : undefined\n\n // Use payment state amount if pre-authorized, otherwise use default.\n // The page controller overrides this with the resolved conditional amount\n // using the full form state (which getViewModel doesn't have access to).\n const amount = paymentState?.amount ?? this.options.amount\n\n return {\n ...viewModel,\n amount: formatCurrency(amount),\n description:\n tComponent(this.def, 'paymentDescription') || this.options.description,\n paymentState\n }\n }\n\n /**\n * Type guard to check if value is PaymentState\n */\n isPaymentState(value: unknown): value is PaymentState {\n return PaymentField.isPaymentState(value)\n }\n\n /**\n * Static type guard to check if value is PaymentState\n */\n static isPaymentState(value: unknown): value is PaymentState {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return false\n }\n\n const state = value as PaymentState\n return (\n typeof state.paymentId === 'string' &&\n typeof state.amount === 'number' &&\n typeof state.description === 'string'\n )\n }\n\n /**\n * Override base isState to validate PaymentState\n */\n isState(value?: FormStateValue | FormState): value is FormState {\n return this.isPaymentState(value)\n }\n\n getFormValue(value?: FormStateValue | FormState) {\n return this.isPaymentState(value)\n ? (value as unknown as NonNullable<FormStateValue>)\n : undefined\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n return this.isPaymentState(state)\n ? `Reference: ${state.reference}\\nAmount: ${formatCurrency(state.amount)}`\n : ''\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return PaymentField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n {\n type: 'paymentRequired',\n template: 'Complete the payment to continue'\n }\n ],\n advancedSettingsErrors: []\n }\n }\n\n /**\n * Resolves the payment amount from conditional amounts configuration.\n * Evaluates conditions in order; first true condition wins.\n * Falls back to the default options.amount.\n */\n static resolveAmount(\n options: PaymentFieldComponent['options'],\n model: FormModel,\n state: FormState\n ): number {\n const { conditionalAmounts } = options\n\n if (!conditionalAmounts?.length) {\n return options.amount\n }\n\n for (const { condition, amount } of conditionalAmounts) {\n if (!model.conditions[condition]) {\n logger.warn(\n `[payment] Condition '${condition}' not found in form conditions. Skipping.`\n )\n continue\n }\n if (model.conditions[condition].fn(state)) {\n return amount\n }\n }\n\n return options.amount\n }\n\n /**\n * Dispatcher for external redirect to GOV.UK Pay\n */\n static async dispatcher(\n request: FormRequestPayload,\n h: FormResponseToolkit,\n args: PaymentExternalArgs\n ): Promise<unknown> {\n const { options, name: componentName } = args.component\n const { model } = args.controller\n const language = resolveLanguage(request)\n const { t } = model.createTranslator(language)\n const state = await args.controller.getState(request)\n const { baseUrl } = getPluginOptions(request.server)\n const summaryUrl = `${baseUrl}/${model.basePath}/summary`\n\n const existingPaymentState = state[componentName]\n if (\n PaymentField.isPaymentState(existingPaymentState) &&\n existingPaymentState.preAuth?.status === 'success'\n ) {\n return h.redirect(summaryUrl).code(StatusCodes.SEE_OTHER)\n }\n\n const isLivePayment = args.isLive && !args.isPreview\n const formId = args.controller.model.formId\n const formsService = model.services.formsService\n const paymentService = await createPaymentService(\n isLivePayment,\n formId,\n formsService\n )\n\n const uuid = randomUUID()\n\n const reference = state.$$__referenceNumber as string\n const resolvedAmount = PaymentField.resolveAmount(options, model, state)\n\n // Zero-amount safety net (page skip should prevent this, but defensive)\n if (resolvedAmount === 0) {\n return h.redirect(summaryUrl).code(StatusCodes.SEE_OTHER)\n }\n\n const description = options.description\n\n const slug = `/${model.basePath}`\n\n const payCallbackUrl = `${baseUrl}/payment-callback?uuid=${uuid}`\n const paymentPageUrl = args.sourceUrl\n\n let prefilledEmail: string | undefined\n const userConfirmationEmail = state.userConfirmationEmailAddress\n if (typeof userConfirmationEmail === 'string' && userConfirmationEmail) {\n prefilledEmail = userConfirmationEmail\n }\n\n const amountInPence = Math.round(resolvedAmount * 100)\n const payment = await paymentService.createPayment(\n amountInPence,\n description,\n payCallbackUrl,\n reference,\n isLivePayment,\n { formId, slug },\n prefilledEmail\n )\n\n if (!payment) {\n const message = isLivePayment\n ? t('components.paymentField.cannotTakePayment')\n : t('components.paymentField.testApiKey')\n const govukError = createError(componentName, message)\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n return h\n .redirect(`${request.url.pathname}${request.url.search}`)\n .code(StatusCodes.SEE_OTHER)\n }\n\n const sessionData: PaymentSessionData = {\n uuid,\n formId,\n reference,\n amount: resolvedAmount,\n description,\n paymentId: payment.paymentId,\n componentName,\n returnUrl: summaryUrl,\n failureUrl: paymentPageUrl,\n isLivePayment\n }\n\n request.yar.set(`payment-${uuid}`, sessionData)\n\n return h.redirect(payment.paymentUrl).code(StatusCodes.SEE_OTHER)\n }\n\n /**\n * Called on form submission to capture the payment\n * @see https://docs.payments.service.gov.uk/delayed_capture/#delay-taking-a-payment\n */\n async onSubmit(\n request: FormRequestPayload,\n _metadata: FormMetadata,\n context: FormContext\n ): Promise<void> {\n const language = resolveLanguage(request)\n const { t } = this.model.createTranslator(language)\n\n // Zero-amount bypass — no capture needed\n const resolvedAmount = PaymentField.resolveAmount(\n this.options,\n this.model,\n context.state\n )\n if (resolvedAmount === 0) {\n return\n }\n\n const paymentState = this.getPaymentStateFromState(context.state)\n\n if (!paymentState) {\n throw new PaymentPreAuthError(\n this,\n t('components.paymentField.completePayment'),\n true,\n PaymentErrorTypes.PaymentIncomplete\n )\n }\n\n if (paymentState.capture?.status === 'success') {\n return\n }\n\n const { paymentId, isLivePayment, formId } = paymentState\n const formsService = this.model.services.formsService\n const paymentService = await createPaymentService(\n isLivePayment,\n formId,\n formsService\n )\n\n /**\n * @see https://docs.payments.service.gov.uk/api_reference/#payment-status-lifecycle\n */\n const status = await paymentService.getPaymentStatus(\n paymentId,\n isLivePayment\n )\n\n PaymentSubmissionError.checkPaymentAmount(\n status.amount,\n resolvedAmount,\n this,\n t\n )\n\n if (status.state.status === 'success') {\n await this.markPaymentCaptured(request, paymentState)\n return\n }\n\n if (status.state.status !== 'capturable') {\n throw new PaymentPreAuthError(\n this,\n t('components.paymentField.paymentExpired'),\n true,\n PaymentErrorTypes.PaymentExpired\n )\n }\n\n const captured = await paymentService.capturePayment(\n paymentId,\n status.amount\n )\n\n if (!captured) {\n throw new PaymentPreAuthError(\n this,\n t('components.paymentField.submissionFailed'),\n false\n )\n }\n\n await this.markPaymentCaptured(request, paymentState)\n }\n\n /**\n * Updates payment state to mark capture as successful\n * This ensures we don't try to re-capture on submission retry\n */\n private async markPaymentCaptured(\n request: FormRequestPayload,\n paymentState: PaymentState\n ): Promise<void> {\n const updatedState: PaymentState = {\n ...paymentState,\n capture: {\n status: 'success',\n createdAt: new Date().toISOString()\n }\n }\n\n if (this.page) {\n const currentState = await this.page.getState(request)\n await this.page.mergeState(request, currentState, {\n [this.name]: updatedState\n })\n }\n }\n}\n\n/**\n * Session data stored when dispatching to GOV.UK Pay\n */\nexport interface PaymentSessionData {\n uuid: string\n formId: string\n reference: string\n amount: number\n description: string\n paymentId: string\n componentName: string\n returnUrl: string\n failureUrl: string\n isLivePayment: boolean\n}\n"],"mappings":"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","ignoreList":[]}
@@ -51,6 +51,7 @@ export declare class SelectField extends ListFormComponent {
51
51
  upload?: {
52
52
  count: number;
53
53
  summaryList: import("../types.js").SummaryList;
54
+ uploadingLabel?: string;
54
55
  };
55
56
  };
56
57
  }
@@ -52,6 +52,7 @@ export declare class SelectionControlField extends ListFormComponent {
52
52
  upload?: {
53
53
  count: number;
54
54
  summaryList: import("../../../../server/plugins/engine/types.js").SummaryList;
55
+ uploadingLabel?: string;
55
56
  };
56
57
  };
57
58
  /**
@@ -55,6 +55,7 @@ export declare class TelephoneNumberField extends FormComponent {
55
55
  upload?: {
56
56
  count: number;
57
57
  summaryList: import("../../../../server/plugins/engine/types.js").SummaryList;
58
+ uploadingLabel?: string;
58
59
  };
59
60
  };
60
61
  /**
@@ -69,6 +69,7 @@ export declare class UkAddressField extends FormComponent {
69
69
  upload?: {
70
70
  count: number;
71
71
  summaryList: import("../../../../server/plugins/engine/types.js").SummaryList;
72
+ uploadingLabel?: string;
72
73
  };
73
74
  };
74
75
  isState(value?: FormStateValue | FormState): value is UkAddressState;
@@ -104,6 +104,7 @@ export interface ViewModel extends Record<string, unknown> {
104
104
  upload?: {
105
105
  count: number;
106
106
  summaryList: SummaryList;
107
+ uploadingLabel?: string;
107
108
  };
108
109
  }
109
110
  export interface ComponentViewModel {
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../../../../src/server/plugins/engine/components/types.ts"],"sourcesContent":["import { type ComponentType, type Item } from '@defra/forms-model'\n\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport {\n type FormPayload,\n type FormSubmissionError,\n type FormValue,\n type SummaryList\n} from '~/src/server/plugins/engine/types.js'\n\nexport interface RenderContext {\n payload: FormPayload\n errors: FormSubmissionError[] | undefined\n translator: Translator\n isForceAccess?: boolean\n}\n\nexport type ComponentText = {\n classes?: string\n attributes?: string | Record<string, string>\n} & (\n | {\n text: string\n html?: string\n }\n | {\n text?: string\n html: string\n }\n)\n\nexport interface Label {\n text: string\n classes?: string\n html?: string\n isPageHeading?: boolean\n}\n\nexport interface Content {\n title?: string\n text: string\n condition?: string\n}\n\nexport interface BackLink {\n text: string\n href: string\n}\n\nexport type ListItemLabel = Omit<Label, 'text' | 'isPageHeading'>\n\nexport interface ListItem {\n text?: string\n value?: Item['value']\n hint?: {\n id?: string\n text: string\n }\n checked?: boolean\n selected?: boolean\n label?: ListItemLabel\n condition?: string\n}\n\nexport interface DateInputItem {\n label?: Label\n type?: string\n id?: string\n name?: string\n value?: Item['value']\n classes?: string\n // Prefix/suffix are used by location fields (e.g., LatLong, EastingNorthing) for units like \"°\"\n // but not by date fields. This interface is reused by both component types.\n prefix?: ComponentText\n suffix?: ComponentText\n errorMessage?: {\n text: string\n }\n condition?: undefined\n}\n\nexport interface ViewModel extends Record<string, unknown> {\n label?: Label\n type?: string\n id?: string\n name?: string\n value?: FormValue\n hint?: {\n id?: string\n text: string\n }\n prefix?: ComponentText\n suffix?: ComponentText\n classes?: string\n condition?: string\n errors?: FormSubmissionError[]\n errorMessage?: {\n text: string\n }\n summaryHtml?: string\n html?: string\n attributes: {\n autocomplete?: string\n maxlength?: number\n multiple?: string\n accept?: string\n inputmode?: string\n }\n content?: Content | Content[] | string\n maxlength?: number\n maxwords?: number\n rows?: number\n items?: ListItem[] | DateInputItem[]\n fieldset?: {\n attributes?: string | Record<string, string>\n legend?: Label\n }\n formGroup?: {\n classes?: string\n attributes?: string | Record<string, string>\n }\n showFieldsetError?: boolean\n components?: ComponentViewModel[]\n upload?: {\n count: number\n summaryList: SummaryList\n }\n}\n\nexport interface ComponentViewModel {\n type: ComponentType\n isFormComponent: boolean\n model: ViewModel\n}\n\nexport interface DatePartsState extends Record<string, number> {\n day: number\n month: number\n year: number\n}\n\nexport interface MonthYearState extends Record<string, number> {\n month: number\n year: number\n}\n\nexport interface EastingNorthingState extends Record<string, number> {\n easting: number\n northing: number\n}\n\nexport interface LatLongState extends Record<string, number> {\n latitude: number\n longitude: number\n}\n"],"mappings":"","ignoreList":[]}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../../../src/server/plugins/engine/components/types.ts"],"sourcesContent":["import { type ComponentType, type Item } from '@defra/forms-model'\n\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport {\n type FormPayload,\n type FormSubmissionError,\n type FormValue,\n type SummaryList\n} from '~/src/server/plugins/engine/types.js'\n\nexport interface RenderContext {\n payload: FormPayload\n errors: FormSubmissionError[] | undefined\n translator: Translator\n isForceAccess?: boolean\n}\n\nexport type ComponentText = {\n classes?: string\n attributes?: string | Record<string, string>\n} & (\n | {\n text: string\n html?: string\n }\n | {\n text?: string\n html: string\n }\n)\n\nexport interface Label {\n text: string\n classes?: string\n html?: string\n isPageHeading?: boolean\n}\n\nexport interface Content {\n title?: string\n text: string\n condition?: string\n}\n\nexport interface BackLink {\n text: string\n href: string\n}\n\nexport type ListItemLabel = Omit<Label, 'text' | 'isPageHeading'>\n\nexport interface ListItem {\n text?: string\n value?: Item['value']\n hint?: {\n id?: string\n text: string\n }\n checked?: boolean\n selected?: boolean\n label?: ListItemLabel\n condition?: string\n}\n\nexport interface DateInputItem {\n label?: Label\n type?: string\n id?: string\n name?: string\n value?: Item['value']\n classes?: string\n // Prefix/suffix are used by location fields (e.g., LatLong, EastingNorthing) for units like \"°\"\n // but not by date fields. This interface is reused by both component types.\n prefix?: ComponentText\n suffix?: ComponentText\n errorMessage?: {\n text: string\n }\n condition?: undefined\n}\n\nexport interface ViewModel extends Record<string, unknown> {\n label?: Label\n type?: string\n id?: string\n name?: string\n value?: FormValue\n hint?: {\n id?: string\n text: string\n }\n prefix?: ComponentText\n suffix?: ComponentText\n classes?: string\n condition?: string\n errors?: FormSubmissionError[]\n errorMessage?: {\n text: string\n }\n summaryHtml?: string\n html?: string\n attributes: {\n autocomplete?: string\n maxlength?: number\n multiple?: string\n accept?: string\n inputmode?: string\n }\n content?: Content | Content[] | string\n maxlength?: number\n maxwords?: number\n rows?: number\n items?: ListItem[] | DateInputItem[]\n fieldset?: {\n attributes?: string | Record<string, string>\n legend?: Label\n }\n formGroup?: {\n classes?: string\n attributes?: string | Record<string, string>\n }\n showFieldsetError?: boolean\n components?: ComponentViewModel[]\n upload?: {\n count: number\n summaryList: SummaryList\n uploadingLabel?: string\n }\n}\n\nexport interface ComponentViewModel {\n type: ComponentType\n isFormComponent: boolean\n model: ViewModel\n}\n\nexport interface DatePartsState extends Record<string, number> {\n day: number\n month: number\n year: number\n}\n\nexport interface MonthYearState extends Record<string, number> {\n month: number\n year: number\n}\n\nexport interface EastingNorthingState extends Record<string, number> {\n easting: number\n northing: number\n}\n\nexport interface LatLongState extends Record<string, number> {\n latitude: number\n longitude: number\n}\n"],"mappings":"","ignoreList":[]}
@@ -1,3 +1,4 @@
1
+ import { yesNoListNoItemId, yesNoListYesItemId } from '@defra/forms-model';
1
2
  import { createTranslator } from "./createTranslator.js";
2
3
  import { extractBaseTranslations } from "./extractBaseTranslations.js";
3
4
  import { createFormI18nInstance } from "./index.js";
@@ -12,6 +13,12 @@ export function loadFormTranslations(def, i18nInstance) {
12
13
  if (formTranslations) {
13
14
  for (const [lng, resources] of Object.entries(formTranslations)) {
14
15
  i18nInstance.addResourceBundle(lng, 'form', resources, true, true);
16
+
17
+ // Temporary workaround - until we develop a better solution
18
+ if (lng === 'cy') {
19
+ i18nInstance.addResource(lng, 'form', `listItems.${yesNoListYesItemId}.text`, 'Ie');
20
+ i18nInstance.addResource(lng, 'form', `listItems.${yesNoListNoItemId}.text`, 'Nage');
21
+ }
15
22
  }
16
23
  }
17
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"createFormTranslator.js","names":["createTranslator","extractBaseTranslations","createFormI18nInstance","createFormTranslator","def","language","baseTranslations","i18nInstance","loadFormTranslations","formTranslations","metadata","translations","lng","resources","Object","entries","addResourceBundle"],"sources":["../../../../../src/server/plugins/engine/i18n/createFormTranslator.ts"],"sourcesContent":["import { type FormDefinition } from '@defra/forms-model'\nimport { type i18n } from 'i18next'\n\nimport { createTranslator } from '~/src/server/plugins/engine/i18n/createTranslator.js'\nimport { extractBaseTranslations } from '~/src/server/plugins/engine/i18n/extractBaseTranslations.js'\nimport { createFormI18nInstance } from '~/src/server/plugins/engine/i18n/index.js'\nimport {\n type FormDefinitionTranslations,\n type Translator\n} from '~/src/server/plugins/engine/i18n/types.js'\n\nexport function createFormTranslator(\n def: FormDefinition,\n language: string\n): Translator {\n const baseTranslations = extractBaseTranslations(def)\n const i18nInstance = createFormI18nInstance(baseTranslations)\n loadFormTranslations(def, i18nInstance)\n\n return createTranslator(i18nInstance, language)\n}\n\nexport function loadFormTranslations(def: FormDefinition, i18nInstance: i18n) {\n const formTranslations = def.metadata?.translations as\n | FormDefinitionTranslations\n | undefined\n\n if (formTranslations) {\n for (const [lng, resources] of Object.entries(formTranslations)) {\n i18nInstance.addResourceBundle(lng, 'form', resources, true, true)\n }\n }\n}\n"],"mappings":"AAGA,SAASA,gBAAgB;AACzB,SAASC,uBAAuB;AAChC,SAASC,sBAAsB;AAM/B,OAAO,SAASC,oBAAoBA,CAClCC,GAAmB,EACnBC,QAAgB,EACJ;EACZ,MAAMC,gBAAgB,GAAGL,uBAAuB,CAACG,GAAG,CAAC;EACrD,MAAMG,YAAY,GAAGL,sBAAsB,CAACI,gBAAgB,CAAC;EAC7DE,oBAAoB,CAACJ,GAAG,EAAEG,YAAY,CAAC;EAEvC,OAAOP,gBAAgB,CAACO,YAAY,EAAEF,QAAQ,CAAC;AACjD;AAEA,OAAO,SAASG,oBAAoBA,CAACJ,GAAmB,EAAEG,YAAkB,EAAE;EAC5E,MAAME,gBAAgB,GAAGL,GAAG,CAACM,QAAQ,EAAEC,YAE1B;EAEb,IAAIF,gBAAgB,EAAE;IACpB,KAAK,MAAM,CAACG,GAAG,EAAEC,SAAS,CAAC,IAAIC,MAAM,CAACC,OAAO,CAACN,gBAAgB,CAAC,EAAE;MAC/DF,YAAY,CAACS,iBAAiB,CAACJ,GAAG,EAAE,MAAM,EAAEC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC;IACpE;EACF;AACF","ignoreList":[]}
1
+ {"version":3,"file":"createFormTranslator.js","names":["yesNoListNoItemId","yesNoListYesItemId","createTranslator","extractBaseTranslations","createFormI18nInstance","createFormTranslator","def","language","baseTranslations","i18nInstance","loadFormTranslations","formTranslations","metadata","translations","lng","resources","Object","entries","addResourceBundle","addResource"],"sources":["../../../../../src/server/plugins/engine/i18n/createFormTranslator.ts"],"sourcesContent":["import {\n yesNoListNoItemId,\n yesNoListYesItemId,\n type FormDefinition\n} from '@defra/forms-model'\nimport { type i18n } from 'i18next'\n\nimport { createTranslator } from '~/src/server/plugins/engine/i18n/createTranslator.js'\nimport { extractBaseTranslations } from '~/src/server/plugins/engine/i18n/extractBaseTranslations.js'\nimport { createFormI18nInstance } from '~/src/server/plugins/engine/i18n/index.js'\nimport {\n type FormDefinitionTranslations,\n type Translator\n} from '~/src/server/plugins/engine/i18n/types.js'\n\nexport function createFormTranslator(\n def: FormDefinition,\n language: string\n): Translator {\n const baseTranslations = extractBaseTranslations(def)\n const i18nInstance = createFormI18nInstance(baseTranslations)\n loadFormTranslations(def, i18nInstance)\n\n return createTranslator(i18nInstance, language)\n}\n\nexport function loadFormTranslations(def: FormDefinition, i18nInstance: i18n) {\n const formTranslations = def.metadata?.translations as\n | FormDefinitionTranslations\n | undefined\n\n if (formTranslations) {\n for (const [lng, resources] of Object.entries(formTranslations)) {\n i18nInstance.addResourceBundle(lng, 'form', resources, true, true)\n\n // Temporary workaround - until we develop a better solution\n if (lng === 'cy') {\n i18nInstance.addResource(\n lng,\n 'form',\n `listItems.${yesNoListYesItemId}.text`,\n 'Ie'\n )\n i18nInstance.addResource(\n lng,\n 'form',\n `listItems.${yesNoListNoItemId}.text`,\n 'Nage'\n )\n }\n }\n }\n}\n"],"mappings":"AAAA,SACEA,iBAAiB,EACjBC,kBAAkB,QAEb,oBAAoB;AAG3B,SAASC,gBAAgB;AACzB,SAASC,uBAAuB;AAChC,SAASC,sBAAsB;AAM/B,OAAO,SAASC,oBAAoBA,CAClCC,GAAmB,EACnBC,QAAgB,EACJ;EACZ,MAAMC,gBAAgB,GAAGL,uBAAuB,CAACG,GAAG,CAAC;EACrD,MAAMG,YAAY,GAAGL,sBAAsB,CAACI,gBAAgB,CAAC;EAC7DE,oBAAoB,CAACJ,GAAG,EAAEG,YAAY,CAAC;EAEvC,OAAOP,gBAAgB,CAACO,YAAY,EAAEF,QAAQ,CAAC;AACjD;AAEA,OAAO,SAASG,oBAAoBA,CAACJ,GAAmB,EAAEG,YAAkB,EAAE;EAC5E,MAAME,gBAAgB,GAAGL,GAAG,CAACM,QAAQ,EAAEC,YAE1B;EAEb,IAAIF,gBAAgB,EAAE;IACpB,KAAK,MAAM,CAACG,GAAG,EAAEC,SAAS,CAAC,IAAIC,MAAM,CAACC,OAAO,CAACN,gBAAgB,CAAC,EAAE;MAC/DF,YAAY,CAACS,iBAAiB,CAACJ,GAAG,EAAE,MAAM,EAAEC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC;;MAElE;MACA,IAAID,GAAG,KAAK,IAAI,EAAE;QAChBL,YAAY,CAACU,WAAW,CACtBL,GAAG,EACH,MAAM,EACN,aAAab,kBAAkB,OAAO,EACtC,IACF,CAAC;QACDQ,YAAY,CAACU,WAAW,CACtBL,GAAG,EACH,MAAM,EACN,aAAad,iBAAiB,OAAO,EACrC,MACF,CAAC;MACH;IACF;EACF;AACF","ignoreList":[]}
@@ -60,6 +60,12 @@
60
60
  "totalAmount": "Cyfanswm",
61
61
  "reference": "Cyfeirnod",
62
62
  "dateOfPayment": "Dyddiad talu",
63
+ "fileUpload_zero": "Ni uwchlwythwyd unrhyw ffeiliau",
64
+ "fileUpload_one": "Uwchlwythwyd [[count]] ffeil",
65
+ "fileUpload_two": "Uwchlwythwyd dwy feil",
66
+ "fileUpload_few": "Uwchlwythwyd [[count]] ffeil",
67
+ "fileUpload_many": "Uwchlwythwyd [[count]] o ffeiliau",
68
+ "fileUpload_other": "Uwchlwythwyd [[count]] ffeil",
63
69
  "submissionFailed": "Roedd problem ac ni chyflwynwyd eich ffurflen. Ceisiwch gyflwyno'r ffurflen eto",
64
70
  "submissionFailedContactSuffix": " neu gallwch [[contactUsLink]] a dyfynnu'ch cyfeirnod i drefnu ad-daliad",
65
71
  "contactUsLinkText": "cysylltu â ni (yn agor mewn tab newydd)",
@@ -79,17 +85,34 @@
79
85
  "change": "Newid",
80
86
  "notProvided": "Heb ei ddarparu",
81
87
  "visuallyHiddenItem": "eitem [[index]]",
88
+ "pageTitle_zero": "Nid ydych wedi ychwanegu unrhyw atebion",
82
89
  "pageTitle_one": "Rydych wedi ychwanegu [[count]] ateb",
90
+ "pageTitle_two": "Rydych wedi ychwanegu dau ateb",
91
+ "pageTitle_few": "Rydych wedi ychwanegu [[count]] ateb",
92
+ "pageTitle_many": "Rydych wedi ychwanegu [[count]] o atebion",
83
93
  "pageTitle_other": "Rydych wedi ychwanegu [[count]] ateb",
94
+ "tooMany_zero": "Ni allwch ychwanegu unrhyw atebion",
84
95
  "tooMany_one": "Dim ond hyd at [[count]] ateb y gallwch ei ychwanegu",
96
+ "tooMany_two": "Dim ond hyd at ddau gateb y gallwch eu hychwanegu",
97
+ "tooMany_few": "Dim ond hyd at [[count]] ateb y gallwch eu hychwanegu",
98
+ "tooMany_many": "Dim ond hyd at [[count]] o atebion y gallwch eu hychwanegu",
85
99
  "tooMany_other": "Dim ond hyd at [[count]] ateb y gallwch eu hychwanegu",
100
+ "tooFew_zero": "Rhaid i chi ychwanegu atebion",
86
101
  "tooFew_one": "Rhaid i chi ychwanegu o leiaf [[count]] ateb",
102
+ "tooFew_two": "Rhaid i chi ychwanegu o leiaf ddau gateb",
103
+ "tooFew_few": "Rhaid i chi ychwanegu o leiaf [[count]] ateb",
104
+ "tooFew_many": "Rhaid i chi ychwanegu o leiaf [[count]] o atebion",
87
105
  "tooFew_other": "Rhaid i chi ychwanegu o leiaf [[count]] ateb"
88
106
  },
89
107
 
90
108
  "fileUpload": {
91
109
  "upload": "Uwchlwytho ffeil",
92
- "maxFilesReached": "Rydych wedi cyrraedd nifer uchaf y ffeiliau. Tynnwch ffeil i uwchlwytho rhagor."
110
+ "maxFilesReached": "Rydych wedi cyrraedd nifer uchaf y ffeiliau. Tynnwch ffeil i uwchlwytho rhagor.",
111
+ "remove": "Tynnu",
112
+ "removeFileTitle": "Ydych chi'n siŵr eich bod chi eisiau dileu'r ffeil hon?",
113
+ "removeFileBody": "Ni allwch adfer ffeiliau a dynnwyd.",
114
+ "removeFileButton": "Dileu ffeil",
115
+ "cancel": "Canslo"
93
116
  },
94
117
 
95
118
  "exit": {
@@ -111,6 +134,7 @@
111
134
  "body": "Os hoffech dderbyn e-bost prawf, [[link]] yr hoffech i gyflwyniadau ffurflen fynd iddo.",
112
135
  "linkText": "nodwch y cyfeiriad e-bost (yn agor mewn tab newydd)"
113
136
  },
137
+ "bannerTitle": "Pwysig",
114
138
  "bannerForce": "Rhagolwg o ffurflen [[previewMode]] yw hon. Peidiwch â rhoi gwybodaeth bersonol.",
115
139
  "bannerText": "Dyma ragolwg o dudalen ffurflen [[previewMode]] rydych chi'n ei golygu.",
116
140
  "previousPages": "Mae'n dibynnu ar atebion o dudalennau cynharach yn y ffurflen. Yn y fersiwn fyw, bydd angen i ddefnyddwyr gwblhau'r cwestiynau hynny yn gyntaf."
@@ -134,7 +158,11 @@
134
158
  "findAnAddressInstead": "dod o hyd i gyfeiriad yn lle hynny",
135
159
  "noAddressFoundTitle": "Ni chanfuwyd cyfeiriad",
136
160
  "noAddressFoundBody": "Ni allem ddod o hyd i gyfeiriad cyfatebol",
161
+ "addressFound_zero": "Ni chanfuwyd unrhyw gyfeiriadau ar gyfer",
137
162
  "addressFound_one": "Canfuwyd [[count]] cyfeiriad ar gyfer",
163
+ "addressFound_two": "Canfuwyd dau gyfeiriad ar gyfer",
164
+ "addressFound_few": "Canfuwyd [[count]] chyfeiriad ar gyfer",
165
+ "addressFound_many": "Canfuwyd [[count]] o gyfeiriadau ar gyfer",
138
166
  "addressFound_other": "Canfuwyd [[count]] cyfeiriad ar gyfer",
139
167
  "validation": {
140
168
  "invalidPostcode": "Nodwch god post dilys neu nodwch gyfeiriad â llaw",
@@ -220,7 +248,7 @@
220
248
 
221
249
  "yesNoField": {
222
250
  "yes": "Ie",
223
- "no": "Na",
251
+ "no": "Nage",
224
252
  "validation": {
225
253
  "selectYesNoRequired": "{{#label}} - dewiswch ie neu na"
226
254
  }
@@ -236,7 +264,11 @@
236
264
  },
237
265
 
238
266
  "geospatialField": {
267
+ "added_zero": "Ni ychwanegwyd unrhyw leoliadau",
239
268
  "added_one": "Ychwanegwyd [[count]] lleoliad",
269
+ "added_two": "Ychwanegwyd dau leoliad",
270
+ "added_few": "Ychwanegwyd [[count]] lleoliad",
271
+ "added_many": "Ychwanegwyd [[count]] o leoliadau",
240
272
  "added_other": "Ychwanegwyd [[count]] lleoliad",
241
273
  "validation": {
242
274
  "descriptionRequired": "Rhowch ddisgrifiad ar gyfer lleoliad [[count]]",
@@ -245,7 +277,7 @@
245
277
  },
246
278
 
247
279
  "paymentField": {
248
- "detailsRequired": "Angen manylion talu",
280
+ "detailsRequired": "Angen talu",
249
281
  "pendingTransaction": "Mae'n bosibl y gwelwch drafodiad ar y gweill yn eich cyfrif banc ond dim ond pan fyddwch yn cyflwyno'r ffurflen y codir tâl arnoch.",
250
282
  "addPaymentDetails": "Ychwanegu manylion talu",
251
283
  "cannotTakePayment": "Mae problem ac ni allwn dderbyn taliad. Cysylltwch â ni (manylion yn nhroedyn y ffurflen hon) neu cadwch eich cynnydd a dychwelyd at y ffurflen yn ddiweddarach.",
@@ -261,11 +293,29 @@
261
293
  },
262
294
 
263
295
  "fileUploadField": {
296
+ "uploading": "Yn uwchlwytho…",
264
297
  "uploaded": "Wedi'i uwchlwytho",
265
298
  "uploadFailed": "Roedd problem gyda'ch ffeiliau a uwchlwythwyd. Uwchlwythwch nhw eto cyn cyflwyno'r ffurflen.",
266
299
  "uploadedFilesHeading": "Ffeiliau a uwchlwythwyd",
300
+ "filesCount_zero": "Uwchlwythwyd [[count]] o ffeiliau",
267
301
  "filesCount_one": "Uwchlwythwyd [[count]] ffeil",
302
+ "filesCount_two": "Uwchlwythwyd [[count]] ffeil",
303
+ "filesCount_few": "Uwchlwythwyd [[count]] ffeil",
304
+ "filesCount_many": "Uwchlwythwyd [[count]] o ffeiliau",
268
305
  "filesCount_other": "Uwchlwythwyd [[count]] ffeil"
306
+ },
307
+
308
+ "characterCount": {
309
+ "_comment": "Note the use of %{count} instead of [[count]]. This is because we are passing to the govuk template, not passing to Joi",
310
+ "underLimit": {
311
+ "one": "Mae gennych %{count} nod ar ôl",
312
+ "other": "Mae gennych %{count} nod ar ôl"
313
+ },
314
+ "atLimit": "Mae gennych 0 nod ar ôl",
315
+ "overLimit": {
316
+ "one": "Mae gennych %{count} nod yn ormod",
317
+ "other": "Mae gennych %{count} nod yn ormod"
318
+ }
269
319
  }
270
320
  }
271
321
  }
@@ -60,6 +60,8 @@
60
60
  "totalAmount": "Total amount",
61
61
  "reference": "Reference",
62
62
  "dateOfPayment": "Date of payment",
63
+ "fileUpload_one": "Uploaded [[count]] file",
64
+ "fileUpload_other": "Uploaded [[count]] files",
63
65
  "submissionFailed": "There was a problem and your form was not submitted. Try submitting the form again",
64
66
  "submissionFailedContactSuffix": " or you can [[contactUsLink]] and quote your reference number to arrange a refund",
65
67
  "contactUsLinkText": "contact us (opens in new tab)",
@@ -89,7 +91,12 @@
89
91
 
90
92
  "fileUpload": {
91
93
  "upload": "Upload file",
92
- "maxFilesReached": "You have reached the maximum number of files. Please remove a file to upload more."
94
+ "maxFilesReached": "You have reached the maximum number of files. Please remove a file to upload more.",
95
+ "remove": "Remove",
96
+ "removeFileTitle": "Are you sure you want to remove this file?",
97
+ "removeFileBody": "You cannot recover removed files.",
98
+ "removeFileButton": "Remove file",
99
+ "cancel": "Cancel"
93
100
  },
94
101
 
95
102
  "exit": {
@@ -111,6 +118,7 @@
111
118
  "body": "If you want to receive a test email, [[link]] you want form submissions to go to.",
112
119
  "linkText": "enter the email address (opens in new tab)"
113
120
  },
121
+ "bannerTitle": "Important",
114
122
  "bannerForce": "This is a preview of a [[previewMode]] form. Do not enter personal information.",
115
123
  "bannerText": "This is a preview of a [[previewMode]] form page you are editing.",
116
124
  "previousPages": "It depends on answers from earlier pages in the form. In the live version, users will need to complete those questions first."
@@ -245,7 +253,7 @@
245
253
  },
246
254
 
247
255
  "paymentField": {
248
- "detailsRequired": "Payment details required",
256
+ "detailsRequired": "Payment required",
249
257
  "pendingTransaction": "You may see a pending transaction in your bank account but you will only be charged when you submit the form.",
250
258
  "addPaymentDetails": "Add payment details",
251
259
  "cannotTakePayment": "There is a problem and we cannot take a payment. Contact us (details in the footer of this form) or save your progress and return to the form later.",
@@ -261,11 +269,25 @@
261
269
  },
262
270
 
263
271
  "fileUploadField": {
272
+ "uploading": "Uploading…",
264
273
  "uploaded": "Uploaded",
265
274
  "uploadFailed": "There was a problem with your uploaded files. Re-upload them before submitting the form again.",
266
275
  "uploadedFilesHeading": "Uploaded files",
267
276
  "filesCount_one": "[[count]] file uploaded",
268
277
  "filesCount_other": "[[count]] files uploaded"
278
+ },
279
+
280
+ "characterCount": {
281
+ "_comment": "Note the use of %{count} instead of [[count]]. This is because we are passing to the govuk template, not passing to Joi",
282
+ "underLimit": {
283
+ "one": "You have %{count} character remaining",
284
+ "other": "You have %{count} characters remaining"
285
+ },
286
+ "atLimit": "You have 0 characters remaining",
287
+ "overLimit": {
288
+ "one": "You have %{count} character too many",
289
+ "other": "You have %{count} characters too many"
290
+ }
269
291
  }
270
292
  }
271
293
  }
@@ -13,6 +13,7 @@ export type FormDefinitionTranslations = Record<string, {
13
13
  hint: string;
14
14
  content: string;
15
15
  shortDescription: string;
16
+ paymentDescription: string;
16
17
  }>>;
17
18
  sections: Record<string, Partial<Pick<Section, 'title'>>>;
18
19
  listItems: Record<string, Partial<Pick<Item, 'text' | 'hint'>>>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../../../../src/server/plugins/engine/i18n/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Item,\n type Page,\n type Section\n} from '@defra/forms-model'\n\n/**\n * Structure of the `translations` block in a form definition.\n * Also used to derive the valid property keys for the typed tX functions.\n */\nexport type FormDefinitionTranslations = Record<\n string,\n {\n pages: Record<string, Partial<{ title: string; repeatTitle: string }>>\n // ComponentDef is a discriminated union; Pick across the full union is not feasible.\n // The four keys below are the complete set of translatable component properties.\n components: Record<\n string,\n Partial<{\n title: string\n hint: string\n content: string\n shortDescription: string\n }>\n >\n sections: Record<string, Partial<Pick<Section, 'title'>>>\n listItems: Record<string, Partial<Pick<Item, 'text' | 'hint'>>>\n form: Record<string, string>\n }\n>\n\ntype EntityTranslations = FormDefinitionTranslations[string]\n\n/**\n * Scoped translator returned by model.createTranslator(language).\n * t — plugin namespace (UI strings, buttons, errors, sub-field labels)\n * tForm — form namespace: form attributes, currently only form name\n * tPage — form namespace: page titles\n * tComponent — form namespace: component titles, hints, shortDescriptions\n * tSection — form namespace: section titles\n * tListItem — form namespace: list item text\n * language - current language\n */\nexport interface Translator {\n t: (key: string, opts?: Record<string, unknown>) => string\n tForm: (prop: string) => string\n tPage: (page: Page, prop: keyof EntityTranslations['pages'][string]) => string\n tComponent: (\n component: ComponentDef,\n prop: keyof EntityTranslations['components'][string]\n ) => string\n tSection: (\n section: Section,\n prop: keyof EntityTranslations['sections'][string]\n ) => string\n tListItem: (\n item: Item,\n prop: keyof EntityTranslations['listItems'][string]\n ) => string\n language: string\n}\n"],"mappings":"","ignoreList":[]}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../../../src/server/plugins/engine/i18n/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Item,\n type Page,\n type Section\n} from '@defra/forms-model'\n\n/**\n * Structure of the `translations` block in a form definition.\n * Also used to derive the valid property keys for the typed tX functions.\n */\nexport type FormDefinitionTranslations = Record<\n string,\n {\n pages: Record<string, Partial<{ title: string; repeatTitle: string }>>\n // ComponentDef is a discriminated union; Pick across the full union is not feasible.\n // The four keys below are the complete set of translatable component properties.\n components: Record<\n string,\n Partial<{\n title: string\n hint: string\n content: string\n shortDescription: string\n paymentDescription: string\n }>\n >\n sections: Record<string, Partial<Pick<Section, 'title'>>>\n listItems: Record<string, Partial<Pick<Item, 'text' | 'hint'>>>\n form: Record<string, string>\n }\n>\n\ntype EntityTranslations = FormDefinitionTranslations[string]\n\n/**\n * Scoped translator returned by model.createTranslator(language).\n * t — plugin namespace (UI strings, buttons, errors, sub-field labels)\n * tForm — form namespace: form attributes, currently only form name\n * tPage — form namespace: page titles\n * tComponent — form namespace: component titles, hints, shortDescriptions\n * tSection — form namespace: section titles\n * tListItem — form namespace: list item text\n * language - current language\n */\nexport interface Translator {\n t: (key: string, opts?: Record<string, unknown>) => string\n tForm: (prop: string) => string\n tPage: (page: Page, prop: keyof EntityTranslations['pages'][string]) => string\n tComponent: (\n component: ComponentDef,\n prop: keyof EntityTranslations['components'][string]\n ) => string\n tSection: (\n section: Section,\n prop: keyof EntityTranslations['sections'][string]\n ) => string\n tListItem: (\n item: Item,\n prop: keyof EntityTranslations['listItems'][string]\n ) => string\n language: string\n}\n"],"mappings":"","ignoreList":[]}
@@ -133,24 +133,26 @@ export class FileUploadPageController extends QuestionPageController {
133
133
  const {
134
134
  filename
135
135
  } = fileToRemove.status.form.file;
136
+ const translator = this.getTranslator(request);
136
137
  const {
137
138
  t
138
- } = this.getTranslator(request);
139
+ } = translator;
139
140
  return h.view(this.fileDeleteViewName, {
140
141
  ...viewModel,
141
142
  context,
142
143
  backLink: this.getBackLink(request, context, t),
143
- pageTitle: `Are you sure you want to remove this file?`,
144
+ pageTitle: t('pages.fileUpload.removeFileTitle'),
144
145
  itemTitle: filename,
145
146
  confirmation: {
146
- text: 'You cannot recover removed files.'
147
+ text: t('pages.fileUpload.removeFileBody')
147
148
  },
148
149
  buttonConfirm: {
149
- text: 'Remove file'
150
+ text: t('pages.fileUpload.removeFileButton')
150
151
  },
151
152
  buttonCancel: {
152
- text: 'Cancel'
153
- }
153
+ text: t('pages.fileUpload.cancel')
154
+ },
155
+ t
154
156
  });
155
157
  };
156
158
  }
@@ -219,6 +221,9 @@ export class FileUploadPageController extends QuestionPageController {
219
221
  const {
220
222
  state
221
223
  } = context;
224
+ const {
225
+ t
226
+ } = translator;
222
227
  const upload = this.getUploadFromState(state);
223
228
  const viewModel = super.getViewModel(request, context, translator);
224
229
  const {
@@ -229,6 +234,13 @@ export class FileUploadPageController extends QuestionPageController {
229
234
  const [formComponent] = components.filter(({
230
235
  model
231
236
  }) => model.id === fileUpload.name);
237
+ formComponent.model.upload = {
238
+ count: formComponent.model.upload?.count ?? 0,
239
+ summaryList: formComponent.model.upload?.summaryList ?? {
240
+ rows: []
241
+ },
242
+ uploadingLabel: t('components.fileUploadField.uploading')
243
+ };
232
244
  const index = components.indexOf(formComponent);
233
245
  const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl);
234
246
  return {
@@ -239,7 +251,8 @@ export class FileUploadPageController extends QuestionPageController {
239
251
  // Split out components before/after
240
252
  componentsBefore: components.slice(0, index),
241
253
  components: components.slice(index),
242
- proxyUrl
254
+ proxyUrl,
255
+ t
243
256
  };
244
257
  }
245
258