import { ComponentType, hasFormComponents, isFormType, type DeclarationFieldComponent, type Item } from '@defra/forms-model' import joi, { type ArraySchema, type BooleanSchema, type StringSchema } from 'joi' import { FormComponent, isFormValue } from '~/src/server/plugins/engine/components/FormComponent.js' import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js' import { type ErrorMessageTemplateList, type FormPayload, type FormState, type FormStateValue, type FormSubmissionError, type FormSubmissionState, type FormValue } from '~/src/server/plugins/engine/types.js' export class DeclarationField extends FormComponent { private readonly DEFAULT_DECLARATION_LABEL = 'I understand and agree' declare options: DeclarationFieldComponent['options'] declare declarationConfirmationLabel: string declare formSchema: ArraySchema declare stateSchema: BooleanSchema declare content: string headerStartLevel: number constructor( def: DeclarationFieldComponent, props: ConstructorParameters[1] ) { super(def, props) const { options, content } = def let checkboxSchema = joi.string().valid('true') if (options.required !== false) { checkboxSchema = checkboxSchema.required() } const formSchema = joi .array() .items(checkboxSchema, joi.string().valid('unchecked').strip()) .label(this.label) .single() .messages({ 'any.required': messageTemplate.declarationRequired as string, 'any.unknown': messageTemplate.declarationRequired as string, 'array.includesRequiredUnknowns': messageTemplate.declarationRequired as string }) as ArraySchema this.formSchema = formSchema this.stateSchema = joi.boolean().cast('string').label(this.label).required() this.options = options this.content = content this.declarationConfirmationLabel = options.declarationConfirmationLabel ?? this.DEFAULT_DECLARATION_LABEL const formComponents = hasFormComponents(props.page?.pageDef) ? props.page.pageDef.components : [] const numOfQuestionsOnPage = formComponents.filter((q) => isFormType(q.type) ).length const hasGuidance = formComponents.some( (comp, idx) => comp.type === ComponentType.Markdown && idx === 0 ) this.headerStartLevel = numOfQuestionsOnPage < 2 && !hasGuidance ? 2 : 3 } getFormValueFromState(state: FormSubmissionState) { const { name } = this return state[name] === true ? 'true' : 'false' } getFormDataFromState(state: FormSubmissionState): FormPayload { const { name } = this return { [name]: state[name] === true ? 'true' : 'false' } } getStateFromValidForm(payload: FormPayload): FormState { const { name } = this const payloadValue = payload[name] const value = this.isValue(payloadValue) && payloadValue.length > 0 && payloadValue.every((v) => { return v === 'true' }) return { [name]: value } } getContextValueFromFormValue(value: FormValue | FormPayload): boolean { return value === 'true' } getFormValue(value?: FormStateValue | FormState) { return this.isValue(value) ? value : undefined } getDisplayStringFromFormValue(value: FormValue | FormPayload): string { return value === 'true' ? this.declarationConfirmationLabel : 'Not provided' } getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) { const defaultDeclarationConfirmationLabel = 'I confirm that I understand and accept this declaration' const { hint, content, declarationConfirmationLabel = defaultDeclarationConfirmationLabel } = this const viewModel = super.getViewModel(payload, errors) let { fieldset, label } = viewModel fieldset ??= { legend: { text: label.text, classes: 'govuk-fieldset__legend--m' } } const payloadValue = payload[this.name] const isChecked = payloadValue === 'true' || payloadValue === true || (Array.isArray(payloadValue) && payloadValue.some((x) => x === 'true')) return { ...viewModel, hint: hint ? { text: hint } : undefined, fieldset, content, items: [ { text: declarationConfirmationLabel, value: 'true', checked: isChecked } ], headerStartLevel: this.headerStartLevel } } isValue(value?: FormStateValue | FormState): value is Item['value'][] { if (!Array.isArray(value)) { return false } // Skip checks when empty if (!value.length) { return true } return value.every(isFormValue) } /** * For error preview page that shows all possible errors on a component */ getAllPossibleErrors(): ErrorMessageTemplateList { return DeclarationField.getAllPossibleErrors() } /** * Static version of getAllPossibleErrors that doesn't require a component instance. */ static getAllPossibleErrors(): ErrorMessageTemplateList { return { baseErrors: [ { type: 'required', template: messageTemplate.declarationRequired } ], advancedSettingsErrors: [] } } static isBool(value?: FormStateValue | FormState): value is boolean { return isFormValue(value) && typeof value === 'boolean' } }