From b29b9fa6e1e3090215e2df010efa3db519a947fa Mon Sep 17 00:00:00 2001 From: Chris Pymm Date: Thu, 19 Jun 2025 06:16:32 +0100 Subject: [PATCH 1/6] docs: swicth CYA to use summary cards --- docs/stylesheets/application.scss | 1 + docs/stylesheets/components/_summary-card.scss | 9 +++++++++ helpers/check-your-answers.js | 6 +++--- views/common/partials/side-navigation.njk | 4 ++-- views/community/pages/check-your-answers.njk | 9 ++++++++- 5 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 docs/stylesheets/components/_summary-card.scss 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/_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/helpers/check-your-answers.js b/helpers/check-your-answers.js index 42e0a5a81..c2a61f469 100644 --- a/helpers/check-your-answers.js +++ b/helpers/check-your-answers.js @@ -245,9 +245,9 @@ const extractFieldData = (field, session, options = {}) => { return { key: { text: humanReadableLabel(subKey, fieldName) }, ...displayValue, - actions: { - items: actionItems - } + // actions: { + // items: actionItems + // } } }) } 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..e19f556f0 100644 --- a/views/community/pages/check-your-answers.njk +++ b/views/community/pages/check-your-answers.njk @@ -9,8 +9,15 @@ {% for section in sections %} {% if section.answers.length > 0 %} -

    {{ section.title }}

    {{ govukSummaryList({ + card: { + title: { + text: section.title + }, + actions: { + items: [] + } + }, rows: section.answers, classes: "govuk-!-margin-bottom-9" }) }} From 832c3dd9c54f29cebf9e1b810e283b6af99995d7 Mon Sep 17 00:00:00 2001 From: Chris Pymm Date: Fri, 20 Jun 2025 14:20:52 +0100 Subject: [PATCH 2/6] docs: updated cya page structure --- .eleventy.js | 51 ++- app.js | 2 +- config.js | 140 +++++--- filters/rev.js | 16 +- helpers/check-your-answers.js | 329 ++++++------------- helpers/form-pages.spec.js | 2 - helpers/page-navigation.js | 4 +- helpers/previous-page.spec.js | 4 +- helpers/text-helper.js | 15 +- nodemon.json | 8 +- routes/add-component.js | 24 +- views/community/pages/check-your-answers.njk | 6 +- 12 files changed, 259 insertions(+), 342 deletions(-) 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..3973dfc91 100644 --- a/config.js +++ b/config.js @@ -30,7 +30,10 @@ const config = { howIsTheComponentUsed: { label: 'How is the component being used?' } - } + }, + showOnCya: true, + removable: false, + multiple: false }, 'component-image': { title: 'Component image', @@ -39,7 +42,10 @@ 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, + multiple: false }, 'accessibility-findings': { title: 'Accessibility findings', @@ -57,7 +63,10 @@ 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, + multiple: false }, 'add-external-audit': { title: 'External accessibility audit', @@ -77,7 +86,10 @@ const config = { '/accessibility-findings': { hasComponentBeenTestedExternalAccessibility: 'yes' } - } + }, + showOnCya: true, + removable: true, + multiple: false }, 'add-internal-audit': { title: 'Internal accessibility review', @@ -97,7 +109,10 @@ const config = { '/accessibility-findings': { hasComponentBeenTestedInternalAudit: 'yes' } - } + }, + showOnCya: true, + removable: true, + multiple: false }, 'add-assistive-tech': { title: 'Testing with assistive technology', @@ -115,7 +130,10 @@ const config = { '/accessibility-findings': { hasComponentBeenTestedUsingAssistiveTechnology: 'yes' } - } + }, + showOnCya: true, + removable: true, + multiple: false }, prototype: { title: 'Prototype', @@ -124,7 +142,10 @@ const config = { label: 'Do you have a prototype link?', hint: 'For example to the GOV.UK Prototype Kit, Figma, Sketch, Mural or Miro.' } - } + }, + showOnCya: false, + removable: false, + multiple: false }, 'prototype-url': { title: 'Prototype details', @@ -140,7 +161,10 @@ const config = { }, conditions: { '/prototype': { componentPrototypeUrl: 'yes' } - } + }, + showOnCya: true, + removable: true, + multiple: false }, 'component-code': { title: 'Component code', @@ -148,7 +172,10 @@ const config = { componentCodeAvailable: { label: 'Do you have code for the component?' } - } + }, + showOnCya: false, + removable: false, + multiple: false }, 'component-code-details': { title: 'Component code details', @@ -171,7 +198,10 @@ const config = { }, conditions: { '/component-code': { componentCodeAvailable: 'yes' } - } + }, + showOnCya: true, + removable: true, + multiple: true }, figma: { title: 'Figma design', @@ -179,7 +209,10 @@ const config = { figmaUrl: { label: 'Do you have a Figma link for the component?' } - } + }, + showOnCya: false, + removable: false, + multiple: false }, 'figma-link': { title: 'Figma design details', @@ -195,7 +228,10 @@ const config = { }, conditions: { '/figma': { figmaUrl: 'yes' } - } + }, + showOnCya: true, + removable: true, + multiple: false }, 'your-details': { title: 'Your details', @@ -213,7 +249,10 @@ 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, + multiple: false }, 'check-your-answers': {} }, @@ -242,52 +281,58 @@ const config = { sections: [ { title: 'Component details', - data: ['component-details', 'component-image'] + data: ['component-details'], + removable: false + }, + { + title: 'Component image', + data: ['component-image'], + removable: false }, { title: 'External accessibility audit', - data: [ - { - 'accessibility-findings': { - includeFields: ['hasComponentBeenTestedExternalAccessibility'] - } - }, - 'add-external-audit' - ] + data: ['add-external-audit'], + conditions: { + '/accessibility-findings': { + hasComponentBeenTestedExternalAccessibility: 'yes' + } + }, + removable: true }, { title: 'Internal accessibility review', - data: [ - { - 'accessibility-findings': { - includeFields: ['hasComponentBeenTestedInternalAudit'] - } - }, - 'add-internal-audit' - ] + data: ['add-internal-audit'], + conditions: { + '/accessibility-findings': { + hasComponentBeenTestedInternalAudit: 'yes' + } + }, + removable: true }, { title: 'Assistive technology testing', - data: [ - { - 'accessibility-findings': { - includeFields: ['hasComponentBeenTestedUsingAssistiveTechnology'] - } - }, - 'add-assistive-tech' - ] + data: ['add-assistive-tech'], + conditions: { + '/accessibility-findings': { + hasComponentBeenTestedUsingAssistiveTechnology: 'yes' + } + }, + removable: true }, - { 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: [ - + { title: 'Prototype', data: ['prototype-url'], removable: true }, + { title: 'Figma', data: ['figma-link'], removable: true }, + { + title: 'Code', + data: ['component-code-details'], + multiple: true, + removable: true + }, + { title: 'Your details', data: ['your-details'], removable: false } ], + canRemoveStatic: [], canRemoveMultiples: [ - '/prototype-url', - '/figma-link', + // '/prototype-url', + // '/figma-link', '/component-code-details' ], ignoreFields: [] @@ -298,5 +343,4 @@ const config = { } } - module.exports = config 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 c2a61f469..b2b438cb3 100644 --- a/helpers/check-your-answers.js +++ b/helpers/check-your-answers.js @@ -1,86 +1,53 @@ 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) /** * 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] } // Otherwise use the label from the form pages config - if (form) { - if (formPages[form]?.fields[field]?.label) { - return formPages[form]?.fields[field]?.label + if (section) { + const label = formPages[section]?.fields[field]?.label + if (label) { + return label } } // Else just convert the camelCase to human - return humanReadableLabelText(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 + return humanReadableLabel(field) } /** * 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,197 +61,112 @@ 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 = {} - - 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] - } - }) +const getSections = (sessionPages) => { + const sections = {} + Object.entries(formPages).forEach(([section, config]) => { + if (config.showOnCya) { + if (config.multiple) { + // gather all subpages + sessionPages + .filter((key) => key.startsWith(`/${section}`)) + .map((entry) => entry.slice(1)) // remove leading '/' + .forEach((repeatSection, index) => { + sections[repeatSection] = buildSection( + repeatSection, + config, + index + 1 + ) + }) + } 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 - }, - {} - ) +const buildSection = (sectionKey, sectionConfig, sectionNumber) => { + const section = { + title: `${sectionConfig.title}${sectionNumber ? ` ${sectionNumber}` : ''}`, + actions: [], + section: [] } - // 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 - } - - // Only include other language value if `componentCodeLanguage` is - // set to 'other' - if(subKey === 'componentCodeLanguageOther' && !codeLanguageIsOther) { - return null - } - - 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) - ) - } - - // console.log(`display value: ${displayValue.value.text}`) + if (sectionConfig.removable) { + section.actions.push({ + href: `${hrefRoot}/remove/${sectionKey}`, + text: 'Remove', + visuallyHiddenText: section.title + }) + } - if (opts.removableFields.includes(key) && !removeAdded) { - removeAdded = true - actionItems.push({ - href: `${hrefRoot}/remove${key}`, - text: 'Remove', - visuallyHiddenText: `${humanReadableLabel(fieldName)} - ${humanReadableLabel(subKey, fieldName)}` - }) - } + section.actions.push({ + href: `${hrefRoot}/${sectionKey}`, + text: 'Change', + visuallyHiddenText: section.title + }) - actionItems.push({ - href: `${hrefRoot}${key}`, - text: ((subKey === 'componentCode') ? 'Review & Change' : 'Change'), - visuallyHiddenText: `${humanReadableLabel(fieldName)} - ${humanReadableLabel(subKey, fieldName)}` - }) + return section +} - // console.log( - // `display key: ${formPages[fieldName]?.fields[subKey]?.label}` - // ) +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 + } + }) } /** @@ -294,29 +176,14 @@ const extractFieldData = (field, session, options = {}) => { * @returns {object} - The formatted answers. */ 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}`) - ) - ] + const sessionPages = Object.keys(session) + const sections = getSections(sessionPages) - const answers = [] - sections.forEach((section) => { - answers.push({ - title: section.title, - answers: answersFromSession(section.data, session, canRemove) - }) + Object.entries(sections).forEach(([key, section]) => { + section.answerRows = getAnswersForSection(key, session[`/${key}`]) }) - // console.log(answers) - return answers + + return Object.values(sections) } module.exports = checkYourAnswers 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..a8e28cabc 100644 --- a/helpers/text-helper.js +++ b/helpers/text-helper.js @@ -10,16 +10,8 @@ const humanReadableLabel = (field) => { return 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() - ) + .ucFirst() } const urlToTitleCase = (str) => { @@ -31,9 +23,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 +51,6 @@ module.exports = { toCamelCaseWithRows, humanReadableLabel, urlToTitleCase, - replaceAcronyms, truncateText, sanitizeText, ucFirst 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..d39f658ff 100644 --- a/routes/add-component.js +++ b/routes/add-component.js @@ -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') }) diff --git a/views/community/pages/check-your-answers.njk b/views/community/pages/check-your-answers.njk index e19f556f0..23532c241 100644 --- a/views/community/pages/check-your-answers.njk +++ b/views/community/pages/check-your-answers.njk @@ -8,20 +8,18 @@

    Check your answers

    {% for section in sections %} - {% if section.answers.length > 0 %} {{ govukSummaryList({ card: { title: { text: section.title }, actions: { - items: [] + items: section.actions } }, - rows: section.answers, + rows: section.answerRows, classes: "govuk-!-margin-bottom-9" }) }} - {% endif %} {% endfor %} From 2766d1986d0ed39ea2e5a2152aeb9b0fe626c734 Mon Sep 17 00:00:00 2001 From: Chris Pymm Date: Fri, 20 Jun 2025 14:30:56 +0100 Subject: [PATCH 3/6] docs: cya tweaks --- docs/stylesheets/components/_prose.scss | 4 ++-- helpers/check-your-answers.js | 2 +- helpers/text-helper.js | 3 +-- views/community/pages/check-your-answers.njk | 5 ++--- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/stylesheets/components/_prose.scss b/docs/stylesheets/components/_prose.scss index e2d8c6e76..e1205f169 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; } diff --git a/helpers/check-your-answers.js b/helpers/check-your-answers.js index b2b438cb3..beaea22db 100644 --- a/helpers/check-your-answers.js +++ b/helpers/check-your-answers.js @@ -119,7 +119,7 @@ const buildSection = (sectionKey, sectionConfig, sectionNumber) => { section.actions.push({ href: `${hrefRoot}/${sectionKey}`, - text: 'Change', + text: sectionKey.startsWith('component-code') ? 'Review & Change' : 'Change', visuallyHiddenText: section.title }) diff --git a/helpers/text-helper.js b/helpers/text-helper.js index a8e28cabc..626c72e5a 100644 --- a/helpers/text-helper.js +++ b/helpers/text-helper.js @@ -7,11 +7,10 @@ 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, ' ') .trim() - .ucFirst() } const urlToTitleCase = (str) => { diff --git a/views/community/pages/check-your-answers.njk b/views/community/pages/check-your-answers.njk index 23532c241..caed34a83 100644 --- a/views/community/pages/check-your-answers.njk +++ b/views/community/pages/check-your-answers.njk @@ -17,9 +17,8 @@ items: section.actions } }, - rows: section.answerRows, - classes: "govuk-!-margin-bottom-9" - }) }} + rows: section.answerRows + }) }} {% endfor %} From ccc7bada145afa10989d6121964bc76d717bf2c2 Mon Sep 17 00:00:00 2001 From: Chris Pymm Date: Mon, 23 Jun 2025 12:04:44 +0100 Subject: [PATCH 4/6] docs: allow overrides of cya section titles as well as field labels --- config.js | 85 ++++------------------------------- helpers/check-your-answers.js | 61 +++++++++++++++++-------- 2 files changed, 52 insertions(+), 94 deletions(-) diff --git a/config.js b/config.js index 3973dfc91..d6bd67c93 100644 --- a/config.js +++ b/config.js @@ -260,82 +260,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'], - removable: false - }, - { - title: 'Component image', - data: ['component-image'], - removable: false - }, - { - title: 'External accessibility audit', - data: ['add-external-audit'], - conditions: { - '/accessibility-findings': { - hasComponentBeenTestedExternalAccessibility: 'yes' - } - }, - removable: true - }, - { - title: 'Internal accessibility review', - data: ['add-internal-audit'], - conditions: { - '/accessibility-findings': { - hasComponentBeenTestedInternalAudit: 'yes' - } - }, - removable: true - }, - { - title: 'Assistive technology testing', - data: ['add-assistive-tech'], - conditions: { - '/accessibility-findings': { - hasComponentBeenTestedUsingAssistiveTechnology: 'yes' - } - }, - removable: true - }, - { title: 'Prototype', data: ['prototype-url'], removable: true }, - { title: 'Figma', data: ['figma-link'], removable: true }, - { - title: 'Code', - data: ['component-code-details'], - multiple: true, - removable: true - }, - { title: 'Your details', data: ['your-details'], removable: false } - ], - 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', diff --git a/helpers/check-your-answers.js b/helpers/check-your-answers.js index beaea22db..8992a0565 100644 --- a/helpers/check-your-answers.js +++ b/helpers/check-your-answers.js @@ -22,22 +22,28 @@ const maxWords = 10000 * @param {string} section - The form/section that contains the field * @returns {string} - The human-readable label. */ -const labelForField = (field, section = '') => { +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 (section) { - const label = formPages[section]?.fields[field]?.label - if (label) { - return label - } + const label = formPages[section]?.fields[field]?.label + if (label) { + return label } // Else just convert the camelCase to human return humanReadableLabel(field) } +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. * @@ -73,9 +79,9 @@ const shareYourDetailsValueReplacement = (value) => { } /** - * Gets all sections that should be included in the cya page + * gets all sections that should be included in the cya page * - * @param {Array} sessionPages - keys for the pages present in the session + * @param {array} sessionpages - keys for the pages present in the session * @returns {object} */ const getSections = (sessionPages) => { @@ -102,11 +108,20 @@ const getSections = (sessionPages) => { return sections } -const buildSection = (sectionKey, sectionConfig, sectionNumber) => { +/** + * 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: `${sectionConfig.title}${sectionNumber ? ` ${sectionNumber}` : ''}`, - actions: [], - section: [] + title: labelForSection(sectionKey, sectionNumber), + actions: [] } if (sectionConfig.removable) { @@ -119,13 +134,22 @@ const buildSection = (sectionKey, sectionConfig, sectionNumber) => { section.actions.push({ href: `${hrefRoot}/${sectionKey}`, - text: sectionKey.startsWith('component-code') ? 'Review & Change' : 'Change', + text: sectionKey.startsWith('component-code') + ? 'Review & Change' + : 'Change', visuallyHiddenText: section.title }) return section } +/** + * 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) @@ -170,17 +194,18 @@ const getAnswersForSection = (sectionKey, sessionData = {}) => { } /** - * 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 sessionPages = Object.keys(session) const sections = getSections(sessionPages) Object.entries(sections).forEach(([key, section]) => { - section.answerRows = getAnswersForSection(key, session[`/${key}`]) + const sectionKey = key.split('/').at(0) + section.answerRows = getAnswersForSection(sectionKey, session[`/${key}`]) }) return Object.values(sections) From 4dbc975686fa5a21aecff11823a968dde15f38ec Mon Sep 17 00:00:00 2001 From: Chris Pymm Date: Mon, 23 Jun 2025 23:04:04 +0100 Subject: [PATCH 5/6] docs: handle empty sections and fix change/remove links --- config.js | 62 +++++++++---------- helpers/check-your-answers.js | 65 ++++++++++++-------- views/common/macros/summary-card.njk | 48 +++++++++++++++ views/community/pages/check-your-answers.njk | 29 ++++++--- views/community/pages/figma-link.njk | 8 --- 5 files changed, 136 insertions(+), 76 deletions(-) create mode 100644 views/common/macros/summary-card.njk diff --git a/config.js b/config.js index d6bd67c93..97dcb583d 100644 --- a/config.js +++ b/config.js @@ -135,37 +135,37 @@ const config = { removable: true, multiple: false }, - 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.' - } - }, - showOnCya: false, - removable: false, - multiple: false - }, - '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, - multiple: false - }, + // 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.' + // } + // }, + // showOnCya: false, + // removable: false, + // multiple: false + // }, + // '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, + // multiple: false + // }, 'component-code': { title: 'Component code', fields: { diff --git a/helpers/check-your-answers.js b/helpers/check-your-answers.js index 8992a0565..095876c46 100644 --- a/helpers/check-your-answers.js +++ b/helpers/check-your-answers.js @@ -14,6 +14,7 @@ const { } = require('./text-helper') const maxWords = 10000 +let currentSession /** * Converts a text label to a human-readable format using a predefined mapping. @@ -84,20 +85,23 @@ const shareYourDetailsValueReplacement = (value) => { * @param {array} sessionpages - keys for the pages present in the session * @returns {object} */ -const getSections = (sessionPages) => { +const getSections = () => { const sections = {} Object.entries(formPages).forEach(([section, config]) => { if (config.showOnCya) { - if (config.multiple) { - // gather all subpages - sessionPages - .filter((key) => key.startsWith(`/${section}`)) + // gather all pages + const pages = Object.keys(currentSession).filter((key) => + key.startsWith(`/${section}`) + ) + + if (pages.length > 0) { + pages .map((entry) => entry.slice(1)) // remove leading '/' .forEach((repeatSection, index) => { sections[repeatSection] = buildSection( repeatSection, config, - index + 1 + pages.length > 1 ? index + 1 : null ) }) } else { @@ -121,28 +125,40 @@ const buildSection = (sectionPath, sectionConfig, sectionNumber) => { const sectionKey = sectionPath.split('/').at(0) const section = { title: labelForSection(sectionKey, sectionNumber), - actions: [] + actions: [], + answerRows: getAnswersForSection( + sectionKey, + currentSession[`/${sectionPath}`] + ) } - if (sectionConfig.removable) { - section.actions.push({ - href: `${hrefRoot}/remove/${sectionKey}`, - text: 'Remove', - visuallyHiddenText: section.title - }) + if (sectionConfig.removable && section.answerRows.length > 0) { + section.actions.push(link(`remove/${sectionKey}`, 'Remove', section.title)) } - section.actions.push({ - href: `${hrefRoot}/${sectionKey}`, - text: sectionKey.startsWith('component-code') - ? 'Review & Change' - : 'Change', - visuallyHiddenText: section.title - }) + if (section.answerRows.length === 0 && sectionConfig.conditions) { + const href = Object.keys(sectionConfig.conditions).at(0) + + section.actions.push(link(href, 'Change', section.title)) + } else { + const linkText = sectionKey.startsWith('component-code') + ? 'Review & Change' + : 'Change' + + section.actions.push(link(sectionKey, linkText, section.title)) + } return section } +const link = (href, text, visuallyHiddenText) => { + return { + href: `${hrefRoot}${(href.startsWith('/') ? href : `/${href}`)}`, + text, + visuallyHiddenText + } +} + /** * Gets all the answers from the session for a section * @@ -200,13 +216,8 @@ const getAnswersForSection = (sectionKey, sessionData = {}) => { * @returns {Array} - Array of objects to build summary cards. */ const checkYourAnswers = (session) => { - const sessionPages = Object.keys(session) - const sections = getSections(sessionPages) - - Object.entries(sections).forEach(([key, section]) => { - const sectionKey = key.split('/').at(0) - section.answerRows = getAnswersForSection(sectionKey, session[`/${key}`]) - }) + currentSession = session + const sections = getSections() return Object.values(sections) } 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/community/pages/check-your-answers.njk b/views/community/pages/check-your-answers.njk index caed34a83..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,17 +9,25 @@

    Check your answers

    {% for section in sections %} - {{ govukSummaryList({ - card: { - title: { - text: section.title - }, - actions: { - items: section.actions - } + {% set cardParams = { + title: { + text: section.title }, - rows: section.answerRows - }) }} + actions: { + items: section.actions + } + } + %} + {% if section.answerRows.length > 0 %} + {{ govukSummaryList({ + 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 %} From 06a05f575eb95d1dfa98097321bd401d8fbd5cc4 Mon Sep 17 00:00:00 2001 From: Chris Pymm Date: Tue, 24 Jun 2025 10:00:55 +0100 Subject: [PATCH 6/6] docs: updates to the remove page --- config.js | 64 +++++-------------------- docs/stylesheets/components/_prose.scss | 8 +--- helpers/check-your-answers.js | 5 +- middleware/component-session.js | 8 ++-- routes/add-component.js | 9 ++-- views/community/pages/remove.njk | 4 +- 6 files changed, 25 insertions(+), 73 deletions(-) diff --git a/config.js b/config.js index 97dcb583d..d7905e904 100644 --- a/config.js +++ b/config.js @@ -32,8 +32,7 @@ const config = { } }, showOnCya: true, - removable: false, - multiple: false + removable: false }, 'component-image': { title: 'Component image', @@ -44,8 +43,7 @@ const config = { } }, showOnCya: true, - removable: false, - multiple: false + removable: false }, 'accessibility-findings': { title: 'Accessibility findings', @@ -65,8 +63,7 @@ const config = { } }, showOnCya: false, - removable: false, - multiple: false + removable: false }, 'add-external-audit': { title: 'External accessibility audit', @@ -88,8 +85,7 @@ const config = { } }, showOnCya: true, - removable: true, - multiple: false + removable: true }, 'add-internal-audit': { title: 'Internal accessibility review', @@ -111,8 +107,7 @@ const config = { } }, showOnCya: true, - removable: true, - multiple: false + removable: true }, 'add-assistive-tech': { title: 'Testing with assistive technology', @@ -132,40 +127,8 @@ const config = { } }, showOnCya: true, - removable: true, - multiple: false + removable: true }, - // 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.' - // } - // }, - // showOnCya: false, - // removable: false, - // multiple: false - // }, - // '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, - // multiple: false - // }, 'component-code': { title: 'Component code', fields: { @@ -174,8 +137,7 @@ const config = { } }, showOnCya: false, - removable: false, - multiple: false + removable: false }, 'component-code-details': { title: 'Component code details', @@ -200,8 +162,7 @@ const config = { '/component-code': { componentCodeAvailable: 'yes' } }, showOnCya: true, - removable: true, - multiple: true + removable: true }, figma: { title: 'Figma design', @@ -211,8 +172,7 @@ const config = { } }, showOnCya: false, - removable: false, - multiple: false + removable: false }, 'figma-link': { title: 'Figma design details', @@ -230,8 +190,7 @@ const config = { '/figma': { figmaUrl: 'yes' } }, showOnCya: true, - removable: true, - multiple: false + removable: true }, 'your-details': { title: 'Your details', @@ -251,8 +210,7 @@ const config = { } }, showOnCya: true, - removable: false, - multiple: false + removable: false }, 'check-your-answers': {} }, diff --git a/docs/stylesheets/components/_prose.scss b/docs/stylesheets/components/_prose.scss index e1205f169..8944a6a73 100644 --- a/docs/stylesheets/components/_prose.scss +++ b/docs/stylesheets/components/_prose.scss @@ -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/helpers/check-your-answers.js b/helpers/check-your-answers.js index 095876c46..1cc37ad3e 100644 --- a/helpers/check-your-answers.js +++ b/helpers/check-your-answers.js @@ -222,4 +222,7 @@ const checkYourAnswers = (session) => { return Object.values(sections) } -module.exports = checkYourAnswers +module.exports = { + checkYourAnswers, + getAnswersForSection +} 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/routes/add-component.js b/routes/add-component.js index d39f658ff..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') @@ -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/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 %}
    Submit a new component -

    Are you sure you want to remove {{ type }}?

    +

    Are you sure you want to remove this information?

    {{ govukSummaryList({ rows: summary @@ -22,7 +22,7 @@
    {{ govukButton({ - text: deleteText, + text: 'Delete answers', preventDoubleClick: true, classes: "govuk-button--warning" }) }}