diff --git a/.eleventy.js b/.eleventy.js
index 365a928cf..e8cac127e 100644
--- a/.eleventy.js
+++ b/.eleventy.js
@@ -305,24 +305,39 @@ module.exports = function (eleventyConfig) {
})
// Copies the 11ty base layout and partials to the contributions app layouts directory
- eleventyConfig.on("eleventy.before", async ({ directories, runMode, outputMode }) => {
- fs.copyFile(`${directories.includes}layouts/base.njk`, './views/common/base.njk', (err) => {
- if (err) {
- console.log("Error Found:", err);
- }
- })
-
- fs.copyFile(`${directories.includes}layouts/partials/header.njk`, './views/common/partials/header.njk', (err) => {
- if (err) {
- console.log("Error Found:", err);
- }
- })
- fs.copyFile(`${directories.includes}layouts/partials/footer.njk`, './views/common/partials/footer.njk', (err) => {
- if (err) {
- console.log("Error Found:", err);
- }
- })
- });
+ eleventyConfig.on(
+ 'eleventy.before',
+ async ({ directories, runMode, outputMode }) => {
+ fs.copyFile(
+ `${directories.includes}layouts/base.njk`,
+ './views/common/base.njk',
+ (err) => {
+ if (err) {
+ console.log('Error Found:', err)
+ }
+ }
+ )
+
+ fs.copyFile(
+ `${directories.includes}layouts/partials/header.njk`,
+ './views/common/partials/header.njk',
+ (err) => {
+ if (err) {
+ console.log('Error Found:', err)
+ }
+ }
+ )
+ fs.copyFile(
+ `${directories.includes}layouts/partials/footer.njk`,
+ './views/common/partials/footer.njk',
+ (err) => {
+ if (err) {
+ console.log('Error Found:', err)
+ }
+ }
+ )
+ }
+ )
// Rebuild when a change is made to a component template file
eleventyConfig.addWatchTarget('src/moj/components/**/*.njk')
diff --git a/app.js b/app.js
index 484262a3d..b0c6f8420 100644
--- a/app.js
+++ b/app.js
@@ -86,7 +86,7 @@ app.set('view engine', 'njk')
const njk = expressNunjucks(app, {
watch: isDev,
noCache: false,
- loader: nunjucks.FileSystemLoader,
+ loader: nunjucks.FileSystemLoader
})
njk.env.addFilter('rev', rev)
diff --git a/config.js b/config.js
index 07d9c5887..d7905e904 100644
--- a/config.js
+++ b/config.js
@@ -30,7 +30,9 @@ const config = {
howIsTheComponentUsed: {
label: 'How is the component being used?'
}
- }
+ },
+ showOnCya: true,
+ removable: false
},
'component-image': {
title: 'Component image',
@@ -39,7 +41,9 @@ const config = {
label: 'Upload a file',
hint: 'The file must be a JPG, BMP, PNG, TIF or PDF, and smaller than 10MB.'
}
- }
+ },
+ showOnCya: true,
+ removable: false
},
'accessibility-findings': {
title: 'Accessibility findings',
@@ -57,7 +61,9 @@ const config = {
label: 'Has the component been tested with assistive technology?',
hint: 'This could be with screen reader software or an adaptive keyboard.'
}
- }
+ },
+ showOnCya: false,
+ removable: false
},
'add-external-audit': {
title: 'External accessibility audit',
@@ -77,7 +83,9 @@ const config = {
'/accessibility-findings': {
hasComponentBeenTestedExternalAccessibility: 'yes'
}
- }
+ },
+ showOnCya: true,
+ removable: true
},
'add-internal-audit': {
title: 'Internal accessibility review',
@@ -97,7 +105,9 @@ const config = {
'/accessibility-findings': {
hasComponentBeenTestedInternalAudit: 'yes'
}
- }
+ },
+ showOnCya: true,
+ removable: true
},
'add-assistive-tech': {
title: 'Testing with assistive technology',
@@ -115,32 +125,9 @@ const config = {
'/accessibility-findings': {
hasComponentBeenTestedUsingAssistiveTechnology: 'yes'
}
- }
- },
- prototype: {
- title: 'Prototype',
- fields: {
- componentPrototypeUrl: {
- label: 'Do you have a prototype link?',
- hint: 'For example to the GOV.UK Prototype Kit, Figma, Sketch, Mural or Miro.'
- }
- }
- },
- 'prototype-url': {
- title: 'Prototype details',
- fields: {
- prototypeUrl: {
- label: 'Add the prototype link',
- hint: 'For example, to the GOV.UK Prototype Kit, Figma, Sketch, Mural or Miro. '
- },
- prototypeUrlAdditionalInformation: {
- label: 'Add more information about the prototype (optional)',
- hint: 'Enter any relevant information, for example a username, password or link expiry date.'
- }
},
- conditions: {
- '/prototype': { componentPrototypeUrl: 'yes' }
- }
+ showOnCya: true,
+ removable: true
},
'component-code': {
title: 'Component code',
@@ -148,7 +135,9 @@ const config = {
componentCodeAvailable: {
label: 'Do you have code for the component?'
}
- }
+ },
+ showOnCya: false,
+ removable: false
},
'component-code-details': {
title: 'Component code details',
@@ -171,7 +160,9 @@ const config = {
},
conditions: {
'/component-code': { componentCodeAvailable: 'yes' }
- }
+ },
+ showOnCya: true,
+ removable: true
},
figma: {
title: 'Figma design',
@@ -179,7 +170,9 @@ const config = {
figmaUrl: {
label: 'Do you have a Figma link for the component?'
}
- }
+ },
+ showOnCya: false,
+ removable: false
},
'figma-link': {
title: 'Figma design details',
@@ -195,7 +188,9 @@ const config = {
},
conditions: {
'/figma': { figmaUrl: 'yes' }
- }
+ },
+ showOnCya: true,
+ removable: true
},
'your-details': {
title: 'Your details',
@@ -213,7 +208,9 @@ const config = {
label: 'Sharing your contact details on the component page',
hint: 'Adding your details to the component enables other users to contact you (or your team) to collaborate on the component.\r\nSelect all that apply'
}
- }
+ },
+ showOnCya: true,
+ removable: false
},
'check-your-answers': {}
},
@@ -221,76 +218,15 @@ const config = {
MAX_ADD_ANOTHER: 10,
ACRONYMS_TO_UPPERCASE: ['url'],
CHECK_YOUR_ANSWERS_LABEL_OVERRIDES: {
- // componentOverview: 'Component description',
- // howIsTheComponentUsed: 'How the component is being used in your service',
- // accessibilityReport: 'Audit report',
- // externalOrganisation: 'External accessibility audit supplier',
- // internalOrganisation: 'Internal accessibility audit supplier',
- // issuesDiscovered: 'Issues discovered during the audit',
- // prototypeUrl: 'Prototype link',
- // prototypeUrlAdditionalInformation: 'Additional information',
- // componentCode: 'Code',
- // componentCodeUsage: 'Code usage',
- // componentCodeLanguage: 'Code language',
- // componentCodeLanguageOther: 'Other code language',
- // figmaLink: 'Prototype link',
- // figmaLinkAdditionalInformation: 'Additional information',
- // shareYourDetails: 'Share your details on the component page',
- // componentImage: 'Supporting file'
- },
- CHECK_YOUR_ANSWERS: {
- sections: [
- {
- title: 'Component details',
- data: ['component-details', 'component-image']
- },
- {
- title: 'External accessibility audit',
- data: [
- {
- 'accessibility-findings': {
- includeFields: ['hasComponentBeenTestedExternalAccessibility']
- }
- },
- 'add-external-audit'
- ]
- },
- {
- title: 'Internal accessibility review',
- data: [
- {
- 'accessibility-findings': {
- includeFields: ['hasComponentBeenTestedInternalAudit']
- }
- },
- 'add-internal-audit'
- ]
- },
- {
- title: 'Assistive technology testing',
- data: [
- {
- 'accessibility-findings': {
- includeFields: ['hasComponentBeenTestedUsingAssistiveTechnology']
- }
- },
- 'add-assistive-tech'
- ]
- },
- { title: 'Prototype', data: ['prototype', 'prototype-url'] },
- { title: 'Figma', data: ['figma', 'figma-link'] },
- { title: 'Code', data: ['component-code', 'component-code-details'] },
- { title: 'Your details', data: ['your-details'] }
- ],
- canRemoveStatic: [
-
- ],
- canRemoveMultiples: [
- '/prototype-url',
- '/figma-link',
- '/component-code-details'
- ],
- ignoreFields: []
+ 'component-code-details': {
+ title: 'Code sample',
+ fields: {
+ componentCodeUsage: 'Usage',
+ componentCodeLanguage: 'Language',
+ componentCodeLanguageOther: 'Language',
+ componentCode: 'Code'
+ }
+ }
},
SHARE_YOUR_DETAILS: {
addNameToComponentPage: 'add my name to the component page',
@@ -298,5 +234,4 @@ const config = {
}
}
-
module.exports = config
diff --git a/docs/stylesheets/application.scss b/docs/stylesheets/application.scss
index ed6a93131..2b3e27cc0 100755
--- a/docs/stylesheets/application.scss
+++ b/docs/stylesheets/application.scss
@@ -23,6 +23,7 @@
@forward "components/app-buildingblock-card";
@forward "components/status";
@forward "components/documentation-tabs";
+@forward "components/summary-card";
@view-transition {
navigation: auto;
diff --git a/docs/stylesheets/components/_prose.scss b/docs/stylesheets/components/_prose.scss
index e2d8c6e76..8944a6a73 100644
--- a/docs/stylesheets/components/_prose.scss
+++ b/docs/stylesheets/components/_prose.scss
@@ -47,8 +47,8 @@
@include govuk-typography-weight-bold;
}
- ul,
- ol {
+ ul:not([class]),
+ ol:not([class]) {
@extend %govuk-list;
}
@@ -65,14 +65,8 @@
@extend %govuk-link;
}
- a {
+ a:not(.govuk-button) {
@extend %govuk-link;
-
- /* stylelint-disable selector-no-qualifying-type */
- &.govuk-link--no-visited-state,
- &.govuk-button {
- color: govuk-colour("white");
- }
}
.govuk-link--inverse {
diff --git a/docs/stylesheets/components/_summary-card.scss b/docs/stylesheets/components/_summary-card.scss
new file mode 100644
index 000000000..4600c0d66
--- /dev/null
+++ b/docs/stylesheets/components/_summary-card.scss
@@ -0,0 +1,9 @@
+@use "../moj-frontend" as *;
+
+.govuk-summary-card__title-wrapper {
+ background-color: #e5e6e7;
+}
+
+.govuk-summary-card__content {
+ background-color: govuk-colour("white");
+}
diff --git a/filters/rev.js b/filters/rev.js
index 744ae59af..1bcaa38d5 100644
--- a/filters/rev.js
+++ b/filters/rev.js
@@ -1,14 +1,14 @@
const fs = require('fs')
const rev = (filepath) => {
- if (process.env.ENV === 'production' || process.env.ENV === 'staging') {
- const manifest = JSON.parse(
- fs.readFileSync('public/rev-manifest.json', 'utf8')
- )
- const revision = manifest[filepath]
- return `/${revision || filepath}`
- }
- return `/${filepath}`
+ if (process.env.ENV === 'production' || process.env.ENV === 'staging') {
+ const manifest = JSON.parse(
+ fs.readFileSync('public/rev-manifest.json', 'utf8')
+ )
+ const revision = manifest[filepath]
+ return `/${revision || filepath}`
+ }
+ return `/${filepath}`
}
module.exports = rev
diff --git a/helpers/check-your-answers.js b/helpers/check-your-answers.js
index 42e0a5a81..1cc37ad3e 100644
--- a/helpers/check-your-answers.js
+++ b/helpers/check-your-answers.js
@@ -1,86 +1,60 @@
const {
- MAX_ADD_ANOTHER: maxAddAnother,
- ACRONYMS_TO_UPPERCASE: acronyms,
CHECK_YOUR_ANSWERS_LABEL_OVERRIDES: labelOverrides,
SHARE_YOUR_DETAILS: shareYourDetails,
- CHECK_YOUR_ANSWERS: checkYourAnswersConfig,
COMPONENT_FORM_PAGES: formPages,
ADD_NEW_COMPONENT_ROUTE: hrefRoot
} = require('../config')
const { combineDateFields } = require('./date-fields')
const {
- humanReadableLabel: humanReadableLabelText,
- replaceAcronyms,
+ humanReadableLabel,
truncateText,
sanitizeText,
ucFirst
} = require('./text-helper')
const maxWords = 10000
-// const shareYourDetailsKeys = Object.keys(shareYourDetails)
+let currentSession
/**
* Converts a text label to a human-readable format using a predefined mapping.
*
* @param {string} field - The camelCase field to get a label for.
- * @param {string} form - The form/page that contains the field
+ * @param {string} section - The form/section that contains the field
* @returns {string} - The human-readable label.
*/
-const humanReadableLabel = (field, form = '') => {
+const labelForField = (field, section) => {
// If there's an override, use that
- if (Object.keys(labelOverrides).includes(field)) {
- return labelOverrides[field]
+ const override = labelOverrides[section]?.fields[field]
+ if (override) {
+ return override
}
// Otherwise use the label from the form pages config
- if (form) {
- if (formPages[form]?.fields[field]?.label) {
- return formPages[form]?.fields[field]?.label
- }
+ const label = formPages[section]?.fields[field]?.label
+ if (label) {
+ return label
}
// Else just convert the camelCase to human
- return humanReadableLabelText(field)
+ return humanReadableLabel(field)
}
-/**
- * Extracts and formats answers from session data based on provided forms.
- *
- * @param {Array} data - The forms and config to extract answers from.
- * @param {object} session - The session data.
- * @param {Array} canRemove - The fields that can be removed via a UI action.
- * @returns {object} - The formatted answers for govukSummaryList.
- */
-const answersFromSession = (data, session, canRemove) => {
- const answers = []
- const defaultConfig = { removableFields: canRemove }
- data.forEach((form) => {
- // console.log(form)
- if (typeof form === 'object') {
- console.log('form is an object')
- for (const [formName, formConfig] of Object.entries(form)) {
- // console.log(formConfig)
- const config = Object.assign({}, defaultConfig, formConfig)
- // console.log(config)
- answers.push(...extractFieldData(formName, session, config))
- }
- } else {
- answers.push(...extractFieldData(form, session, defaultConfig))
- }
- })
- // console.log(answers)
- return answers
+const labelForSection = (section, number) => {
+ console.log(section)
+ let label = labelOverrides[section]?.title ?? formPages[section]?.title
+ label = `${label}${number ? ` ${number}` : ''}`
+ return label
}
/**
* Converts an array of values into an HTML list.
*
- * @param {Array} values - The values to convert.
+ * @param {Array} arr - The values to convert.
* @returns {string} - The HTML list.
*/
-const listHTML = (values) => {
- if (!Array.isArray(values)) return ''
+const arrayToUnorderedList = (arr) => {
+ if (!Array.isArray(arr)) return ''
- const listItems = values.map((value) => `
${value}`).join('')
+ const listItems = arr.map((item) => `${item}`).join('')
return ``
}
@@ -94,229 +68,161 @@ const shareYourDetailsValueReplacement = (value) => {
let values = Array.isArray(value) ? value : [value]
values = values.filter((v) => v) // Remove empty values
- if(values.length === 0) {
- return "Do not share my details"
+ if (values.length === 0) {
+ return 'Do not share my details'
}
- return listHTML(
+ return arrayToUnorderedList(
Object.entries(shareYourDetails).map(([key, value]) => {
- return ucFirst(`${(!values.includes(key) ? 'do not ' : '')}${value}`)
+ return ucFirst(`${!values.includes(key) ? 'do not ' : ''}${value}`)
})
)
}
/**
- * Extracts and formats data for a specific field from the session.
+ * gets all sections that should be included in the cya page
*
- * @param {string} field - The field to extract data for.
- * @param {object} session - The session data.
- * @param {object} options - The options for extraction
- * @returns {Array} - The extracted and formatted field data.
+ * @param {array} sessionpages - keys for the pages present in the session
+ * @returns {object}
*/
-// TODO: We don't need the whole session here! only the form we;re extracting
-// for.
-// TODO: Rename this function - extracFieldDataFromForm(form, session, options)
-const extractFieldData = (field, session, options = {}) => {
- const defaults = {
- removableFields: [],
- excludeFields: [],
- includeFields: []
- }
- const opts = Object.assign({}, defaults, options)
- // console.log(opts)
- const fieldName = field
- const fieldPath = `/${field}`
- console.log(`extracting field data for: ${fieldName}`)
-
- let parsedSession = {}
+const getSections = () => {
+ const sections = {}
+ Object.entries(formPages).forEach(([section, config]) => {
+ if (config.showOnCya) {
+ // gather all pages
+ const pages = Object.keys(currentSession).filter((key) =>
+ key.startsWith(`/${section}`)
+ )
- if (opts.includeFields.length > 0) {
- console.log('only including specified fields')
- for (const [key, value] of Object.entries(session)) {
- if (typeof value === 'object' && !Array.isArray(value)) {
- Object.keys(value).forEach(function (field) {
- // console.log(field)
- // console.log(opts.includeFields)
- if (opts.includeFields.includes(field)) {
- console.log('field should be included')
- parsedSession[key] = {}
- parsedSession[key][field] = value[field]
- }
- })
+ if (pages.length > 0) {
+ pages
+ .map((entry) => entry.slice(1)) // remove leading '/'
+ .forEach((repeatSection, index) => {
+ sections[repeatSection] = buildSection(
+ repeatSection,
+ config,
+ pages.length > 1 ? index + 1 : null
+ )
+ })
+ } else {
+ sections[section] = buildSection(section, config)
}
}
- } else {
- console.log('using whole session')
- parsedSession = Object.assign({}, session)
- }
+ })
+ return sections
+}
- // Remove excluded fields from session data
- if (opts.excludeFields.length > 0) {
- console.log('excluding fields')
- parsedSession = Object.entries(parsedSession).reduce(
- (acc, [key, value]) => {
- if (typeof value === 'object' && !Array.isArray(value)) {
- opts.excludeFields.forEach((excludeField) => {
- if (value && typeof value === 'object' && excludeField in value) {
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
- delete value[excludeField]
- }
- })
- }
- acc[key] = value
- return acc
- },
- {}
+/**
+ * builds an object for a summary card section in the cya page
+ *
+ * @param {string} sectionPath - string key of the section, includes subpage
+ * number e.g. component-code-details/1
+ * @param {object} sectionConfig - config object for the section
+ * @param {number} sectionNumber - subpage number for the section
+ * @returns {object}
+ */
+const buildSection = (sectionPath, sectionConfig, sectionNumber) => {
+ const sectionKey = sectionPath.split('/').at(0)
+ const section = {
+ title: labelForSection(sectionKey, sectionNumber),
+ actions: [],
+ answerRows: getAnswersForSection(
+ sectionKey,
+ currentSession[`/${sectionPath}`]
)
}
- // console.log(parsedSession)
-
- // Collect all entries that match the field pattern (e.g., /foo, /foo/1, /foo/2)
- const fieldPattern = new RegExp(`^${fieldPath}(?:/\\d+)?$`)
- const matchingEntries = Object.entries(parsedSession).filter(([key]) =>
- fieldPattern.test(key)
- )
-
- let codeLanguageIsOther = false;
-
- if (matchingEntries.length === 0) return []
-
- return matchingEntries.flatMap(([key, value]) => {
- if (typeof value === 'object' && !Array.isArray(value)) {
- // Handle multiple entries
- const values = combineDateFields(value)
- let removeAdded = false
- return Object.entries(values).map(([subKey, subValue]) => {
- const actionItems = []
- const displayValue = {
- value: {}
- }
-
- // Don't include code language if language is other
- if( subKey === 'componentCodeLanguage' && subValue === 'other') {
- codeLanguageIsOther = true
- return null
- }
+ if (sectionConfig.removable && section.answerRows.length > 0) {
+ section.actions.push(link(`remove/${sectionKey}`, 'Remove', section.title))
+ }
- // Only include other language value if `componentCodeLanguage` is
- // set to 'other'
- if(subKey === 'componentCodeLanguageOther' && !codeLanguageIsOther) {
- return null
- }
+ if (section.answerRows.length === 0 && sectionConfig.conditions) {
+ const href = Object.keys(sectionConfig.conditions).at(0)
- if (subKey === 'shareYourDetails') {
- displayValue.value.html = shareYourDetailsValueReplacement(subValue)
- } else if(subKey === 'componentCode') {
- displayValue.value.text = 'Code provided'
- } else if (
- subValue &&
- typeof subValue === 'object' &&
- 'originalname' in subValue
- ) {
- displayValue.value.text = subValue.originalname
- } else {
- displayValue.value.text = sanitizeText(
- truncateText(subValue, maxWords)
- )
- }
+ section.actions.push(link(href, 'Change', section.title))
+ } else {
+ const linkText = sectionKey.startsWith('component-code')
+ ? 'Review & Change'
+ : 'Change'
- // console.log(`display value: ${displayValue.value.text}`)
+ section.actions.push(link(sectionKey, linkText, section.title))
+ }
- if (opts.removableFields.includes(key) && !removeAdded) {
- removeAdded = true
- actionItems.push({
- href: `${hrefRoot}/remove${key}`,
- text: 'Remove',
- visuallyHiddenText: `${humanReadableLabel(fieldName)} - ${humanReadableLabel(subKey, fieldName)}`
- })
- }
+ return section
+}
- actionItems.push({
- href: `${hrefRoot}${key}`,
- text: ((subKey === 'componentCode') ? 'Review & Change' : 'Change'),
- visuallyHiddenText: `${humanReadableLabel(fieldName)} - ${humanReadableLabel(subKey, fieldName)}`
- })
+const link = (href, text, visuallyHiddenText) => {
+ return {
+ href: `${hrefRoot}${(href.startsWith('/') ? href : `/${href}`)}`,
+ text,
+ visuallyHiddenText
+ }
+}
- // console.log(
- // `display key: ${formPages[fieldName]?.fields[subKey]?.label}`
- // )
+/**
+ * Gets all the answers from the session for a section
+ *
+ * @param {string} sectionKey - string key of the root section (no subpage)
+ * @param {object} sessionData - user response data from the session
+ * @returns {Array}
+ */
+const getAnswersForSection = (sectionKey, sessionData = {}) => {
+ let codeLanguageIsOther = false
+ const data = combineDateFields(sessionData)
- return {
- key: { text: humanReadableLabel(subKey, fieldName) },
- ...displayValue,
- actions: {
- items: actionItems
- }
- }
- })
+ return Object.entries(data).map(([questionKey, answerValue]) => {
+ const displayValue = {
+ value: {}
}
- const actionItems = []
+ // Don't include code language if language is other
+ if (questionKey === 'componentCodeLanguage' && answerValue === 'other') {
+ codeLanguageIsOther = true
+ return null
+ }
- if (opts.removableFields.includes(key)) {
- actionItems.push({
- href: `${hrefRoot}/remove${key}`,
- text: 'Remove',
- visuallyHiddenText: replaceAcronyms(
- humanReadableLabel(fieldName),
- acronyms
- )
- })
+ // Only include other language value if `componentCodeLanguage` is set to 'other'
+ if (questionKey === 'componentCodeLanguageOther' && !codeLanguageIsOther) {
+ return null
}
- actionItems.push({
- href: `${hrefRoot}${key}`,
- text: 'Change',
- visuallyHiddenText: replaceAcronyms(
- humanReadableLabel(fieldName),
- acronyms
+ if (questionKey === 'shareYourDetails') {
+ displayValue.value.html = shareYourDetailsValueReplacement(answerValue)
+ } else if (questionKey === 'componentCode') {
+ displayValue.value.text = 'Code provided'
+ } else if (
+ answerValue &&
+ typeof answerValue === 'object' &&
+ 'originalname' in answerValue
+ ) {
+ displayValue.value.text = answerValue.originalname
+ } else {
+ displayValue.value.text = sanitizeText(
+ truncateText(answerValue, maxWords)
)
- })
+ }
- // Handle single entry
- return [
- {
- key: { text: replaceAcronyms(humanReadableLabel(fieldName), acronyms) },
- value: { text: sanitizeText(truncateText(value, maxWords)) },
- actions: {
- items: actionItems
- }
- }
- ]
- }).filter((entry) => entry)
+ return {
+ key: { text: labelForField(questionKey, sectionKey) },
+ ...displayValue
+ }
+ })
}
/**
- * Main function that processes the session data and returns formatted answers for govukSummaryList.
+ * Main function that processes the session data and returns formatted answers for govukSummaryCards.
*
* @param {object} session - The session data.
- * @returns {object} - The formatted answers.
+ * @returns {Array} - Array of objects to build summary cards.
*/
const checkYourAnswers = (session) => {
- const {
- sections, // The sections for the CYA page
- canRemoveStatic, // The fields that can be removed via a UI action
- canRemoveMultiples // The fields that can be removed via a UI action (where we have dyamically multiple versions)
- } = checkYourAnswersConfig
-
- // Generate a list of fields that can be removed
- const canRemove = [
- ...canRemoveStatic,
- ...canRemoveMultiples.flatMap((item) =>
- Array.from({ length: maxAddAnother }, (_, i) => `${item}/${i + 1}`)
- )
- ]
+ currentSession = session
+ const sections = getSections()
- const answers = []
- sections.forEach((section) => {
- answers.push({
- title: section.title,
- answers: answersFromSession(section.data, session, canRemove)
- })
- })
- // console.log(answers)
- return answers
+ return Object.values(sections)
}
-module.exports = checkYourAnswers
+module.exports = {
+ checkYourAnswers,
+ getAnswersForSection
+}
diff --git a/helpers/form-pages.spec.js b/helpers/form-pages.spec.js
index 760eabcf1..801a930cd 100644
--- a/helpers/form-pages.spec.js
+++ b/helpers/form-pages.spec.js
@@ -1,6 +1,5 @@
const getCurrentFormPages = require('./form-pages')
-
describe('getCurrentFormPages', () => {
const session = {}
@@ -43,4 +42,3 @@ describe('getCurrentFormPages', () => {
])
})
})
-
diff --git a/helpers/page-navigation.js b/helpers/page-navigation.js
index fbcab7e80..cfb76343f 100644
--- a/helpers/page-navigation.js
+++ b/helpers/page-navigation.js
@@ -1,6 +1,4 @@
-const {
- ADD_NEW_COMPONENT_ROUTE: urlRoot
-} = require('../config')
+const { ADD_NEW_COMPONENT_ROUTE: urlRoot } = require('../config')
const getCurrentFormPages = require('./form-pages.js')
diff --git a/helpers/previous-page.spec.js b/helpers/previous-page.spec.js
index 38f228325..f308bfe97 100644
--- a/helpers/previous-page.spec.js
+++ b/helpers/previous-page.spec.js
@@ -15,8 +15,8 @@ describe('getPreviousPage function', () => {
it('should skip pages if conditions are not met', () => {
session['/accessibility-findings'] = {
hasComponentBeenTestedExternalAccessibility: 'no',
- hasComponentBeenTestedInternalAudit: 'no',
- hasComponentBeenTestedUsingAssistiveTechnology: 'yes'
+ hasComponentBeenTestedInternalAudit: 'no',
+ hasComponentBeenTestedUsingAssistiveTechnology: 'yes'
}
const result = getPreviousPage('/add-assistive-tech', session)
expect(result).toBe(`${urlBase}/accessibility-findings`)
diff --git a/helpers/text-helper.js b/helpers/text-helper.js
index b24619e05..626c72e5a 100644
--- a/helpers/text-helper.js
+++ b/helpers/text-helper.js
@@ -7,21 +7,12 @@ const toCamelCaseWithRows = (str) => {
// Format field names into readable labels
const humanReadableLabel = (field) => {
- return field
+ return ucFirst(field)
.replace(/([a-z])([A-Z])/g, '$1 $2')
.replace(/-/g, ' ')
- .replace(/\b\w/g, (char) => char.toUpperCase())
.trim()
}
-// Replace acronyms in a string with uppercase value
-const replaceAcronyms = (str, acronyms) => {
- return str.replace(
- new RegExp(`\\b(${acronyms.join('|')})\\b`, 'gi'),
- (match) => match.toUpperCase()
- )
-}
-
const urlToTitleCase = (str) => {
return str
.toLowerCase()
@@ -31,9 +22,9 @@ const urlToTitleCase = (str) => {
}
const ucFirst = (str) => {
- if (!str) return str;
+ if (!str) return str
- return str[0].toUpperCase() + str.slice(1);
+ return str[0].toUpperCase() + str.slice(1)
}
const truncateText = (text, maxWords) => {
@@ -59,7 +50,6 @@ module.exports = {
toCamelCaseWithRows,
humanReadableLabel,
urlToTitleCase,
- replaceAcronyms,
truncateText,
sanitizeText,
ucFirst
diff --git a/middleware/component-session.js b/middleware/component-session.js
index c33ea627d..bfb42079b 100644
--- a/middleware/component-session.js
+++ b/middleware/component-session.js
@@ -7,12 +7,12 @@ const {
ADD_NEW_COMPONENT_ROUTE,
COMPONENT_FORM_PAGES
} = require('../config')
+const { getAnswersForSection } = require('../helpers/check-your-answers')
const ApplicationError = require('../helpers/application-error')
const extractBody = require('../helpers/extract-body')
const getCurrentFormPages = require('../helpers/form-pages')
const { getNextPage, getPreviousPage } = require('../helpers/page-navigation')
const redis = require('../helpers/redis-client')
-const { humanReadableLabel } = require('../helpers/text-helper')
const camelToKebab = (str) =>
str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
@@ -232,12 +232,10 @@ const getFormDataFromSession = (req, res, next) => {
const getFormSummaryListForRemove = (req, res, next) => {
const url = req.url.replace('/remove', '')
const formData = req.session[url]
+ const sectionKey = url.split('/').at(0)
delete req.removeSummaryRows
if (formData) {
- req.removeSummaryRows = Object.entries(formData).map(([key, value]) => ({
- key: { text: humanReadableLabel(key) },
- value: { text: value?.originalname || value }
- }))
+ req.removeSummaryRows = getAnswersForSection(sectionKey, formData)
}
next()
}
diff --git a/nodemon.json b/nodemon.json
index 3592d74e9..659d6c264 100644
--- a/nodemon.json
+++ b/nodemon.json
@@ -1,5 +1,11 @@
{
- "watch": ["app.js", "config.js", "routes/*.js", "helpers/*.js", "middleware/*.js"],
+ "watch": [
+ "app.js",
+ "config.js",
+ "routes/*.js",
+ "helpers/*.js",
+ "middleware/*.js"
+ ],
"ignore": ["**/*.spec.*"],
"signal": "SIGINT"
}
diff --git a/routes/add-component.js b/routes/add-component.js
index a6daa0c49..4baa5e79a 100644
--- a/routes/add-component.js
+++ b/routes/add-component.js
@@ -6,7 +6,7 @@ const multer = require('multer')
const { COMPONENT_FORM_PAGES, ADD_NEW_COMPONENT_ROUTE } = require('../config')
const ApplicationError = require('../helpers/application-error')
-const checkYourAnswers = require('../helpers/check-your-answers')
+const {checkYourAnswers} = require('../helpers/check-your-answers')
const getPrTitleAndDescription = require('../helpers/get-pr-title-and-description')
const sessionData = require('../helpers/mockSessionData/sessionData.js')
const { urlToTitleCase } = require('../helpers/text-helper')
@@ -91,17 +91,19 @@ router.get('*', (req, res, next) => {
})
// Check your answers page
-router.get(`/${checkYourAnswersPath}`,
+router.get(
+ `/${checkYourAnswersPath}`,
sessionStarted,
getBackLink,
(req, res) => {
- const sections = checkYourAnswers(req.session)
- res.render(checkYourAnswersPath, {
- submitUrl: req.originalUrl,
- sections,
- csrfToken: req?.session?.csrfToken
- })
-})
+ const sections = checkYourAnswers(req.session)
+ res.render(checkYourAnswersPath, {
+ submitUrl: req.originalUrl,
+ sections,
+ csrfToken: req?.session?.csrfToken
+ })
+ }
+)
if (process.env.DEV_DUMMY_DATA) {
// Set dummy data for add component via session
@@ -134,15 +136,13 @@ router.get('/start', (req, res) => {
// Confirmation page
router.get('/confirmation', (req, res) => {
res.render('confirmation', {
- title: "Component submitted"
+ title: 'Component submitted'
})
})
router.all('*', sessionStarted) // Check that we have a session in progress
-router.post('/start',
- verifyCsrf,
- (req, res) => {
+router.post('/start', verifyCsrf, (req, res) => {
res.redirect('/contribute/add-new-component/component-details')
})
@@ -152,10 +152,10 @@ router.get(
validatePageParams,
getFormSummaryListForRemove,
(req, res) => {
- const summary = req?.removeSummaryRows
+ const summaryRows = req?.removeSummaryRows
const type = urlToTitleCase(req?.params?.page || '')
- if (!req?.params?.page || !summary) {
+ if (!req?.params?.page || !summaryRows) {
res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/${checkYourAnswersPath}`)
} else {
res.render('remove', {
@@ -163,8 +163,7 @@ router.get(
formData: req?.formData,
backLink: `${ADD_NEW_COMPONENT_ROUTE}/${checkYourAnswersPath}`,
type,
- summary,
- deleteText: `Delete ${type}`,
+ summaryRows,
csrfToken: req?.session?.csrfToken
})
}
diff --git a/views/common/macros/summary-card.njk b/views/common/macros/summary-card.njk
new file mode 100644
index 000000000..fbd05ba6c
--- /dev/null
+++ b/views/common/macros/summary-card.njk
@@ -0,0 +1,48 @@
+{% from "govuk/macros/attributes.njk" import govukAttributes %}
+
+{%- macro _actionLink(action, cardTitle) %}
+
+ {{- action.html | safe | indent(4) if action.html else action.text -}}
+ {%- if action.visuallyHiddenText or cardTitle -%}
+
+ {%- if action.visuallyHiddenText %} {{ action.visuallyHiddenText }}{% endif -%}
+ {%- if cardTitle %} ({{ cardTitle.html | indent(6) | safe if cardTitle.html else cardTitle.text }}){% endif -%}
+
+ {%- endif -%}
+
+{% endmacro -%}
+
+{%- macro summaryCard(params) %}
+{%- set headingLevel = params.title.headingLevel if params.title.headingLevel else 2 -%}
+
+
+
+ {% if params.title %}
+
+ {{ params.title.html | safe | trim | indent(6) if params.title.html else params.title.text }}
+
+ {% endif %}
+ {% if params.actions.items.length %}
+ {% if params.actions.items.length == 1 %}
+
+ {{ _actionLink(params.actions.items[0], params.title) | trim | indent(4) }}
+
+ {% else %}
+
+ {% for action in params.actions.items %}
+ -
+ {{ _actionLink(action, params.title) | trim | indent(8) }}
+
+ {% endfor %}
+
+ {% endif %}
+ {% endif %}
+
+
+
+ {{ caller() | trim }}
+
+
+{% endmacro -%}
diff --git a/views/common/partials/side-navigation.njk b/views/common/partials/side-navigation.njk
index da8a421fc..0bbc1c3b4 100644
--- a/views/common/partials/side-navigation.njk
+++ b/views/common/partials/side-navigation.njk
@@ -393,14 +393,14 @@
- Setting up Figma prototypes
+ Choosing a prototyping method
- Choosing a prototyping method
+ Setting up Figma prototypes
diff --git a/views/community/pages/check-your-answers.njk b/views/community/pages/check-your-answers.njk
index 4065d03d8..16dfc7cf9 100644
--- a/views/community/pages/check-your-answers.njk
+++ b/views/community/pages/check-your-answers.njk
@@ -1,4 +1,5 @@
{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %}
+{% from "macros/summary-card.njk" import summaryCard %}
{% from "govuk/components/button/macro.njk" import govukButton %}
{% extends "base.njk" %}
{% block content %}
@@ -8,12 +9,24 @@
Check your answers
{% for section in sections %}
- {% if section.answers.length > 0 %}
- {{ section.title }}
+ {% set cardParams = {
+ title: {
+ text: section.title
+ },
+ actions: {
+ items: section.actions
+ }
+ }
+ %}
+ {% if section.answerRows.length > 0 %}
{{ govukSummaryList({
- rows: section.answers,
- classes: "govuk-!-margin-bottom-9"
+ card:cardParams,
+ rows: section.answerRows
}) }}
+ {% else %}
+ {% call summaryCard(cardParams) -%}
+ None
+ {%- endcall %}
{% endif %}
{% endfor %}
diff --git a/views/community/pages/figma-link.njk b/views/community/pages/figma-link.njk
index ce85fb46a..3670e7c38 100644
--- a/views/community/pages/figma-link.njk
+++ b/views/community/pages/figma-link.njk
@@ -58,14 +58,6 @@
text: 'Continue',
preventDoubleClick: true
}) }}
- {% if showAddAnother %}
-
- {{ govukButton({
- text: "Add another link",
- classes: "govuk-button--secondary",
- name: "addAnother"
- }) }}
- {% endif %}
diff --git a/views/community/pages/remove.njk b/views/community/pages/remove.njk
index d7b65f873..df3b3f72d 100644
--- a/views/community/pages/remove.njk
+++ b/views/community/pages/remove.njk
@@ -12,7 +12,7 @@
{% endif %}