@defra/forms-engine-plugin 3.0.7 → 3.0.9
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.
- package/.public/javascripts/application.min.js +1 -1
- package/.public/javascripts/application.min.js.map +1 -1
- package/.public/javascripts/shared.min.js +1 -1
- package/.public/javascripts/shared.min.js.map +1 -1
- package/.server/client/javascripts/file-upload.d.ts +1 -0
- package/.server/client/javascripts/file-upload.js +8 -2
- package/.server/client/javascripts/file-upload.js.map +1 -1
- package/.server/server/forms/register-as-a-unicorn-breeder.yaml +1 -0
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +3 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +40 -37
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/routes/file-upload.js +3 -1
- package/.server/server/plugins/engine/routes/file-upload.js.map +1 -1
- package/.server/server/plugins/engine/types/schema.js +2 -1
- package/.server/server/plugins/engine/types/schema.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +1 -0
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/views/summary.html +12 -2
- package/package.json +2 -2
- package/src/client/javascripts/file-upload.js +16 -2
- package/src/server/forms/register-as-a-unicorn-breeder.yaml +1 -0
- package/src/server/index.test.ts +3 -1
- package/src/server/plugins/engine/pageControllers/SummaryPageController.test.ts +85 -0
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +42 -33
- package/src/server/plugins/engine/routes/file-upload.ts +3 -1
- package/src/server/plugins/engine/types/schema.test.ts +34 -0
- package/src/server/plugins/engine/types/schema.ts +6 -1
- package/src/server/plugins/engine/types.ts +1 -0
- package/src/server/plugins/engine/views/summary.html +12 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build the upload status URL given the current pathname and the upload ID.
|
|
3
|
+
* This only works when called on a file upload page that has a maximum depth of 1 URL segments following the slug.
|
|
3
4
|
* @param {string} pathname – e.g. window.location.pathname
|
|
4
5
|
* @param {string} uploadId
|
|
5
6
|
* @returns {string} e.g. "/form/upload-status/abc123"
|
|
@@ -190,13 +190,19 @@ function reloadPage() {
|
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
192
|
* Build the upload status URL given the current pathname and the upload ID.
|
|
193
|
+
* This only works when called on a file upload page that has a maximum depth of 1 URL segments following the slug.
|
|
193
194
|
* @param {string} pathname – e.g. window.location.pathname
|
|
194
195
|
* @param {string} uploadId
|
|
195
196
|
* @returns {string} e.g. "/form/upload-status/abc123"
|
|
196
197
|
*/
|
|
197
198
|
export function buildUploadStatusUrl(pathname, uploadId) {
|
|
198
|
-
|
|
199
|
-
const
|
|
199
|
+
// Remove preview markers and duplicate slashes
|
|
200
|
+
const normalisedPath = pathname.replace(/\/preview\/(draft|live)/g, '').replace(/\/{2,}/g, '/').replace(/\/$/, '');
|
|
201
|
+
const segments = normalisedPath.split('/').filter(Boolean);
|
|
202
|
+
|
|
203
|
+
// The slug is always the second to last segment
|
|
204
|
+
// The prefix is everything before the slug
|
|
205
|
+
const prefix = segments.length > 2 ? `/${segments.slice(0, segments.length - 2).join('/')}` : '';
|
|
200
206
|
return `${prefix}/upload-status/${uploadId}`;
|
|
201
207
|
}
|
|
202
208
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-upload.js","names":["MAX_POLLING_DURATION","ARIA_DESCRIBEDBY","ERROR_SUMMARY_TITLE_ID","createOrUpdateStatusAnnouncer","form","fileCountP","statusAnnouncer","querySelector","document","createElement","id","className","setAttribute","addStatusAnnouncerToDOM","asHTMLElement","appendChild","body","nextSibling","parentNode","insertBefore","parentElement","findOrCreateSummaryList","summaryList","buttonGroup","createFileRow","selectedFile","statusText","row","name","innerHTML","renderSummary","container","getElementById","uploadForm","closest","HTMLFormElement","fileInput","existingRow","remove","firstChild","textContent","showError","message","errorSummary","topErrorSummary","titleElement","removeAttribute","formGroup","classList","add","inputId","errorMessage","element","reloadPage","window","history","replaceState","location","href","pathname","buildUploadStatusUrl","uploadId","pathSegments","split","filter","segment","prefix","length","pollUploadStatus","attempts","interval","setInterval","clearInterval","uploadStatusUrl","fetch","headers","Accept","then","response","ok","Error","json","data","uploadStatus","catch","handleStandardFormSubmission","formElement","uploadButton","continueButton","focus","setTimeout","disabled","handleAjaxFormSubmission","event","action","preventDefault","formData","FormData","isLocalDev","dataset","proxyUrl","uploadUrl","fetchOptions","method","redirect","mode","initUpload","Array","from","querySelectorAll","find","button","trim","isSubmitting","addEventListener","files","initFileUpload","readyState"],"sources":["../../../src/client/javascripts/file-upload.js"],"sourcesContent":["export const MAX_POLLING_DURATION = 300 // 5 minutes\nconst ARIA_DESCRIBEDBY = 'aria-describedby'\nconst ERROR_SUMMARY_TITLE_ID = 'error-summary-title'\n\n/**\n * Creates or updates status announcer for screen readers\n * @param {HTMLElement | null} form - The form element\n * @param {HTMLElement | null} fileCountP - The file count paragraph element\n * @returns {HTMLElement} The status announcer element\n */\nfunction createOrUpdateStatusAnnouncer(form, fileCountP) {\n let statusAnnouncer = form?.querySelector('#statusInformation')\n\n if (!statusAnnouncer) {\n statusAnnouncer = document.createElement('div')\n statusAnnouncer.id = 'statusInformation'\n statusAnnouncer.className = 'govuk-visually-hidden'\n statusAnnouncer.setAttribute('aria-live', 'polite')\n\n // multiple fallbacks to ensure the status announcer is always added to the DOM\n // this helps with cross-browser compatibility and unexpected DOM structures encountered during QA\n try {\n addStatusAnnouncerToDOM(\n asHTMLElement(form),\n asHTMLElement(fileCountP),\n asHTMLElement(statusAnnouncer)\n )\n } catch {\n try {\n form?.appendChild(statusAnnouncer)\n } catch {\n document.body.appendChild(statusAnnouncer)\n }\n }\n }\n\n return /** @type {HTMLElement} */ (statusAnnouncer)\n}\n\n/**\n * Helper function to add the status announcer to the DOM\n * @param {HTMLElement} form - The form element\n * @param {HTMLElement | null} fileCountP - The file count paragraph element\n * @param {HTMLElement} statusAnnouncer - The status announcer element to add\n */\nfunction addStatusAnnouncerToDOM(form, fileCountP, statusAnnouncer) {\n if (fileCountP?.nextSibling && fileCountP.parentNode === form) {\n form.insertBefore(statusAnnouncer, fileCountP.nextSibling)\n return\n }\n\n const parentElement = fileCountP?.parentNode ?? form\n parentElement.appendChild(statusAnnouncer)\n}\n\n/**\n * Finds an existing summary list or creates a new one\n * @param {HTMLFormElement} form - The form element\n * @param {HTMLElement} fileCountP - The file count paragraph element\n * @returns {HTMLElement} The summary list element\n */\nfunction findOrCreateSummaryList(form, fileCountP) {\n let summaryList = form.querySelector('dl.govuk-summary-list')\n\n if (!summaryList) {\n summaryList = document.createElement('dl')\n summaryList.className = 'govuk-summary-list govuk-summary-list--long-key'\n\n const buttonGroup = form.querySelector('.govuk-button-group')\n\n if (buttonGroup) {\n form.insertBefore(summaryList, buttonGroup)\n } else {\n form.insertBefore(summaryList, fileCountP.nextSibling)\n }\n }\n\n return /** @type {HTMLElement} */ (summaryList)\n}\n\n/**\n * Creates a file row element for the summary list\n * @param {File | null} selectedFile - The selected file\n * @param {string} statusText - The status to display\n * @returns {HTMLElement} The created row element\n */\nfunction createFileRow(selectedFile, statusText) {\n const row = document.createElement('div')\n row.className = 'govuk-summary-list__row'\n row.setAttribute('data-filename', selectedFile?.name ?? '')\n row.innerHTML = `\n <dt class=\"govuk-summary-list__key\">\n ${selectedFile?.name ?? ''}\n </dt>\n <dd class=\"govuk-summary-list__value\">\n <strong class=\"govuk-tag govuk-tag--yellow\">${statusText}</strong>\n </dd>\n <dd class=\"govuk-summary-list__actions\">\n </dd>\n `\n return row\n}\n\n/**\n * Renders or updates the file summary box for the selected file\n * @param {File | null} selectedFile - The selected file\n * @param {string} statusText - The status to display\n * @param {HTMLElement} form - The form element\n */\nfunction renderSummary(selectedFile, statusText, form) {\n const container = document.getElementById('uploadedFilesContainer')\n const uploadForm = container ? container.closest('form') : null\n\n if (!uploadForm || !(uploadForm instanceof HTMLFormElement)) {\n return\n }\n\n const fileCountP = uploadForm.querySelector('p.govuk-body')\n\n if (!fileCountP) {\n return\n }\n\n const statusAnnouncer = createOrUpdateStatusAnnouncer(\n /** @type {HTMLElement} */ (uploadForm),\n /** @type {HTMLElement | null} */ (fileCountP)\n )\n\n const fileInput = form.querySelector('input[type=\"file\"]')\n\n if (fileInput) {\n fileInput.setAttribute(ARIA_DESCRIBEDBY, 'statusInformation')\n }\n\n const summaryList = findOrCreateSummaryList(\n /** @type {HTMLFormElement} */ (uploadForm),\n /** @type {HTMLElement} */ (fileCountP)\n )\n\n const existingRow = document.querySelector(\n `[data-filename=\"${selectedFile?.name}\"]`\n )\n\n if (existingRow) {\n existingRow.remove()\n }\n\n const row = createFileRow(selectedFile, statusText)\n summaryList.insertBefore(row, summaryList.firstChild)\n statusAnnouncer.textContent = `${selectedFile?.name ?? ''} ${statusText}`\n}\n\n/**\n * Shows an error message using the GOV.UK error summary component\n * and adds inline error styling to the file input\n * @param {string} message - The error message to display\n * @param {HTMLElement | null} errorSummary - The error summary container\n * @param {HTMLInputElement} fileInput - The file input element\n * @returns {void}\n */\nfunction showError(message, errorSummary, fileInput) {\n const topErrorSummary = document.querySelector('.govuk-error-summary')\n\n if (topErrorSummary) {\n const titleElement = document.getElementById(ERROR_SUMMARY_TITLE_ID)\n if (titleElement) {\n fileInput.setAttribute(ARIA_DESCRIBEDBY, ERROR_SUMMARY_TITLE_ID)\n } else {\n fileInput.removeAttribute(ARIA_DESCRIBEDBY)\n }\n return\n }\n\n if (errorSummary) {\n errorSummary.innerHTML = `\n <div class=\"govuk-error-summary\" data-module=\"govuk-error-summary\">\n <div role=\"alert\">\n <h2 class=\"govuk-error-summary__title\" id=\"${ERROR_SUMMARY_TITLE_ID}\">\n There is a problem\n </h2>\n <div class=\"govuk-error-summary__body\">\n <ul class=\"govuk-list govuk-error-summary__list\">\n <li>\n <a href=\"#file-upload\">${message}</a>\n </li>\n </ul>\n </div>\n </div>\n </div>\n `\n\n fileInput.setAttribute(ARIA_DESCRIBEDBY, ERROR_SUMMARY_TITLE_ID)\n }\n\n const formGroup = fileInput.closest('.govuk-form-group')\n if (formGroup) {\n formGroup.classList.add('govuk-form-group--error')\n fileInput.classList.add('govuk-file-upload--error')\n\n const inputId = fileInput.id\n let errorMessage = document.getElementById(`${inputId}-error`)\n\n if (!errorMessage) {\n errorMessage = document.createElement('p')\n errorMessage.id = `${inputId}-error`\n errorMessage.className = 'govuk-error-message'\n errorMessage.innerHTML = `<span class=\"govuk-visually-hidden\">Error:</span> ${message}`\n formGroup.insertBefore(errorMessage, fileInput)\n }\n\n fileInput.setAttribute(\n ARIA_DESCRIBEDBY,\n `error-summary-title ${inputId}-error`\n )\n }\n}\n\n/**\n * Helper to safely convert an Element to HTMLElement\n * @param {Element | null} element - The element to convert\n */\nfunction asHTMLElement(element) {\n return /** @type {HTMLElement} */ (element)\n}\n\nfunction reloadPage() {\n window.history.replaceState(null, '', window.location.href)\n window.location.href = window.location.pathname\n}\n\n/**\n * Build the upload status URL given the current pathname and the upload ID.\n * @param {string} pathname – e.g. window.location.pathname\n * @param {string} uploadId\n * @returns {string} e.g. \"/form/upload-status/abc123\"\n */\nexport function buildUploadStatusUrl(pathname, uploadId) {\n const pathSegments = pathname.split('/').filter((segment) => segment)\n const prefix = pathSegments.length > 0 ? `/${pathSegments[0]}` : ''\n return `${prefix}/upload-status/${uploadId}`\n}\n\n/**\n * Polls the upload status endpoint until the file is ready or timeout occurs\n * @param {string} uploadId - The upload ID to check\n */\nfunction pollUploadStatus(uploadId) {\n let attempts = 0\n const interval = setInterval(() => {\n attempts++\n\n if (attempts >= MAX_POLLING_DURATION) {\n clearInterval(interval)\n reloadPage()\n return\n }\n\n const uploadStatusUrl = buildUploadStatusUrl(\n window.location.pathname,\n uploadId\n )\n\n fetch(uploadStatusUrl, {\n headers: {\n Accept: 'application/json'\n }\n })\n .then((response) => {\n if (!response.ok) {\n throw new Error('Network response was not ok')\n }\n return response.json()\n })\n .then((data) => {\n if (data.uploadStatus === 'ready') {\n clearInterval(interval)\n reloadPage()\n }\n })\n .catch(() => {\n clearInterval(interval)\n reloadPage()\n })\n }, 1000)\n}\n\n/**\n * Handle standard form submission for file upload\n * @param {HTMLFormElement} formElement - The form element\n * @param {HTMLInputElement} fileInput - The file input element\n * @param {HTMLButtonElement} uploadButton - The upload button\n * @param {HTMLButtonElement} continueButton - The continue button\n * @param {File | null} selectedFile - The selected file\n */\nfunction handleStandardFormSubmission(\n formElement,\n fileInput,\n uploadButton,\n continueButton,\n selectedFile\n) {\n renderSummary(selectedFile, 'Uploading…', formElement)\n\n fileInput.focus()\n\n setTimeout(() => {\n fileInput.disabled = true\n uploadButton.disabled = true\n continueButton.disabled = true\n }, 100)\n}\n\n/**\n * Handle AJAX form submission with upload ID\n * @param {Event} event - The click event\n * @param {HTMLFormElement} formElement - The form element\n * @param {HTMLInputElement} fileInput - The file input element\n * @param {HTMLButtonElement} uploadButton - The upload button\n * @param {HTMLElement | null} errorSummary - The error summary container\n * @param {string | undefined} uploadId - The upload ID\n * @returns {boolean} Whether the event was handled\n */\nfunction handleAjaxFormSubmission(\n event,\n formElement,\n fileInput,\n uploadButton,\n errorSummary,\n uploadId\n) {\n if (!formElement.action || !uploadId) {\n return false\n }\n\n event.preventDefault()\n\n const formData = new FormData(formElement)\n const isLocalDev = !!formElement.dataset.proxyUrl\n const uploadUrl = formElement.dataset.proxyUrl ?? formElement.action\n\n const fetchOptions = /** @type {RequestInit} */ ({\n method: 'POST',\n body: formData,\n redirect: isLocalDev ? 'follow' : 'manual' // follow mode if local development with the proxy\n })\n\n // no-cors mode if needed local development with the proxy\n if (isLocalDev) {\n fetchOptions.mode = 'no-cors'\n }\n\n fetch(uploadUrl, fetchOptions)\n .then(() => {\n pollUploadStatus(uploadId)\n })\n .catch(() => {\n fileInput.disabled = false\n uploadButton.disabled = false\n\n showError(\n 'There was a problem uploading the file',\n errorSummary,\n fileInput\n )\n\n return null\n })\n\n return true\n}\n\nfunction initUpload() {\n const form = document.querySelector('form:has(input[type=\"file\"])')\n /** @type {HTMLInputElement | null} */\n const fileInput = form ? form.querySelector('input[type=\"file\"]') : null\n /** @type {HTMLButtonElement | null} */\n const uploadButton = form ? form.querySelector('.upload-file-button') : null\n const continueButton =\n /** @type {HTMLButtonElement} */ (\n Array.from(document.querySelectorAll('button.govuk-button')).find(\n (button) => button.textContent?.trim() === 'Continue'\n )\n ) ?? null\n\n const errorSummary = document.querySelector('.govuk-error-summary-container')\n\n if (!form || !fileInput || !uploadButton) {\n return\n }\n\n const formElement = /** @type {HTMLFormElement} */ (form)\n /** @type {File | null} */\n let selectedFile = null\n let isSubmitting = false\n const uploadId = formElement.dataset.uploadId\n\n fileInput.addEventListener('change', () => {\n if (errorSummary) {\n errorSummary.innerHTML = ''\n }\n\n if (fileInput.files && fileInput.files.length > 0) {\n selectedFile = fileInput.files[0]\n }\n })\n\n uploadButton.addEventListener('click', (event) => {\n if (!selectedFile) {\n event.preventDefault()\n showError(\n 'Select a file',\n /** @type {HTMLElement | null} */ (errorSummary),\n fileInput\n )\n return\n }\n\n if (isSubmitting) {\n event.preventDefault()\n return\n }\n\n isSubmitting = true\n\n handleStandardFormSubmission(\n formElement,\n fileInput,\n uploadButton,\n continueButton,\n selectedFile\n )\n\n handleAjaxFormSubmission(\n event,\n formElement,\n fileInput,\n uploadButton,\n /** @type {HTMLElement | null} */ (errorSummary),\n uploadId\n )\n })\n}\n\nexport function initFileUpload() {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', initUpload)\n } else {\n initUpload()\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"file-upload.js","names":["MAX_POLLING_DURATION","ARIA_DESCRIBEDBY","ERROR_SUMMARY_TITLE_ID","createOrUpdateStatusAnnouncer","form","fileCountP","statusAnnouncer","querySelector","document","createElement","id","className","setAttribute","addStatusAnnouncerToDOM","asHTMLElement","appendChild","body","nextSibling","parentNode","insertBefore","parentElement","findOrCreateSummaryList","summaryList","buttonGroup","createFileRow","selectedFile","statusText","row","name","innerHTML","renderSummary","container","getElementById","uploadForm","closest","HTMLFormElement","fileInput","existingRow","remove","firstChild","textContent","showError","message","errorSummary","topErrorSummary","titleElement","removeAttribute","formGroup","classList","add","inputId","errorMessage","element","reloadPage","window","history","replaceState","location","href","pathname","buildUploadStatusUrl","uploadId","normalisedPath","replace","segments","split","filter","Boolean","prefix","length","slice","join","pollUploadStatus","attempts","interval","setInterval","clearInterval","uploadStatusUrl","fetch","headers","Accept","then","response","ok","Error","json","data","uploadStatus","catch","handleStandardFormSubmission","formElement","uploadButton","continueButton","focus","setTimeout","disabled","handleAjaxFormSubmission","event","action","preventDefault","formData","FormData","isLocalDev","dataset","proxyUrl","uploadUrl","fetchOptions","method","redirect","mode","initUpload","Array","from","querySelectorAll","find","button","trim","isSubmitting","addEventListener","files","initFileUpload","readyState"],"sources":["../../../src/client/javascripts/file-upload.js"],"sourcesContent":["export const MAX_POLLING_DURATION = 300 // 5 minutes\nconst ARIA_DESCRIBEDBY = 'aria-describedby'\nconst ERROR_SUMMARY_TITLE_ID = 'error-summary-title'\n\n/**\n * Creates or updates status announcer for screen readers\n * @param {HTMLElement | null} form - The form element\n * @param {HTMLElement | null} fileCountP - The file count paragraph element\n * @returns {HTMLElement} The status announcer element\n */\nfunction createOrUpdateStatusAnnouncer(form, fileCountP) {\n let statusAnnouncer = form?.querySelector('#statusInformation')\n\n if (!statusAnnouncer) {\n statusAnnouncer = document.createElement('div')\n statusAnnouncer.id = 'statusInformation'\n statusAnnouncer.className = 'govuk-visually-hidden'\n statusAnnouncer.setAttribute('aria-live', 'polite')\n\n // multiple fallbacks to ensure the status announcer is always added to the DOM\n // this helps with cross-browser compatibility and unexpected DOM structures encountered during QA\n try {\n addStatusAnnouncerToDOM(\n asHTMLElement(form),\n asHTMLElement(fileCountP),\n asHTMLElement(statusAnnouncer)\n )\n } catch {\n try {\n form?.appendChild(statusAnnouncer)\n } catch {\n document.body.appendChild(statusAnnouncer)\n }\n }\n }\n\n return /** @type {HTMLElement} */ (statusAnnouncer)\n}\n\n/**\n * Helper function to add the status announcer to the DOM\n * @param {HTMLElement} form - The form element\n * @param {HTMLElement | null} fileCountP - The file count paragraph element\n * @param {HTMLElement} statusAnnouncer - The status announcer element to add\n */\nfunction addStatusAnnouncerToDOM(form, fileCountP, statusAnnouncer) {\n if (fileCountP?.nextSibling && fileCountP.parentNode === form) {\n form.insertBefore(statusAnnouncer, fileCountP.nextSibling)\n return\n }\n\n const parentElement = fileCountP?.parentNode ?? form\n parentElement.appendChild(statusAnnouncer)\n}\n\n/**\n * Finds an existing summary list or creates a new one\n * @param {HTMLFormElement} form - The form element\n * @param {HTMLElement} fileCountP - The file count paragraph element\n * @returns {HTMLElement} The summary list element\n */\nfunction findOrCreateSummaryList(form, fileCountP) {\n let summaryList = form.querySelector('dl.govuk-summary-list')\n\n if (!summaryList) {\n summaryList = document.createElement('dl')\n summaryList.className = 'govuk-summary-list govuk-summary-list--long-key'\n\n const buttonGroup = form.querySelector('.govuk-button-group')\n\n if (buttonGroup) {\n form.insertBefore(summaryList, buttonGroup)\n } else {\n form.insertBefore(summaryList, fileCountP.nextSibling)\n }\n }\n\n return /** @type {HTMLElement} */ (summaryList)\n}\n\n/**\n * Creates a file row element for the summary list\n * @param {File | null} selectedFile - The selected file\n * @param {string} statusText - The status to display\n * @returns {HTMLElement} The created row element\n */\nfunction createFileRow(selectedFile, statusText) {\n const row = document.createElement('div')\n row.className = 'govuk-summary-list__row'\n row.setAttribute('data-filename', selectedFile?.name ?? '')\n row.innerHTML = `\n <dt class=\"govuk-summary-list__key\">\n ${selectedFile?.name ?? ''}\n </dt>\n <dd class=\"govuk-summary-list__value\">\n <strong class=\"govuk-tag govuk-tag--yellow\">${statusText}</strong>\n </dd>\n <dd class=\"govuk-summary-list__actions\">\n </dd>\n `\n return row\n}\n\n/**\n * Renders or updates the file summary box for the selected file\n * @param {File | null} selectedFile - The selected file\n * @param {string} statusText - The status to display\n * @param {HTMLElement} form - The form element\n */\nfunction renderSummary(selectedFile, statusText, form) {\n const container = document.getElementById('uploadedFilesContainer')\n const uploadForm = container ? container.closest('form') : null\n\n if (!uploadForm || !(uploadForm instanceof HTMLFormElement)) {\n return\n }\n\n const fileCountP = uploadForm.querySelector('p.govuk-body')\n\n if (!fileCountP) {\n return\n }\n\n const statusAnnouncer = createOrUpdateStatusAnnouncer(\n /** @type {HTMLElement} */ (uploadForm),\n /** @type {HTMLElement | null} */ (fileCountP)\n )\n\n const fileInput = form.querySelector('input[type=\"file\"]')\n\n if (fileInput) {\n fileInput.setAttribute(ARIA_DESCRIBEDBY, 'statusInformation')\n }\n\n const summaryList = findOrCreateSummaryList(\n /** @type {HTMLFormElement} */ (uploadForm),\n /** @type {HTMLElement} */ (fileCountP)\n )\n\n const existingRow = document.querySelector(\n `[data-filename=\"${selectedFile?.name}\"]`\n )\n\n if (existingRow) {\n existingRow.remove()\n }\n\n const row = createFileRow(selectedFile, statusText)\n summaryList.insertBefore(row, summaryList.firstChild)\n statusAnnouncer.textContent = `${selectedFile?.name ?? ''} ${statusText}`\n}\n\n/**\n * Shows an error message using the GOV.UK error summary component\n * and adds inline error styling to the file input\n * @param {string} message - The error message to display\n * @param {HTMLElement | null} errorSummary - The error summary container\n * @param {HTMLInputElement} fileInput - The file input element\n * @returns {void}\n */\nfunction showError(message, errorSummary, fileInput) {\n const topErrorSummary = document.querySelector('.govuk-error-summary')\n\n if (topErrorSummary) {\n const titleElement = document.getElementById(ERROR_SUMMARY_TITLE_ID)\n if (titleElement) {\n fileInput.setAttribute(ARIA_DESCRIBEDBY, ERROR_SUMMARY_TITLE_ID)\n } else {\n fileInput.removeAttribute(ARIA_DESCRIBEDBY)\n }\n return\n }\n\n if (errorSummary) {\n errorSummary.innerHTML = `\n <div class=\"govuk-error-summary\" data-module=\"govuk-error-summary\">\n <div role=\"alert\">\n <h2 class=\"govuk-error-summary__title\" id=\"${ERROR_SUMMARY_TITLE_ID}\">\n There is a problem\n </h2>\n <div class=\"govuk-error-summary__body\">\n <ul class=\"govuk-list govuk-error-summary__list\">\n <li>\n <a href=\"#file-upload\">${message}</a>\n </li>\n </ul>\n </div>\n </div>\n </div>\n `\n\n fileInput.setAttribute(ARIA_DESCRIBEDBY, ERROR_SUMMARY_TITLE_ID)\n }\n\n const formGroup = fileInput.closest('.govuk-form-group')\n if (formGroup) {\n formGroup.classList.add('govuk-form-group--error')\n fileInput.classList.add('govuk-file-upload--error')\n\n const inputId = fileInput.id\n let errorMessage = document.getElementById(`${inputId}-error`)\n\n if (!errorMessage) {\n errorMessage = document.createElement('p')\n errorMessage.id = `${inputId}-error`\n errorMessage.className = 'govuk-error-message'\n errorMessage.innerHTML = `<span class=\"govuk-visually-hidden\">Error:</span> ${message}`\n formGroup.insertBefore(errorMessage, fileInput)\n }\n\n fileInput.setAttribute(\n ARIA_DESCRIBEDBY,\n `error-summary-title ${inputId}-error`\n )\n }\n}\n\n/**\n * Helper to safely convert an Element to HTMLElement\n * @param {Element | null} element - The element to convert\n */\nfunction asHTMLElement(element) {\n return /** @type {HTMLElement} */ (element)\n}\n\nfunction reloadPage() {\n window.history.replaceState(null, '', window.location.href)\n window.location.href = window.location.pathname\n}\n\n/**\n * Build the upload status URL given the current pathname and the upload ID.\n * This only works when called on a file upload page that has a maximum depth of 1 URL segments following the slug.\n * @param {string} pathname – e.g. window.location.pathname\n * @param {string} uploadId\n * @returns {string} e.g. \"/form/upload-status/abc123\"\n */\nexport function buildUploadStatusUrl(pathname, uploadId) {\n // Remove preview markers and duplicate slashes\n const normalisedPath = pathname\n .replace(/\\/preview\\/(draft|live)/g, '')\n .replace(/\\/{2,}/g, '/')\n .replace(/\\/$/, '')\n\n const segments = normalisedPath.split('/').filter(Boolean)\n\n // The slug is always the second to last segment\n // The prefix is everything before the slug\n const prefix =\n segments.length > 2\n ? `/${segments.slice(0, segments.length - 2).join('/')}`\n : ''\n\n return `${prefix}/upload-status/${uploadId}`\n}\n\n/**\n * Polls the upload status endpoint until the file is ready or timeout occurs\n * @param {string} uploadId - The upload ID to check\n */\nfunction pollUploadStatus(uploadId) {\n let attempts = 0\n const interval = setInterval(() => {\n attempts++\n\n if (attempts >= MAX_POLLING_DURATION) {\n clearInterval(interval)\n reloadPage()\n return\n }\n\n const uploadStatusUrl = buildUploadStatusUrl(\n window.location.pathname,\n uploadId\n )\n\n fetch(uploadStatusUrl, {\n headers: {\n Accept: 'application/json'\n }\n })\n .then((response) => {\n if (!response.ok) {\n throw new Error('Network response was not ok')\n }\n return response.json()\n })\n .then((data) => {\n if (data.uploadStatus === 'ready') {\n clearInterval(interval)\n reloadPage()\n }\n })\n .catch(() => {\n clearInterval(interval)\n reloadPage()\n })\n }, 1000)\n}\n\n/**\n * Handle standard form submission for file upload\n * @param {HTMLFormElement} formElement - The form element\n * @param {HTMLInputElement} fileInput - The file input element\n * @param {HTMLButtonElement} uploadButton - The upload button\n * @param {HTMLButtonElement} continueButton - The continue button\n * @param {File | null} selectedFile - The selected file\n */\nfunction handleStandardFormSubmission(\n formElement,\n fileInput,\n uploadButton,\n continueButton,\n selectedFile\n) {\n renderSummary(selectedFile, 'Uploading…', formElement)\n\n fileInput.focus()\n\n setTimeout(() => {\n fileInput.disabled = true\n uploadButton.disabled = true\n continueButton.disabled = true\n }, 100)\n}\n\n/**\n * Handle AJAX form submission with upload ID\n * @param {Event} event - The click event\n * @param {HTMLFormElement} formElement - The form element\n * @param {HTMLInputElement} fileInput - The file input element\n * @param {HTMLButtonElement} uploadButton - The upload button\n * @param {HTMLElement | null} errorSummary - The error summary container\n * @param {string | undefined} uploadId - The upload ID\n * @returns {boolean} Whether the event was handled\n */\nfunction handleAjaxFormSubmission(\n event,\n formElement,\n fileInput,\n uploadButton,\n errorSummary,\n uploadId\n) {\n if (!formElement.action || !uploadId) {\n return false\n }\n\n event.preventDefault()\n\n const formData = new FormData(formElement)\n const isLocalDev = !!formElement.dataset.proxyUrl\n const uploadUrl = formElement.dataset.proxyUrl ?? formElement.action\n\n const fetchOptions = /** @type {RequestInit} */ ({\n method: 'POST',\n body: formData,\n redirect: isLocalDev ? 'follow' : 'manual' // follow mode if local development with the proxy\n })\n\n // no-cors mode if needed local development with the proxy\n if (isLocalDev) {\n fetchOptions.mode = 'no-cors'\n }\n\n fetch(uploadUrl, fetchOptions)\n .then(() => {\n pollUploadStatus(uploadId)\n })\n .catch(() => {\n fileInput.disabled = false\n uploadButton.disabled = false\n\n showError(\n 'There was a problem uploading the file',\n errorSummary,\n fileInput\n )\n\n return null\n })\n\n return true\n}\n\nfunction initUpload() {\n const form = document.querySelector('form:has(input[type=\"file\"])')\n /** @type {HTMLInputElement | null} */\n const fileInput = form ? form.querySelector('input[type=\"file\"]') : null\n /** @type {HTMLButtonElement | null} */\n const uploadButton = form ? form.querySelector('.upload-file-button') : null\n const continueButton =\n /** @type {HTMLButtonElement} */ (\n Array.from(document.querySelectorAll('button.govuk-button')).find(\n (button) => button.textContent?.trim() === 'Continue'\n )\n ) ?? null\n\n const errorSummary = document.querySelector('.govuk-error-summary-container')\n\n if (!form || !fileInput || !uploadButton) {\n return\n }\n\n const formElement = /** @type {HTMLFormElement} */ (form)\n /** @type {File | null} */\n let selectedFile = null\n let isSubmitting = false\n const uploadId = formElement.dataset.uploadId\n\n fileInput.addEventListener('change', () => {\n if (errorSummary) {\n errorSummary.innerHTML = ''\n }\n\n if (fileInput.files && fileInput.files.length > 0) {\n selectedFile = fileInput.files[0]\n }\n })\n\n uploadButton.addEventListener('click', (event) => {\n if (!selectedFile) {\n event.preventDefault()\n showError(\n 'Select a file',\n /** @type {HTMLElement | null} */ (errorSummary),\n fileInput\n )\n return\n }\n\n if (isSubmitting) {\n event.preventDefault()\n return\n }\n\n isSubmitting = true\n\n handleStandardFormSubmission(\n formElement,\n fileInput,\n uploadButton,\n continueButton,\n selectedFile\n )\n\n handleAjaxFormSubmission(\n event,\n formElement,\n fileInput,\n uploadButton,\n /** @type {HTMLElement | null} */ (errorSummary),\n uploadId\n )\n })\n}\n\nexport function initFileUpload() {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', initUpload)\n } else {\n initUpload()\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Page } from '@defra/forms-model';
|
|
1
|
+
import { type FormMetadata, type Page } from '@defra/forms-model';
|
|
2
2
|
import { type RouteOptions } from '@hapi/hapi';
|
|
3
3
|
import { SummaryViewModel, type FormModel } from '~/src/server/plugins/engine/models/index.js';
|
|
4
4
|
import { type Detail, type DetailItem } from '~/src/server/plugins/engine/models/types.js';
|
|
@@ -22,6 +22,8 @@ export declare class SummaryPageController extends QuestionPageController {
|
|
|
22
22
|
* If a form is incomplete, a user will be redirected to the start page.
|
|
23
23
|
*/
|
|
24
24
|
makePostRouteHandler(): (request: FormRequestPayload, context: FormContext, h: FormResponseToolkit) => Promise<import("@hapi/hapi").ResponseObject>;
|
|
25
|
+
handleFormSubmit(request: FormRequestPayload, context: FormContext, h: FormResponseToolkit): Promise<import("@hapi/hapi").ResponseObject>;
|
|
25
26
|
get postRouteOptions(): RouteOptions<FormRequestPayloadRefs>;
|
|
26
27
|
}
|
|
28
|
+
export declare function submitForm(context: FormContext, request: FormRequestPayload, summaryViewModel: SummaryViewModel, model: FormModel, emailAddress: string, formMetadata: FormMetadata): Promise<void>;
|
|
27
29
|
export declare function getFormSubmissionData(context: FormContext, details: Detail[]): DetailItem[];
|
|
@@ -42,6 +42,7 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
42
42
|
viewModel.phaseTag = this.phaseTag;
|
|
43
43
|
viewModel.components = components;
|
|
44
44
|
viewModel.allowSaveAndExit = this.shouldShowSaveAndExit(request.server);
|
|
45
|
+
viewModel.errors = errors;
|
|
45
46
|
return viewModel;
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -65,13 +66,6 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
65
66
|
*/
|
|
66
67
|
makePostRouteHandler() {
|
|
67
68
|
return async (request, context, h) => {
|
|
68
|
-
const {
|
|
69
|
-
model
|
|
70
|
-
} = this;
|
|
71
|
-
const {
|
|
72
|
-
params
|
|
73
|
-
} = request;
|
|
74
|
-
|
|
75
69
|
// Check if this is a save-and-exit action
|
|
76
70
|
const {
|
|
77
71
|
action
|
|
@@ -79,38 +73,47 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
79
73
|
if (action === FormAction.SaveAndExit) {
|
|
80
74
|
return this.handleSaveAndExit(request, context, h);
|
|
81
75
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
76
|
+
return this.handleFormSubmit(request, context, h);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async handleFormSubmit(request, context, h) {
|
|
80
|
+
const {
|
|
81
|
+
model
|
|
82
|
+
} = this;
|
|
83
|
+
const {
|
|
84
|
+
params
|
|
85
|
+
} = request;
|
|
86
|
+
const cacheService = getCacheService(request.server);
|
|
87
|
+
const {
|
|
88
|
+
formsService
|
|
89
|
+
} = this.model.services;
|
|
90
|
+
const {
|
|
91
|
+
getFormMetadata
|
|
92
|
+
} = formsService;
|
|
89
93
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
// Get the form metadata using the `slug` param
|
|
95
|
+
const formMetadata = await getFormMetadata(params.slug);
|
|
96
|
+
const {
|
|
97
|
+
notificationEmail
|
|
98
|
+
} = formMetadata;
|
|
99
|
+
const {
|
|
100
|
+
isPreview
|
|
101
|
+
} = checkFormStatus(request.params);
|
|
102
|
+
const emailAddress = notificationEmail ?? this.model.def.outputEmail;
|
|
103
|
+
checkEmailAddressForLiveFormSubmission(emailAddress, isPreview);
|
|
100
104
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
// Send submission email
|
|
106
|
+
if (emailAddress) {
|
|
107
|
+
const viewModel = this.getSummaryViewModel(request, context);
|
|
108
|
+
await submitForm(context, request, viewModel, model, emailAddress, formMetadata);
|
|
109
|
+
}
|
|
110
|
+
await cacheService.setConfirmationState(request, {
|
|
111
|
+
confirmed: true
|
|
112
|
+
});
|
|
109
113
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
};
|
|
114
|
+
// Clear all form data
|
|
115
|
+
await cacheService.clearState(request);
|
|
116
|
+
return this.proceed(request, h, this.getStatusPath());
|
|
114
117
|
}
|
|
115
118
|
get postRouteOptions() {
|
|
116
119
|
return {
|
|
@@ -124,7 +127,7 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
124
127
|
};
|
|
125
128
|
}
|
|
126
129
|
}
|
|
127
|
-
async function submitForm(context, request, summaryViewModel, model, emailAddress, formMetadata) {
|
|
130
|
+
export async function submitForm(context, request, summaryViewModel, model, emailAddress, formMetadata) {
|
|
128
131
|
await extendFileRetention(model, context.state, emailAddress);
|
|
129
132
|
const formStatus = checkFormStatus(request.params);
|
|
130
133
|
const logTags = ['submit', 'submissionApi'];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SummaryPageController.js","names":["hasComponentsEvenIfNoNext","Boom","ComponentCollection","FileUploadField","getAnswer","checkEmailAddressForLiveFormSubmission","checkFormStatus","getCacheService","SummaryViewModel","QuestionPageController","FormAction","SummaryPageController","allowSaveAndExit","constructor","model","pageDef","viewName","collection","components","page","getSummaryViewModel","request","context","viewModel","query","payload","errors","getViewModel","backLink","getBackLink","feedbackLink","phaseTag","shouldShowSaveAndExit","server","makeGetRouteHandler","h","hasMissingNotificationEmail","view","makePostRouteHandler","params","action","SaveAndExit","handleSaveAndExit","cacheService","formsService","services","getFormMetadata","formMetadata","slug","notificationEmail","isPreview","emailAddress","def","outputEmail","submitForm","setConfirmationState","confirmed","clearState","proceed","getStatusPath","postRouteOptions","ext","onPreHandler","method","continue","summaryViewModel","extendFileRetention","state","formStatus","logTags","logger","info","items","getFormSubmissionData","details","submitResponse","submitData","yar","id","undefined","badRequest","outputService","submit","updatedRetrievalKey","formSubmissionService","persistFiles","files","pages","forEach","fileUploadComponents","fields","filter","component","values","getFormValueFromState","length","push","map","status","fileId","form","file","initiatedRetrievalKey","metadata","retrievalKey","sessionId","main","item","name","title","label","value","field","format","repeaters","subItems","detailItems","subItem","relevantPages","href","flatMap","flat"],"sources":["../../../../../src/server/plugins/engine/pageControllers/SummaryPageController.ts"],"sourcesContent":["import {\n hasComponentsEvenIfNoNext,\n type FormMetadata,\n type Page,\n type SubmitPayload\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type RouteOptions } from '@hapi/hapi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport { getAnswer } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n getCacheService\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport {\n type Detail,\n type DetailItem\n} from '~/src/server/plugins/engine/models/types.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport {\n FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport class SummaryPageController extends QuestionPageController {\n declare pageDef: Page\n allowSaveAndExit = true\n\n /**\n * The controller which is used when Page[\"controller\"] is defined as \"./pages/summary.js\"\n */\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n this.viewName = 'summary'\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponentsEvenIfNoNext(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n }\n\n getSummaryViewModel(\n request: FormContextRequest,\n context: FormContext\n ): SummaryViewModel {\n const viewModel = new SummaryViewModel(request, this, context)\n\n const { query } = request\n const { payload, errors } = context\n const components = this.collection.getViewModel(payload, errors, query)\n\n // We already figure these out in the base page controller. Take them and apply them to our page-specific model.\n // This is a stop-gap until we can add proper inheritance in place.\n viewModel.backLink = this.getBackLink(request, context)\n viewModel.feedbackLink = this.feedbackLink\n viewModel.phaseTag = this.phaseTag\n viewModel.components = components\n viewModel.allowSaveAndExit = this.shouldShowSaveAndExit(request.server)\n\n return viewModel\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a GET request at `/{id}/{path*}`,\n */\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewName } = this\n\n const viewModel = this.getSummaryViewModel(request, context)\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a POST request at `/{id}/{path*}`.\n * If a form is incomplete, a user will be redirected to the start page.\n */\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { model } = this\n const { params } = request\n\n // Check if this is a save-and-exit action\n const { action } = request.payload\n if (action === FormAction.SaveAndExit) {\n return this.handleSaveAndExit(request, context, h)\n }\n\n const cacheService = getCacheService(request.server)\n\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Get the form metadata using the `slug` param\n const formMetadata = await getFormMetadata(params.slug)\n const { notificationEmail } = formMetadata\n const { isPreview } = checkFormStatus(request.params)\n const emailAddress = notificationEmail ?? this.model.def.outputEmail\n\n checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)\n\n // Send submission email\n if (emailAddress) {\n const viewModel = this.getSummaryViewModel(request, context)\n await submitForm(\n context,\n request,\n viewModel,\n model,\n emailAddress,\n formMetadata\n )\n }\n\n await cacheService.setConfirmationState(request, { confirmed: true })\n\n // Clear all form data\n await cacheService.clearState(request)\n\n return this.proceed(request, h, this.getStatusPath())\n }\n }\n\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n ext: {\n onPreHandler: {\n method(request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n\nasync function submitForm(\n context: FormContext,\n request: FormRequestPayload,\n summaryViewModel: SummaryViewModel,\n model: FormModel,\n emailAddress: string,\n formMetadata: FormMetadata\n) {\n await extendFileRetention(model, context.state, emailAddress)\n\n const formStatus = checkFormStatus(request.params)\n const logTags = ['submit', 'submissionApi']\n\n request.logger.info(logTags, 'Preparing email', formStatus)\n\n // Get detail items\n const items = getFormSubmissionData(\n summaryViewModel.context,\n summaryViewModel.details\n )\n\n // Submit data\n request.logger.info(logTags, 'Submitting data')\n const submitResponse = await submitData(\n model,\n items,\n emailAddress,\n request.yar.id\n )\n\n if (submitResponse === undefined) {\n throw Boom.badRequest('Unexpected empty response from submit api')\n }\n\n return model.services.outputService.submit(\n context,\n request,\n model,\n emailAddress,\n items,\n submitResponse,\n formMetadata\n )\n}\n\nasync function extendFileRetention(\n model: FormModel,\n state: FormSubmissionState,\n updatedRetrievalKey: string\n) {\n const { formSubmissionService } = model.services\n const { persistFiles } = formSubmissionService\n const files: { fileId: string; initiatedRetrievalKey: string }[] = []\n\n // For each file upload component with files in\n // state, add the files to the batch getting persisted\n model.pages.forEach((page) => {\n const fileUploadComponents = page.collection.fields.filter(\n (component) => component instanceof FileUploadField\n )\n\n fileUploadComponents.forEach((component) => {\n const values = component.getFormValueFromState(state)\n if (!values?.length) {\n return\n }\n\n files.push(\n ...values.map(({ status }) => ({\n fileId: status.form.file.fileId,\n initiatedRetrievalKey: status.metadata.retrievalKey\n }))\n )\n })\n })\n\n if (files.length) {\n return persistFiles(files, updatedRetrievalKey)\n }\n}\n\nfunction submitData(\n model: FormModel,\n items: DetailItem[],\n retrievalKey: string,\n sessionId: string\n) {\n const { formSubmissionService } = model.services\n const { submit } = formSubmissionService\n\n const payload: SubmitPayload = {\n sessionId,\n retrievalKey,\n\n // Main form answers\n main: items\n .filter((item) => 'field' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n value: getAnswer(item.field, item.state, { format: 'data' })\n })),\n\n // Repeater form answers\n repeaters: items\n .filter((item) => 'subItems' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n\n // Repeater item values\n value: item.subItems.map((detailItems) =>\n detailItems.map((subItem) => ({\n name: subItem.name,\n title: subItem.label,\n value: getAnswer(subItem.field, subItem.state, { format: 'data' })\n }))\n )\n }))\n }\n\n return submit(payload)\n}\n\nexport function getFormSubmissionData(context: FormContext, details: Detail[]) {\n return context.relevantPages\n .map(({ href }) =>\n details.flatMap(({ items }) =>\n items.filter(({ page }) => page.href === href)\n )\n )\n .flat()\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"SummaryPageController.js","names":["hasComponentsEvenIfNoNext","Boom","ComponentCollection","FileUploadField","getAnswer","checkEmailAddressForLiveFormSubmission","checkFormStatus","getCacheService","SummaryViewModel","QuestionPageController","FormAction","SummaryPageController","allowSaveAndExit","constructor","model","pageDef","viewName","collection","components","page","getSummaryViewModel","request","context","viewModel","query","payload","errors","getViewModel","backLink","getBackLink","feedbackLink","phaseTag","shouldShowSaveAndExit","server","makeGetRouteHandler","h","hasMissingNotificationEmail","view","makePostRouteHandler","action","SaveAndExit","handleSaveAndExit","handleFormSubmit","params","cacheService","formsService","services","getFormMetadata","formMetadata","slug","notificationEmail","isPreview","emailAddress","def","outputEmail","submitForm","setConfirmationState","confirmed","clearState","proceed","getStatusPath","postRouteOptions","ext","onPreHandler","method","continue","summaryViewModel","extendFileRetention","state","formStatus","logTags","logger","info","items","getFormSubmissionData","details","submitResponse","submitData","yar","id","undefined","badRequest","outputService","submit","updatedRetrievalKey","formSubmissionService","persistFiles","files","pages","forEach","fileUploadComponents","fields","filter","component","values","getFormValueFromState","length","push","map","status","fileId","form","file","initiatedRetrievalKey","metadata","retrievalKey","sessionId","main","item","name","title","label","value","field","format","repeaters","subItems","detailItems","subItem","relevantPages","href","flatMap","flat"],"sources":["../../../../../src/server/plugins/engine/pageControllers/SummaryPageController.ts"],"sourcesContent":["import {\n hasComponentsEvenIfNoNext,\n type FormMetadata,\n type Page,\n type SubmitPayload\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type RouteOptions } from '@hapi/hapi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport { getAnswer } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n getCacheService\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport {\n type Detail,\n type DetailItem\n} from '~/src/server/plugins/engine/models/types.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport {\n FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport class SummaryPageController extends QuestionPageController {\n declare pageDef: Page\n allowSaveAndExit = true\n\n /**\n * The controller which is used when Page[\"controller\"] is defined as \"./pages/summary.js\"\n */\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n this.viewName = 'summary'\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponentsEvenIfNoNext(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n }\n\n getSummaryViewModel(\n request: FormContextRequest,\n context: FormContext\n ): SummaryViewModel {\n const viewModel = new SummaryViewModel(request, this, context)\n\n const { query } = request\n const { payload, errors } = context\n const components = this.collection.getViewModel(payload, errors, query)\n\n // We already figure these out in the base page controller. Take them and apply them to our page-specific model.\n // This is a stop-gap until we can add proper inheritance in place.\n viewModel.backLink = this.getBackLink(request, context)\n viewModel.feedbackLink = this.feedbackLink\n viewModel.phaseTag = this.phaseTag\n viewModel.components = components\n viewModel.allowSaveAndExit = this.shouldShowSaveAndExit(request.server)\n viewModel.errors = errors\n\n return viewModel\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a GET request at `/{id}/{path*}`,\n */\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewName } = this\n\n const viewModel = this.getSummaryViewModel(request, context)\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a POST request at `/{id}/{path*}`.\n * If a form is incomplete, a user will be redirected to the start page.\n */\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n // Check if this is a save-and-exit action\n const { action } = request.payload\n if (action === FormAction.SaveAndExit) {\n return this.handleSaveAndExit(request, context, h)\n }\n\n return this.handleFormSubmit(request, context, h)\n }\n }\n\n async handleFormSubmit(\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) {\n const { model } = this\n const { params } = request\n\n const cacheService = getCacheService(request.server)\n\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Get the form metadata using the `slug` param\n const formMetadata = await getFormMetadata(params.slug)\n const { notificationEmail } = formMetadata\n const { isPreview } = checkFormStatus(request.params)\n const emailAddress = notificationEmail ?? this.model.def.outputEmail\n\n checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)\n\n // Send submission email\n if (emailAddress) {\n const viewModel = this.getSummaryViewModel(request, context)\n await submitForm(\n context,\n request,\n viewModel,\n model,\n emailAddress,\n formMetadata\n )\n }\n\n await cacheService.setConfirmationState(request, { confirmed: true })\n\n // Clear all form data\n await cacheService.clearState(request)\n\n return this.proceed(request, h, this.getStatusPath())\n }\n\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n ext: {\n onPreHandler: {\n method(request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n\nexport async function submitForm(\n context: FormContext,\n request: FormRequestPayload,\n summaryViewModel: SummaryViewModel,\n model: FormModel,\n emailAddress: string,\n formMetadata: FormMetadata\n) {\n await extendFileRetention(model, context.state, emailAddress)\n\n const formStatus = checkFormStatus(request.params)\n const logTags = ['submit', 'submissionApi']\n\n request.logger.info(logTags, 'Preparing email', formStatus)\n\n // Get detail items\n const items = getFormSubmissionData(\n summaryViewModel.context,\n summaryViewModel.details\n )\n\n // Submit data\n request.logger.info(logTags, 'Submitting data')\n const submitResponse = await submitData(\n model,\n items,\n emailAddress,\n request.yar.id\n )\n\n if (submitResponse === undefined) {\n throw Boom.badRequest('Unexpected empty response from submit api')\n }\n\n return model.services.outputService.submit(\n context,\n request,\n model,\n emailAddress,\n items,\n submitResponse,\n formMetadata\n )\n}\n\nasync function extendFileRetention(\n model: FormModel,\n state: FormSubmissionState,\n updatedRetrievalKey: string\n) {\n const { formSubmissionService } = model.services\n const { persistFiles } = formSubmissionService\n const files: { fileId: string; initiatedRetrievalKey: string }[] = []\n\n // For each file upload component with files in\n // state, add the files to the batch getting persisted\n model.pages.forEach((page) => {\n const fileUploadComponents = page.collection.fields.filter(\n (component) => component instanceof FileUploadField\n )\n\n fileUploadComponents.forEach((component) => {\n const values = component.getFormValueFromState(state)\n if (!values?.length) {\n return\n }\n\n files.push(\n ...values.map(({ status }) => ({\n fileId: status.form.file.fileId,\n initiatedRetrievalKey: status.metadata.retrievalKey\n }))\n )\n })\n })\n\n if (files.length) {\n return persistFiles(files, updatedRetrievalKey)\n }\n}\n\nfunction submitData(\n model: FormModel,\n items: DetailItem[],\n retrievalKey: string,\n sessionId: string\n) {\n const { formSubmissionService } = model.services\n const { submit } = formSubmissionService\n\n const payload: SubmitPayload = {\n sessionId,\n retrievalKey,\n\n // Main form answers\n main: items\n .filter((item) => 'field' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n value: getAnswer(item.field, item.state, { format: 'data' })\n })),\n\n // Repeater form answers\n repeaters: items\n .filter((item) => 'subItems' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n\n // Repeater item values\n value: item.subItems.map((detailItems) =>\n detailItems.map((subItem) => ({\n name: subItem.name,\n title: subItem.label,\n value: getAnswer(subItem.field, subItem.state, { format: 'data' })\n }))\n )\n }))\n }\n\n return submit(payload)\n}\n\nexport function getFormSubmissionData(context: FormContext, details: Detail[]) {\n return context.relevantPages\n .map(({ href }) =>\n details.flatMap(({ items }) =>\n items.filter(({ page }) => page.href === href)\n )\n )\n .flat()\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -12,7 +12,9 @@ export async function getHandler(request, h) {
|
|
|
12
12
|
error: 'Status check failed'
|
|
13
13
|
}).code(400);
|
|
14
14
|
}
|
|
15
|
-
return h.response(
|
|
15
|
+
return h.response({
|
|
16
|
+
uploadStatus: status.uploadStatus
|
|
17
|
+
});
|
|
16
18
|
} catch (err) {
|
|
17
19
|
request.logger.error(err, `[uploadStatusFailed] Upload status check failed for uploadId: ${uploadId} - ${getErrorMessage(err)}`);
|
|
18
20
|
return h.response({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-upload.js","names":["getErrorMessage","Joi","getUploadStatus","getHandler","request","h","uploadId","params","status","response","error","code","err","logger","getRoutes","method","path","handler","options","plugins","crumb","validate","object","keys","string","guid","required"],"sources":["../../../../../src/server/plugins/engine/routes/file-upload.ts"],"sourcesContent":["import { getErrorMessage } from '@defra/forms-model'\nimport { type ResponseToolkit, type ServerRoute } from '@hapi/hapi'\nimport Joi from 'joi'\n\nimport { getUploadStatus } from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n type FormRequest,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\n\nexport async function getHandler(\n request: FormRequest,\n h: Pick<ResponseToolkit, 'response'>\n) {\n const { uploadId } = request.params as unknown as {\n uploadId: string\n }\n try {\n const status = await getUploadStatus(uploadId)\n\n if (!status) {\n return h.response({ error: 'Status check failed' }).code(400)\n }\n\n return h.response(status)\n } catch (err) {\n request.logger.error(\n err,\n `[uploadStatusFailed] Upload status check failed for uploadId: ${uploadId} - ${getErrorMessage(err)}`\n )\n return h.response({ error: 'Status check error' }).code(500)\n }\n}\n\nexport function getRoutes(): ServerRoute<FormRequestRefs>[] {\n return [\n {\n method: 'get',\n path: '/upload-status/{uploadId}',\n handler: getHandler,\n options: {\n plugins: {\n crumb: false\n },\n validate: {\n params: Joi.object().keys({\n uploadId: Joi.string().guid().required()\n })\n }\n }\n }\n ]\n}\n"],"mappings":"AAAA,SAASA,eAAe,QAAQ,oBAAoB;AAEpD,OAAOC,GAAG,MAAM,KAAK;AAErB,SAASC,eAAe;AAMxB,OAAO,eAAeC,UAAUA,CAC9BC,OAAoB,EACpBC,CAAoC,EACpC;EACA,MAAM;IAAEC;EAAS,CAAC,GAAGF,OAAO,CAACG,MAE5B;EACD,IAAI;IACF,MAAMC,MAAM,GAAG,MAAMN,eAAe,CAACI,QAAQ,CAAC;IAE9C,IAAI,CAACE,MAAM,EAAE;MACX,OAAOH,CAAC,CAACI,QAAQ,CAAC;QAAEC,KAAK,EAAE;MAAsB,CAAC,CAAC,CAACC,IAAI,CAAC,GAAG,CAAC;IAC/D;IAEA,OAAON,CAAC,CAACI,QAAQ,
|
|
1
|
+
{"version":3,"file":"file-upload.js","names":["getErrorMessage","Joi","getUploadStatus","getHandler","request","h","uploadId","params","status","response","error","code","uploadStatus","err","logger","getRoutes","method","path","handler","options","plugins","crumb","validate","object","keys","string","guid","required"],"sources":["../../../../../src/server/plugins/engine/routes/file-upload.ts"],"sourcesContent":["import { getErrorMessage } from '@defra/forms-model'\nimport { type ResponseToolkit, type ServerRoute } from '@hapi/hapi'\nimport Joi from 'joi'\n\nimport { getUploadStatus } from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n type FormRequest,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\n\nexport async function getHandler(\n request: FormRequest,\n h: Pick<ResponseToolkit, 'response'>\n) {\n const { uploadId } = request.params as unknown as {\n uploadId: string\n }\n try {\n const status = await getUploadStatus(uploadId)\n\n if (!status) {\n return h.response({ error: 'Status check failed' }).code(400)\n }\n\n return h.response({\n uploadStatus: status.uploadStatus\n })\n } catch (err) {\n request.logger.error(\n err,\n `[uploadStatusFailed] Upload status check failed for uploadId: ${uploadId} - ${getErrorMessage(err)}`\n )\n return h.response({ error: 'Status check error' }).code(500)\n }\n}\n\nexport function getRoutes(): ServerRoute<FormRequestRefs>[] {\n return [\n {\n method: 'get',\n path: '/upload-status/{uploadId}',\n handler: getHandler,\n options: {\n plugins: {\n crumb: false\n },\n validate: {\n params: Joi.object().keys({\n uploadId: Joi.string().guid().required()\n })\n }\n }\n }\n ]\n}\n"],"mappings":"AAAA,SAASA,eAAe,QAAQ,oBAAoB;AAEpD,OAAOC,GAAG,MAAM,KAAK;AAErB,SAASC,eAAe;AAMxB,OAAO,eAAeC,UAAUA,CAC9BC,OAAoB,EACpBC,CAAoC,EACpC;EACA,MAAM;IAAEC;EAAS,CAAC,GAAGF,OAAO,CAACG,MAE5B;EACD,IAAI;IACF,MAAMC,MAAM,GAAG,MAAMN,eAAe,CAACI,QAAQ,CAAC;IAE9C,IAAI,CAACE,MAAM,EAAE;MACX,OAAOH,CAAC,CAACI,QAAQ,CAAC;QAAEC,KAAK,EAAE;MAAsB,CAAC,CAAC,CAACC,IAAI,CAAC,GAAG,CAAC;IAC/D;IAEA,OAAON,CAAC,CAACI,QAAQ,CAAC;MAChBG,YAAY,EAAEJ,MAAM,CAACI;IACvB,CAAC,CAAC;EACJ,CAAC,CAAC,OAAOC,GAAG,EAAE;IACZT,OAAO,CAACU,MAAM,CAACJ,KAAK,CAClBG,GAAG,EACH,iEAAiEP,QAAQ,MAAMN,eAAe,CAACa,GAAG,CAAC,EACrG,CAAC;IACD,OAAOR,CAAC,CAACI,QAAQ,CAAC;MAAEC,KAAK,EAAE;IAAqB,CAAC,CAAC,CAACC,IAAI,CAAC,GAAG,CAAC;EAC9D;AACF;AAEA,OAAO,SAASI,SAASA,CAAA,EAAmC;EAC1D,OAAO,CACL;IACEC,MAAM,EAAE,KAAK;IACbC,IAAI,EAAE,2BAA2B;IACjCC,OAAO,EAAEf,UAAU;IACnBgB,OAAO,EAAE;MACPC,OAAO,EAAE;QACPC,KAAK,EAAE;MACT,CAAC;MACDC,QAAQ,EAAE;QACRf,MAAM,EAAEN,GAAG,CAACsB,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC;UACxBlB,QAAQ,EAAEL,GAAG,CAACwB,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC,CAAC,CAACC,QAAQ,CAAC;QACzC,CAAC;MACH;IACF;EACF,CAAC,CACF;AACH","ignoreList":[]}
|
|
@@ -11,7 +11,8 @@ export const formAdapterSubmissionMessageMetaSchema = Joi.object().keys({
|
|
|
11
11
|
status: Joi.string().valid(...Object.values(FormStatus)).required(),
|
|
12
12
|
isPreview: Joi.boolean().required(),
|
|
13
13
|
notificationEmail: notificationEmailAddressSchema.required(),
|
|
14
|
-
versionMetadata: formVersionMetadataSchema.optional()
|
|
14
|
+
versionMetadata: formVersionMetadataSchema.optional(),
|
|
15
|
+
custom: Joi.object().pattern(/^/, Joi.any()).unknown().optional().description('Custom properties for the message')
|
|
15
16
|
});
|
|
16
17
|
export const formAdapterSubmissionMessageDataSchema = Joi.object().keys({
|
|
17
18
|
main: Joi.object(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","names":["FormStatus","formVersionMetadataSchema","idSchema","notificationEmailAddressSchema","slugSchema","titleSchema","Joi","FormAdapterSubmissionSchemaVersion","formAdapterSubmissionMessageMetaSchema","object","keys","schemaVersion","string","valid","Object","values","timestamp","date","required","referenceNumber","formName","formId","formSlug","status","isPreview","boolean","notificationEmail","versionMetadata","optional","formAdapterSubmissionMessageDataSchema","main","repeaters","files","
|
|
1
|
+
{"version":3,"file":"schema.js","names":["FormStatus","formVersionMetadataSchema","idSchema","notificationEmailAddressSchema","slugSchema","titleSchema","Joi","FormAdapterSubmissionSchemaVersion","formAdapterSubmissionMessageMetaSchema","object","keys","schemaVersion","string","valid","Object","values","timestamp","date","required","referenceNumber","formName","formId","formSlug","status","isPreview","boolean","notificationEmail","versionMetadata","optional","custom","pattern","any","unknown","description","formAdapterSubmissionMessageDataSchema","main","repeaters","files","array","items","fileName","fileId","userDownloadLink","formAdapterSubmissionMessageResultSchema","formAdapterSubmissionMessagePayloadSchema","meta","data","result"],"sources":["../../../../../src/server/plugins/engine/types/schema.ts"],"sourcesContent":["import {\n FormStatus,\n formVersionMetadataSchema,\n idSchema,\n notificationEmailAddressSchema,\n slugSchema,\n titleSchema\n} from '@defra/forms-model'\nimport Joi from 'joi'\n\nimport { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/enums.js'\nimport {\n type FormAdapterSubmissionMessageData,\n type FormAdapterSubmissionMessageMeta,\n type FormAdapterSubmissionMessagePayload,\n type FormAdapterSubmissionMessageResult\n} from '~/src/server/plugins/engine/types.js'\n\nexport const formAdapterSubmissionMessageMetaSchema =\n Joi.object<FormAdapterSubmissionMessageMeta>().keys({\n schemaVersion: Joi.string().valid(\n ...Object.values(FormAdapterSubmissionSchemaVersion)\n ),\n timestamp: Joi.date().required(),\n referenceNumber: Joi.string().required(),\n formName: titleSchema,\n formId: idSchema,\n formSlug: slugSchema,\n status: Joi.string()\n .valid(...Object.values(FormStatus))\n .required(),\n isPreview: Joi.boolean().required(),\n notificationEmail: notificationEmailAddressSchema.required(),\n versionMetadata: formVersionMetadataSchema.optional(),\n custom: Joi.object()\n .pattern(/^/, Joi.any())\n .unknown()\n .optional()\n .description('Custom properties for the message')\n })\n\nexport const formAdapterSubmissionMessageDataSchema =\n Joi.object<FormAdapterSubmissionMessageData>().keys({\n main: Joi.object(),\n repeaters: Joi.object(),\n files: Joi.object().pattern(\n Joi.string(),\n Joi.array().items(\n Joi.object().keys({\n fileName: Joi.string().required(),\n fileId: Joi.string().required(),\n userDownloadLink: Joi.string().required()\n })\n )\n )\n })\n\nexport const formAdapterSubmissionMessageResultSchema =\n Joi.object<FormAdapterSubmissionMessageResult>().keys({\n files: Joi.object()\n .keys({\n main: Joi.string().required(),\n repeaters: Joi.object()\n })\n .required()\n })\n\nexport const formAdapterSubmissionMessagePayloadSchema =\n Joi.object<FormAdapterSubmissionMessagePayload>().keys({\n meta: formAdapterSubmissionMessageMetaSchema.required(),\n data: formAdapterSubmissionMessageDataSchema.required(),\n result: formAdapterSubmissionMessageResultSchema.required()\n })\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -292,6 +292,7 @@ export interface FormAdapterSubmissionMessageMeta {
|
|
|
292
292
|
isPreview: boolean;
|
|
293
293
|
notificationEmail: string;
|
|
294
294
|
versionMetadata?: FormVersionMetadata;
|
|
295
|
+
custom?: Record<string, unknown>;
|
|
295
296
|
}
|
|
296
297
|
export type FormAdapterSubmissionMessageMetaSerialised = Omit<FormAdapterSubmissionMessageMeta, 'schemaVersion' | 'timestamp' | 'status'> & {
|
|
297
298
|
schemaVersion: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":["FileStatus","UploadStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormDefinition,\n type FormMetadata,\n type FormVersionMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport {\n type PluginProperties,\n type Request,\n type ResponseObject\n} from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel,\n type DatePartsState,\n type MonthYearState\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItemField } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n type FileStatus,\n type FormAdapterSubmissionSchemaVersion,\n type UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormParams,\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/cacheService.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport type AnyFormRequest = FormRequest | FormRequestPayload\nexport type AnyRequest = Request | AnyFormRequest\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n submittedVersionNumber?: number\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<\n FormRequest,\n 'app' | 'method' | 'params' | 'path' | 'query' | 'url' | 'server'\n >\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport {\n FileStatus,\n UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n allowSaveAndExit: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n allowSaveAndExit: boolean\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type GlobalFunction = (value: unknown) => unknown\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: AnyFormRequest,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport type SaveAndExitHandler = (\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n) => ResponseObject\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cache?: CacheService | string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndExit?: SaveAndExitHandler\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n}\n\nexport interface FormAdapterSubmissionMessageMeta {\n schemaVersion: FormAdapterSubmissionSchemaVersion\n timestamp: Date\n referenceNumber: string\n formName: string\n formId: string\n formSlug: string\n status: FormStatus\n isPreview: boolean\n notificationEmail: string\n versionMetadata?: FormVersionMetadata\n}\n\nexport type FormAdapterSubmissionMessageMetaSerialised = Omit<\n FormAdapterSubmissionMessageMeta,\n 'schemaVersion' | 'timestamp' | 'status'\n> & {\n schemaVersion: number\n status: string\n timestamp: string\n}\nexport interface FormAdapterFile {\n fileName: string\n fileId: string\n userDownloadLink: string\n}\n\nexport interface FormAdapterSubmissionMessageResult {\n files: {\n main: string\n repeaters: Record<string, string>\n }\n}\n\n/**\n * A detail item specifically for files\n */\nexport type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {\n field: FileUploadField\n}\nexport type RichFormValue =\n | FormValue\n | FormPayload\n | DatePartsState\n | MonthYearState\n | UkAddressState\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue | null>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, FormAdapterFile[]>\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n result: FormAdapterSubmissionMessageResult\n}\n\nexport interface FormAdapterSubmissionMessage\n extends FormAdapterSubmissionMessagePayload {\n messageId: string\n recordCreatedAt: Date\n}\n\nexport interface FormAdapterSubmissionService {\n handleFormSubmission: (\n submissionMessage: FormAdapterSubmissionMessage\n ) => unknown\n}\n"],"mappings":"AAqDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAsGA,SACEA,UAAU,EACVC,YAAY;;AA8Nd;AACA;AACA","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"types.js","names":["FileStatus","UploadStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormDefinition,\n type FormMetadata,\n type FormVersionMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport {\n type PluginProperties,\n type Request,\n type ResponseObject\n} from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel,\n type DatePartsState,\n type MonthYearState\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItemField } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n type FileStatus,\n type FormAdapterSubmissionSchemaVersion,\n type UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormParams,\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/cacheService.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport type AnyFormRequest = FormRequest | FormRequestPayload\nexport type AnyRequest = Request | AnyFormRequest\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n submittedVersionNumber?: number\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<\n FormRequest,\n 'app' | 'method' | 'params' | 'path' | 'query' | 'url' | 'server'\n >\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport {\n FileStatus,\n UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n allowSaveAndExit: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n allowSaveAndExit: boolean\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type GlobalFunction = (value: unknown) => unknown\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: AnyFormRequest,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport type SaveAndExitHandler = (\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n) => ResponseObject\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cache?: CacheService | string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndExit?: SaveAndExitHandler\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n}\n\nexport interface FormAdapterSubmissionMessageMeta {\n schemaVersion: FormAdapterSubmissionSchemaVersion\n timestamp: Date\n referenceNumber: string\n formName: string\n formId: string\n formSlug: string\n status: FormStatus\n isPreview: boolean\n notificationEmail: string\n versionMetadata?: FormVersionMetadata\n custom?: Record<string, unknown>\n}\n\nexport type FormAdapterSubmissionMessageMetaSerialised = Omit<\n FormAdapterSubmissionMessageMeta,\n 'schemaVersion' | 'timestamp' | 'status'\n> & {\n schemaVersion: number\n status: string\n timestamp: string\n}\nexport interface FormAdapterFile {\n fileName: string\n fileId: string\n userDownloadLink: string\n}\n\nexport interface FormAdapterSubmissionMessageResult {\n files: {\n main: string\n repeaters: Record<string, string>\n }\n}\n\n/**\n * A detail item specifically for files\n */\nexport type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {\n field: FileUploadField\n}\nexport type RichFormValue =\n | FormValue\n | FormPayload\n | DatePartsState\n | MonthYearState\n | UkAddressState\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue | null>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, FormAdapterFile[]>\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n result: FormAdapterSubmissionMessageResult\n}\n\nexport interface FormAdapterSubmissionMessage\n extends FormAdapterSubmissionMessagePayload {\n messageId: string\n recordCreatedAt: Date\n}\n\nexport interface FormAdapterSubmissionService {\n handleFormSubmission: (\n submissionMessage: FormAdapterSubmissionMessage\n ) => unknown\n}\n"],"mappings":"AAqDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAsGA,SACEA,UAAU,EACVC,YAAY;;AA+Nd;AACA;AACA","ignoreList":[]}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %}
|
|
5
5
|
{% from "govuk/components/button/macro.njk" import govukButton %}
|
|
6
6
|
{% from "partials/components.html" import componentList with context %}
|
|
7
|
+
{% from "govuk/components/input/macro.njk" import govukInput %}
|
|
7
8
|
|
|
8
9
|
{% block content %}
|
|
9
10
|
<div class="govuk-grid-row">
|
|
@@ -12,6 +13,13 @@
|
|
|
12
13
|
{% include "partials/preview-banner.html" %}
|
|
13
14
|
{% endif %}
|
|
14
15
|
|
|
16
|
+
{% if errors %}
|
|
17
|
+
{{ govukErrorSummary({
|
|
18
|
+
titleText: "There is a problem",
|
|
19
|
+
errorList: checkErrorTemplates(errors)
|
|
20
|
+
}) }}
|
|
21
|
+
{% endif %}
|
|
22
|
+
|
|
15
23
|
{% if hasMissingNotificationEmail %}
|
|
16
24
|
{% include "partials/warn-missing-notification-email.html" %}
|
|
17
25
|
{% endif %}
|
|
@@ -33,6 +41,10 @@
|
|
|
33
41
|
<form method="post" novalidate>
|
|
34
42
|
<input type="hidden" name="crumb" value="{{ crumb }}">
|
|
35
43
|
|
|
44
|
+
{{ componentList(components) }}
|
|
45
|
+
|
|
46
|
+
{% block customPageContent %}{% endblock %}
|
|
47
|
+
|
|
36
48
|
{% if declaration %}
|
|
37
49
|
<h2 class="govuk-heading-m" id="declaration">Declaration</h2>
|
|
38
50
|
<div class="govuk-body">
|
|
@@ -40,8 +52,6 @@
|
|
|
40
52
|
</div>
|
|
41
53
|
{% endif %}
|
|
42
54
|
|
|
43
|
-
{{ componentList(components) }}
|
|
44
|
-
|
|
45
55
|
<div class="govuk-button-group">
|
|
46
56
|
{% set isDeclaration = declaration or components | length %}
|
|
47
57
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.9",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
"license": "SEE LICENSE IN LICENSE",
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@defra/forms-model": "^3.0.
|
|
73
|
+
"@defra/forms-model": "^3.0.559",
|
|
74
74
|
"@defra/hapi-tracing": "^1.26.0",
|
|
75
75
|
"@elastic/ecs-pino-format": "^1.5.0",
|
|
76
76
|
"@hapi/boom": "^10.0.1",
|