diff --git a/app.js b/app.js index 7d1980a5c..13015579b 100644 --- a/app.js +++ b/app.js @@ -1,11 +1,22 @@ /* eslint import/order: "off" */ /* eslint n/no-unpublished-require: "off" */ const Sentry = require('@sentry/node') +const { + APP_PORT, + ENV, + REDIS_URL, + SESSION_SECRET, + SENTRY_DSN +} = require('./config') -Sentry.init({ - dsn: 'https://304866cea16570b04e2090537ae9ac77@o345774.ingest.us.sentry.io/4509252675371008', - sendDefaultPii: true -}) +const isDev = ENV === 'development' +if (!isDev) { + Sentry.init({ + dsn: SENTRY_DSN, + sendDefaultPii: false, + environment: ENV + }) +} const path = require('path') const redisClient = require('./helpers/redis-client') @@ -18,15 +29,12 @@ const RedisStore = require('connect-redis')(session) const helmet = require('helmet') const nunjucks = require('nunjucks') const { xss } = require('express-xss-sanitizer') -const ApplicationError = require('./helpers/application-error') const rev = require('./filters/rev') -const { APP_PORT, ENV, REDIS_URL, SESSION_SECRET } = require('./config') const addComponentRoutes = require('./routes/add-component') const app = express() -const isDev = ENV === 'development' if (!isDev) { // Only trust single proxy (Nginx) @@ -55,7 +63,7 @@ const sessionOptions = { secret: SESSION_SECRET, resave: true, saveUninitialized: true, - cookie: { secure: !isDev, maxAge: 24 * 60 * 60 * 1000 } + cookie: { secure: !isDev, maxAge: 24 * 60 * 60 * 1000 } } if (REDIS_URL) { @@ -87,7 +95,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) @@ -107,9 +115,10 @@ app.use(xss()) app.use('/contribute/add-new-component', addComponentRoutes) // Fallback route to 404 -app.get('*', (req, res, next) => { - const error = new ApplicationError('Page not found', 404) - next(error) +app.use((req, res) => { + res.status(404).render('404', { + title: 'Page not found' + }) }) // The error handler must be registered before any other error middleware and after all controllers diff --git a/config.js b/config.js index 4fb6ee709..9be15e7ff 100644 --- a/config.js +++ b/config.js @@ -5,19 +5,34 @@ const config = { GITHUB_REPO_NAME: process.env.GITHUB_REPO_NAME || 'your-default-repo-name', NOTIFY_PR_TEMPLATE: process.env.NOTIFY_PR_TEMPLATE || '', NOTIFY_SUBMISSION_TEMPLATE: process.env.NOTIFY_SUBMISSION_TEMPLATE || '', + NOTIFY_VERIFICATION_TEMPLATE: process.env.NOTIFY_VERIFICATION_TEMPLATE || '', NOTIFY_EMAIL: process.env.NOTIFY_EMAIL || 'your-email', NOTIFY_TOKEN: process.env.NOTIFY_TOKEN || 'your-default-repo-token', NOTIFY_EMAIL_RETRY_MS: parseInt(process.env.NOTIFY_EMAIL_RETRY_MS, 10) || 5000, NOTIFY_EMAIL_MAX_RETRIES: parseInt(process.env.NOTIFY_EMAIL_MAX_RETRIES, 10) || 5, + APP_URL: process.env.APP_URL, APP_PORT: parseInt(process.env.APP_PORT, 10) || 3001, REDIS_URL: process.env.REDIS_URL, REDIS_AUTH_TOKEN: process.env.REDIS_AUTH_TOKEN, REDIS_PORT: parseInt(process.env.REDIS_PORT, 10) || 6379, SESSION_SECRET: process.env.SESSION_SECRET || 'your-secret-key', ENV: process.env.ENV || 'development', + SENTRY_DSN: process.env.SENTRY_DSN, + ALLOWED_EMAIL_DOMAINS: ['justice.gov.uk'], COMPONENT_FORM_PAGES: { + 'email': { + title: 'Enter your email address', + fields: { + emailAddress: { + label: 'Email address', + hint: 'Enter an email address ending @justice.gov.uk' + } + }, + showOnCya: false, + removable: false + }, 'component-details': { title: 'Component details', fields: { @@ -231,6 +246,35 @@ const config = { SHARE_YOUR_DETAILS: { addNameToComponentPage: 'add my name to the component page', addTeamNameToComponentPage: 'add my team name to the component page' + }, + MESSAGES: { + emailVerificationExpired: { + type: 'error', + title: 'Email verification link expired', + text: `Enter your email address below to receive a new verification link.` + }, + emailVerificationSuccess: { + type: 'success', + title: 'Your email address has been verified', + text: 'You can now submit a new component' + }, + emailVerificationInvalidToken: { + type: 'error', + title: 'Email verification link invalid', + text: `Enter your email address below to receive a new verification link.` + }, + componentImageUploaded: (filename) => { + return { + type: 'success', + text: `File ‘${filename}’ has been uploaded.` + } + }, + componentImageRemoved: (filename) => { + return { + type: 'success', + message: `File ‘${filename}’ has been removed.` + } + } } } diff --git a/docs/_data/statusInfo.json b/docs/_data/statusInfo.json index 705bbfd68..1c6bf8d23 100644 --- a/docs/_data/statusInfo.json +++ b/docs/_data/statusInfo.json @@ -12,7 +12,7 @@ "Experimental": { "link": "How to use ‘experimental’ {{ types }}", "linkContent": "Anyone can add an ‘experimental’ status {{ type }} to the MoJ Design System. They’re early in development and can be used as a starting point.", - "statusMessage": "Created" + "statusMessage": "Added" }, "Archived": { "link": "About ‘archived’ {{ types }}", diff --git a/docs/_includes/layouts/base.njk b/docs/_includes/layouts/base.njk index 939032e99..59fe855f6 100644 --- a/docs/_includes/layouts/base.njk +++ b/docs/_includes/layouts/base.njk @@ -40,7 +40,7 @@
{% block beforeContent %}{% endblock %} -
+
{% block content %} {{ content | safe }} {% endblock %} diff --git a/docs/_includes/layouts/partials/building-block-header.njk b/docs/_includes/layouts/partials/building-block-header.njk index 941eac92a..24a29982a 100644 --- a/docs/_includes/layouts/partials/building-block-header.njk +++ b/docs/_includes/layouts/partials/building-block-header.njk @@ -11,8 +11,8 @@ status, statusInfo[status], statusDate, - creatorName, - creatorTeam + contributorName, + contributorTeam ) if statusInfo[status] }}
diff --git a/docs/_includes/layouts/partials/page-header.njk b/docs/_includes/layouts/partials/page-header.njk index 7aa9a6427..96a6cb9ea 100644 --- a/docs/_includes/layouts/partials/page-header.njk +++ b/docs/_includes/layouts/partials/page-header.njk @@ -16,16 +16,16 @@ text: title, caption: subsection, statusTag: { text: status } if status - }) }} + }) }} - {{ statusLabel( - type, - status, - statusInfo[status], - statusDate, - creatorName, - creatorTeam - ) if statusInfo[status] }} + {{ statusLabel({ + type: type, + status: status, + statusData: statusInfo[status], + statusDate: statusDate, + contributorName: contributorName, + contributorTeam: contributorTeam + }) if statusInfo[status] }} {% if lede %} {{ lede | safe }} diff --git a/docs/_includes/macros/status-label/macro.njk b/docs/_includes/macros/status-label/macro.njk index 009397720..fe403603c 100644 --- a/docs/_includes/macros/status-label/macro.njk +++ b/docs/_includes/macros/status-label/macro.njk @@ -1,3 +1,3 @@ -{% macro statusLabel(type, status, statusData, statusDate, creatorName, creatorTeam) %} - {%- include "./template.njk" -%} +{% macro statusLabel(params) %} + {% include "./template.njk" %} {% endmacro %} diff --git a/docs/_includes/macros/status-label/template.njk b/docs/_includes/macros/status-label/template.njk index 75e19d3c3..a291a138f 100644 --- a/docs/_includes/macros/status-label/template.njk +++ b/docs/_includes/macros/status-label/template.njk @@ -1,36 +1,30 @@ {% from "govuk/components/details/macro.njk" import govukDetails %} - - {% set statusAttribution -%} - {%- if status == "Experimental" %} - by {{ creatorName | default("someone") }} in {{ creatorTeam | default("the community") }} - {%- endif %} - {%- if statusDate %} - in {{ statusDate }} + {%- if params.status == "Experimental" %} + {%- if params.contributorTeam %} + by {{ params.contributorName | default("someone") }} in {{ params.contributorTeam }} + {%- elif params.contributorName %} + by {{ contributorName }} + {% endif %} + {% endif %} + {%- if params.statusDate %} + in {{ params.statusDate }} {%- endif %}. {%- endset %} -{% set typeSingular = type -%} -{% set typePlural = type + "s" -%} +{% set typeSingular = params.type -%} +{% set typePlural = params.type + "s" -%} {% set linkText -%} - {{ statusData.link | renderString({ + {{ params.statusData.link | renderString({ types: typePlural, type: typeSingular }) | upperFirst }} {%- endset %} {% set linkHtml -%} - {{ statusData.linkContent | renderString({ + {{ params.statusData.linkContent | renderString({ types: typePlural, type: typeSingular }) | upperFirst }} @@ -38,7 +32,7 @@ Arguments to be passed: {%- endset %}

- {{ statusData.statusMessage }} {{ statusAttribution }} + {{ params.statusData.statusMessage }} {{ statusAttribution }}

{{ govukDetails({ diff --git a/docs/stylesheets/components/_layout.scss b/docs/stylesheets/components/_layout.scss index cb3869b0b..dbf760206 100644 --- a/docs/stylesheets/components/_layout.scss +++ b/docs/stylesheets/components/_layout.scss @@ -40,6 +40,19 @@ @include govuk-responsive-padding(6); @include govuk-media-query(app.$breakpoint) { - @include govuk-responsive-padding(9); + @include govuk-responsive-padding(9, 'top'); + @include govuk-responsive-padding(9, 'right'); + @include govuk-responsive-padding(9, 'left'); + } +} + +.app-layout__back-link { + position: absolute; + @include govuk-responsive-padding(6, 'right'); + @include govuk-responsive-padding(6, 'left'); + + @include govuk-media-query(app.$breakpoint) { + @include govuk-responsive-padding(9, 'right'); + @include govuk-responsive-padding(9, 'left'); } } diff --git a/helpers/mockSessionData/sessionData.js b/helpers/mockSessionData/sessionData.js index 28f54acd0..c0f04eb1a 100644 --- a/helpers/mockSessionData/sessionData.js +++ b/helpers/mockSessionData/sessionData.js @@ -2,9 +2,9 @@ module.exports = { '/component-details': { componentName: 'Duis', componentOverview: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam porttitor, turpis eu congue semper, felis purus blandit purus, eu finibus dui orci et augue. Vestibulum nec dignissim ante. Sed vehicula sagittis nunc, sed iaculis lorem. Quisque quis lorem non lorem ornare venenatis. Morbi luctus, enim et tincidunt pellentesque, ante justo venenatis nisl, rhoncus lobortis quam nunc in lectus. Nam consectetur sapien sem, quis laoreet mi dapibus sit amet. Nullam consectetur erat ut diam luctus posuere. Duis vulputate turpis vitae magna commodo pharetra. Sed varius pulvinar sapien, nec tempus ante pulvinar sit amet. Sed mauris tortor, dictum tristique aliquam vestibulum, porta et justo.\r\n\r\n', + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.\r\n\r\nAliquam porttitor, turpis eu congue semper, felis purus blandit purus, eu finibus dui orci et augue. Vestibulum nec dignissim ante.\r\n\r\n Sed vehicula sagittis nunc, sed iaculis lorem. Quisque quis lorem non lorem ornare venenatis. Morbi luctus, enim et tincidunt pellentesque, ante justo venenatis nisl, rhoncus lobortis quam nunc in lectus. Nam consectetur sapien sem, quis laoreet mi dapibus sit amet. Nullam consectetur erat ut diam luctus posuere. Duis vulputate turpis vitae magna commodo pharetra. Sed varius pulvinar sapien, nec tempus ante pulvinar sit amet. Sed mauris tortor, dictum tristique aliquam vestibulum, porta et justo.\r\n\r\n', howIsTheComponentUsed: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam porttitor, turpis eu congue semper, felis purus blandit purus, eu finibus dui orci et augue. Vestibulum nec dignissim ante. Sed vehicula sagittis nunc, sed iaculis lorem. Quisque quis lorem non lorem ornare venenatis. Morbi luctus, enim et tincidunt pellentesque, ante justo venenatis nisl, rhoncus lobortis quam nunc in lectus. Nam consectetur sapien sem, quis laoreet mi dapibus sit amet. Nullam consectetur erat ut diam luctus posuere. Duis vulputate turpis vitae magna commodo pharetra. Sed varius pulvinar sapien, nec tempus ante pulvinar sit amet. Sed mauris tortor, dictum tristique aliquam vestibulum, porta et justo.\r\n\r\n' + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam porttitor, turpis eu congue semper, felis purus blandit purus, eu finibus dui orci et augue.\r\n\r\nVestibulum nec dignissim ante. Sed vehicula sagittis nunc, sed iaculis lorem. Quisque quis lorem non lorem ornare venenatis. Morbi luctus, enim et tincidunt pellentesque, ante justo venenatis nisl, rhoncus lobortis quam nunc in lectus. Nam consectetur sapien sem, quis laoreet mi dapibus sit amet. Nullam consectetur erat ut diam luctus posuere. Duis vulputate turpis vitae magna commodo pharetra. Sed varius pulvinar sapien, nec tempus ante pulvinar sit amet. Sed mauris tortor, dictum tristique aliquam vestibulum, porta et justo.\r\n\r\n' }, '/component-image': {}, '/accessibility-findings': { @@ -18,7 +18,7 @@ module.exports = { 'auditDate-month': '1', 'auditDate-year': '2020', issuesDiscovered: - 'In lacus ipsum, molestie nec sapien vitae, tincidunt tristique nisi. Integer a lacus quis nisl mollis fringilla. Nullam blandit imperdiet mauris, ac feugiat ante. Vestibulum nec semper nulla, ut ultricies massa. Curabitur lacinia tortor augue, sed varius leo viverra non. Fusce vitae libero ac orci elementum vestibulum eu et libero. In rhoncus laoreet nisi, sit amet rutrum tellus. Ut ut dui in metus sodales molestie eu ut purus.\r\n\r\n' + 'In lacus ipsum, molestie nec sapien vitae, tincidunt tristique nisi.\r\n\r\n Integer a lacus quis nisl mollis fringilla. Nullam blandit imperdiet mauris, ac feugiat ante.\r\n\r\n* lklsdkfjl\r\n* sdflkjsdflkjsdflk sdflkjlk lsdfkjlksdfj\r\n\r\nVestibulum nec semper nulla, ut ultricies massa. Curabitur lacinia tortor augue, sed varius leo viverra non. Fusce vitae libero ac orci elementum vestibulum eu et libero.\r\n\r\n In rhoncus laoreet nisi, sit amet rutrum tellus. Ut ut dui in metus sodales molestie eu ut purus.\r\n\r\n' }, '/add-internal-audit': { internalOrganisation: 'Curabitur', @@ -26,7 +26,7 @@ module.exports = { 'auditDate-month': '1', 'auditDate-year': '2020', issuesDiscovered: - 'In lacus ipsum, molestie nec sapien vitae, tincidunt tristique nisi. Integer a lacus quis nisl mollis fringilla. Nullam blandit imperdiet mauris, ac feugiat ante. Vestibulum nec semper nulla, ut ultricies massa. Curabitur lacinia tortor augue, sed varius leo viverra non. Fusce vitae libero ac orci elementum vestibulum eu et libero. In rhoncus laoreet nisi, sit amet rutrum tellus. Ut ut dui in metus sodales molestie eu ut purus.\r\n\r\n' + 'In lacus ipsum, molestie nec sapien vitae, tincidunt tristique nisi. Integer a lacus quis nisl mollis fringilla\r\n\r\nNullam blandit imperdiet mauris, ac feugiat ante. Vestibulum nec semper nulla, ut ultricies massa. Curabitur lacinia tortor augue, sed varius leo viverra non. Fusce vitae libero ac orci elementum vestibulum eu et libero. In rhoncus laoreet nisi, sit amet rutrum tellus. Ut ut dui in metus sodales molestie eu ut purus.\r\n\r\n' }, '/add-assistive-tech': { 'testingDate-day': '1', @@ -49,11 +49,21 @@ module.exports = { componentCodeAvailable: 'yes' }, '/component-code-details': { - componentCodeLanguage: 'other', - componentCodeLanguageOther: 'Nunjucks', + componentCodeLanguage: 'nunjucks', componentCodeUsage: 'In lacus ipsum, molestie nec sapien vitae, tincidunt tristique nisi. Integer a lacus quis nisl mollis fringilla. Nullam blandit imperdiet mauris, ac feugiat ante. Vestibulum nec semper nulla, ut ultricies massa. Curabitur lacinia tortor augue, sed varius leo viverra non. Fusce vitae libero ac orci elementum vestibulum eu et libero. In rhoncus laoreet nisi, sit amet rutrum tellus. Ut ut dui in metus sodales molestie eu ut purus.\r\n\r\n', - componentCode: '

test

' + componentCode: `{% from "govuk/components/notification-banner/macro.njk" import govukNotificationBanner %} + +{% set html %} +

+ You have 7 days left to send your application. + View application. +

+{% endset %} + +{{ govukNotificationBanner({ + html: html +}) }}` }, '/your-details': { fullName: 'test test', @@ -70,8 +80,219 @@ module.exports = { figmaLinkAdditionalInformation: 'only link' }, '/component-code-details/1': { + componentCodeLanguage: 'html', + componentCodeUsage: 'Some HTML', + componentCode: `
+
+

+ Important +

+
+
+

+ You have 7 days left to send your application. + View application. +

+
+
` + }, + '/component-code-details/2': { componentCodeLanguage: 'css', componentCodeUsage: 'Some CSS', - componentCode: 'style' + componentCode: `.govuk-notification-banner { + font-family: GDS Transport,arial,sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 400; + font-size: 1rem; + line-height: 1.25; + margin-bottom: 30px; + border: 5px solid #1d70b8; + background-color: #1d70b8 +} + +@media print { + .govuk-notification-banner { + font-family: sans-serif + } +} + +@media (min-width: 40.0625em) { + .govuk-notification-banner { + font-size:1.1875rem; + line-height: 1.3157894737 + } +} + +@media print { + .govuk-notification-banner { + font-size: 14pt; + line-height: 1.15 + } +} + +@media (min-width: 40.0625em) { + .govuk-notification-banner { + margin-bottom:50px + } +} + +.govuk-notification-banner:focus { + outline: 3px solid #fd0 +} + +.govuk-notification-banner__header { + padding: 2px 15px 5px; + border-bottom: 1px solid transparent +} + +@media (min-width: 40.0625em) { + .govuk-notification-banner__header { + padding:2px 20px 5px + } +} + +.govuk-notification-banner__title { + font-size: 1rem; + line-height: 1.25; + font-weight: 700; + margin: 0; + padding: 0; + color: #fff +} + +@media (min-width: 40.0625em) { + .govuk-notification-banner__title { + font-size:1.1875rem; + line-height: 1.3157894737 + } +} + +@media print { + .govuk-notification-banner__title { + font-size: 14pt; + line-height: 1.15 + } +} + +.govuk-notification-banner__content { + color: #0b0c0c; + padding: 15px; + background-color: #fff +} + +@media print { + .govuk-notification-banner__content { + color: #000 + } +} + +@media (min-width: 40.0625em) { + .govuk-notification-banner__content { + padding:20px + } +} + +.govuk-notification-banner__content>* { + box-sizing: border-box; + max-width: 605px +} + +.govuk-notification-banner__content>:last-child { + margin-bottom: 0 +} + +.govuk-notification-banner__heading { + font-size: 1.125rem; + line-height: 1.1111111111; + font-weight: 700; + margin: 0 0 15px; + padding: 0 +} + +@media (min-width: 40.0625em) { + .govuk-notification-banner__heading { + font-size:1.5rem; + line-height: 1.25 + } +} + +@media print { + .govuk-notification-banner__heading { + font-size: 18pt; + line-height: 1.15 + } +} + +.govuk-notification-banner__link { + font-family: GDS Transport,arial,sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-decoration: underline; + text-decoration-thickness: max(1px,.0625rem); + text-underline-offset: .1578em +} + +@media print { + .govuk-notification-banner__link { + font-family: sans-serif + } +} + +.govuk-notification-banner__link:hover { + text-decoration-thickness: max(3px,.1875rem,.12em); + -webkit-text-decoration-skip-ink: none; + text-decoration-skip-ink: none; + -webkit-text-decoration-skip: none; + text-decoration-skip: none +} + +.govuk-notification-banner__link:focus { + outline: 3px solid transparent; + background-color: #fd0; + box-shadow: 0 -2px #fd0,0 4px #0b0c0c; + text-decoration: none +} + +@supports not (text-wrap: balance) { + .govuk-notification-banner__link:focus { + -webkit-box-decoration-break:clone; + box-decoration-break: clone + } +} + +.govuk-notification-banner__link:link,.govuk-notification-banner__link:visited { + color: #1d70b8 +} + +.govuk-notification-banner__link:hover { + color: #003078 +} + +.govuk-notification-banner__link:active,.govuk-notification-banner__link:focus { + color: #0b0c0c +} + +.govuk-notification-banner--success { + border-color: #00703c; + background-color: #00703c +} + +.govuk-notification-banner--success .govuk-notification-banner__link:link,.govuk-notification-banner--success .govuk-notification-banner__link:visited { + color: #00703c +} + +.govuk-notification-banner--success .govuk-notification-banner__link:hover { + color: #004e2a +} + +.govuk-notification-banner--success .govuk-notification-banner__link:active { + color: #00703c +} + +.govuk-notification-banner--success .govuk-notification-banner__link:focus { + color: #0b0c0c +}` } + } diff --git a/kubernetes-deploy-preview.tpl b/kubernetes-deploy-preview.tpl index df0a8637f..11df5a9fd 100644 --- a/kubernetes-deploy-preview.tpl +++ b/kubernetes-deploy-preview.tpl @@ -63,6 +63,11 @@ spec: secretKeyRef: name: notify-submission-template key: notify-submission-template + - name: NOTIFY_VERIFICATION_TEMPLATE + valueFrom: + secretKeyRef: + name: notify-verification-template + key: notify-verification-template - name: NOTIFY_EMAIL valueFrom: secretKeyRef: @@ -83,8 +88,15 @@ spec: secretKeyRef: name: notify-email-max-retries key: notify-email-max-retries + - name: SENTRY_DSN + valueFrom: + secretKeyRef: + name: sentry-dsn + key: sentry-dsn - name: BRANCH value: ${BRANCH} + - name: APP_URL + value: https://moj-frontend-${BRANCH}.apps.live.cloud-platform.service.justice.gov.uk - name: REDIS_URL valueFrom: secretKeyRef: @@ -150,4 +162,4 @@ spec: service: name: moj-frontend-service-${BRANCH} port: - number: 3000 \ No newline at end of file + number: 3000 diff --git a/kubernetes-deploy-production.tpl b/kubernetes-deploy-production.tpl index 153f475b4..1ae24ee3b 100644 --- a/kubernetes-deploy-production.tpl +++ b/kubernetes-deploy-production.tpl @@ -50,6 +50,11 @@ spec: secretKeyRef: name: notify-submission-template key: notify-submission-template + - name: NOTIFY_VERIFICATION_TEMPLATE + valueFrom: + secretKeyRef: + name: notify-verification-template + key: notify-verification-template - name: NOTIFY_EMAIL valueFrom: secretKeyRef: @@ -70,9 +75,15 @@ spec: secretKeyRef: name: notify-email-max-retries key: notify-email-max-retries + - name: SENTRY_DSN + valueFrom: + secretKeyRef: + name: sentry-dsn + key: sentry-dsn - name: BRANCH value: ${BRANCH} - - name: REDIS_URL + - name: APP_URL + value: https://design-patterns.service.justice.gov.uk - name: REDIS_URL valueFrom: secretKeyRef: name: moj-frontend-ec-cluster-output diff --git a/middleware/component-session.js b/middleware/component-session.js index 8ba125a2d..a4225dad9 100644 --- a/middleware/component-session.js +++ b/middleware/component-session.js @@ -5,7 +5,9 @@ const sanitize = require('sanitize-filename') const { MAX_ADD_ANOTHER: maxAddAnother, ADD_NEW_COMPONENT_ROUTE, - COMPONENT_FORM_PAGES: formPages + ALLOWED_EMAIL_DOMAINS: allowedDomains, + COMPONENT_FORM_PAGES: formPages, + MESSAGES } = require('../config') const { getAnswersForSection } = require('../helpers/check-your-answers') const ApplicationError = require('../helpers/application-error') @@ -38,6 +40,22 @@ const getPageData = (req) => { return formPages[pageData] } +const checkEmailDomain = (req, res, next) => { + let allowed = false + const email = req?.body?.emailAddress + console.log({ email }) + if (email) { + const domain = email.split('@').at(-1) + console.log({ domain }) + if (allowedDomains.includes(domain)) { + allowed = true + } + } + console.log({ allowed }) + req.emailDomainAllowed = allowed + next() +} + const transformErrorsToErrorList = (errors) => { return errors.map((error) => ({ text: error.message, @@ -45,6 +63,12 @@ const transformErrorsToErrorList = (errors) => { })) } +const setCurrentFormPages = (req, res, next) => { + const { url, session } = req + req.formPages = getCurrentFormPages(url, session) + next() +} + const setNextPage = (req, res, next) => { console.log('setting nextPage') const amendingAnswers = req?.session?.checkYourAnswers @@ -80,9 +104,15 @@ const clearSkippedPageData = (req, res, next) => { // Delete page and subpage data for (const sessionPage of Object.keys(req.session)) { if ( - !['started', 'cookie', 'csrfToken', 'checkYourAnswers'].includes( - sessionPage - ) + ![ + 'started', + 'cookie', + 'csrfToken', + 'checkYourAnswers', + 'emailToken', + 'emailDomainAllowed', + 'verified' + ].includes(sessionPage) ) { console.log(sessionPage) const parentPage = `/${sessionPage.split('/')[1]}` @@ -138,7 +168,7 @@ const validateFormDataFileUpload = (err, req, res, next) => { const validateFormData = (req, res, next) => { console.log('validate form data') - const schemaName = req.url.split('/')[1] + const schemaName = req.url.split('/').at(1).split('?').at(0) const schema = require(`../schema/${schemaName}.schema`) const body = extractBody(req?.url, { ...req.body }) delete body._csrf @@ -190,6 +220,7 @@ const validateFormData = (req, res, next) => { } const saveSession = (req, res, next) => { + console.log('saving session') if (!req.session) req.session = {} const { _csrf, ...body } = req.body @@ -298,10 +329,7 @@ const removeFromSession = (req, res, next) => { const filename = req.session[url]?.componentImage?.originalname console.log(filename) if (filename) { - req.session.sessionFlash = { - type: 'success', - message: `File ‘${filename}’ has been removed.` - } + req.session.sessionFlash = MESSAGES.componentImageRemoved(filename) } } @@ -335,6 +363,14 @@ const sessionStarted = (req, res, next) => { next() } +const sessionVerified = (req, res, next) => { + if (!req?.session?.verified) { + console.error('No verified email for session') + return res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/start`) + } + next() +} + const validateComponentImagePage = (req, res, next) => { if (req.params.page !== 'component-image') { const error = new ApplicationError('Invalid page', 400) @@ -376,8 +412,40 @@ const saveFileToRedis = async (req, res, next) => { next() } +const validatePageParams = (req, res, next) => { + const validPages = Object.keys(formPages) + let valid = true + + if (req.params.page) { + // if page is present it must be in allowlist of configured pages + valid = validPages.includes(req.params.page) + } + + if (req.params.subpage) { + // if subpage is present it must always be a number + valid = /^\d+$/.test(req.params.subpage) + } + + if (valid) { + next() + } else { + const error = new ApplicationError('Page not found', 404) + next(error) + } +} + +const setCsrfToken = (req, res, next) => { + if (req?.session) { + if (!req?.session?.csrfToken) { + req.session.csrfToken = crypto.randomBytes(32).toString('hex') + } + } + next() +} + module.exports = { setNextPage, + setCurrentFormPages, validateFormData, saveSession, getFormDataFromSession, @@ -387,8 +455,12 @@ module.exports = { getFormSummaryListForRemove, removeFromSession, sessionStarted, + sessionVerified, validateFormDataFileUpload, validateComponentImagePage, saveFileToRedis, - clearSkippedPageData + clearSkippedPageData, + checkEmailDomain, + validatePageParams, + setCsrfToken } diff --git a/middleware/generate-documentation.js b/middleware/generate-documentation.js index 1fc62a78b..697267210 100644 --- a/middleware/generate-documentation.js +++ b/middleware/generate-documentation.js @@ -1,110 +1,100 @@ /* eslint-disable prefer-template */ const moment = require('moment') +const { MAX_ADD_ANOTHER } = require('../config.js') +const { ucFirst } = require('../helpers/text-helper.js') const generateMarkdown = (data, files) => { + console.log(data) const { '/component-details': details } = data - const documentationDirectory = 'docs/components' const componentName = details?.componentName || 'unknown-component' - const sanitizedComponentName = componentName - .toLowerCase() - .replace(/[^a-z0-9-]/g, '-') - .replace(/-+/g, '-') - const filename = `${documentationDirectory}/${sanitizedComponentName}.md` - const generateLinksSection = (data) => { - const noLinks = - 'No prototypes or design files have been added for this component. If you have used this component in your service and you have a prototype you can share it here.\n' + const sanitizedComponentName = ucFirst( + componentName + .toLowerCase() + .replace(/[^a-z0-9-]/g, '-') + .replace(/-+/g, '-') + ) + const filename = `${sanitizedComponentName}.md` + + const githubDiscussionLink = (componentName = '') => { + return `[${componentName ? `${componentName} ` : ''}Github discussion]({{ githuburl }})` + } + + const generateDesignsTabContent = (data) => { let content = '' - let prototypeContent = '' - let figmaContent = '' - let n = 0 - // Process prototype data - while (data[`/prototype-url${n > 0 ? `/${n}` : ''}`]) { - const prototype = data[`/prototype-url${n > 0 ? `/${n}` : ''}`] - prototypeContent += ` -${prototype?.prototypeUrlAdditionalInformation || ''} + if (data['/figma-link']) { + content += `A Figma link has been added for this component. There may be more links and resources in the ${githubDiscussionLink(sanitizedComponentName)}.\r\n -Prototype link (opens in a new tab) -` - n++ - } +### Figma link - // Process figma data - n = 0 - while (data[`/figma-link${n > 0 ? `/${n}` : ''}`]) { - const figma = data[`/figma-link${n > 0 ? `/${n}` : ''}`] - figmaContent += ` -${figma?.figmaLinkAdditionalInformation || ''} + [View the ${sanitizedComponentName} in Figma (opens in a new tab)](${data['/figma-link']?.figmaUrl || ''})\r\n\r\n` -Figma link (opens in a new tab) -` - n++ - } + content += `${data['/figma-link']?.figmaLinkAdditionalInformation || ''}\r\n` - if (prototypeContent.length) { - content += '### Prototype\n' + prototypeContent - } - if (figmaContent.length) { - content += '\n### Figma\n' + figmaContent + content += `### Contribute prototypes and Figma links + + If you have design files that are relevant to this component you can add them to the ${githubDiscussionLink()}. This helps other people to use it in their service.` + } else { + content = `A Figma link was not included when this component was added. + + There may be more information in the ${githubDiscussionLink(sanitizedComponentName)}. You can also view the component image in the overview. + +## Contribute a Figma link + + If you have a Figma link for this component (or a component like it) you can add it to ${githubDiscussionLink()}. This helps other people to use it in their service.` } - return content.length ? content : noLinks + return content } - const generateAccessibilityReportSection = (data, files) => { - const contentHeading = - 'If you have had an accessibility audit or tested with users with access needs then you could contribute to this component.\n' - const noContent = - 'No accessibility findings have been added for this component.\n' - let content = '' - const externalAudit = data['/add-external-audit'] - const externalAuditFile = files?.['/add-external-audit'] - const internalAudit = data['/add-internal-audit'] - const internalAuditFile = files?.['/add-internal-audit'] - const assistiveTech = data['/add-assistive-tech'] - const assistiveTechReport = files?.['/add-assistive-tech'] - + const generateAccessibilityTabContent = (data) => { const formatDate = (day, month, year) => { if (day && month && year) { - return moment(`${year}-${month}-${day}`, 'YYYY-M-D').format('MMMM YYYY') + return moment(`${year}-${month}-${day}`, 'YYYY-M-D').format( + 'D MMMM YYYY' + ) } return null } - if (externalAudit && externalAudit.externalOrganisation) { - const auditDate = formatDate( + let content = '' + const externalAudit = data['/add-external-audit'] + const internalAudit = data['/add-internal-audit'] + const assistiveTech = data['/add-assistive-tech'] + + if (externalAudit) { + const externalAuditDate = formatDate( externalAudit['auditDate-day'], externalAudit['auditDate-month'], externalAudit['auditDate-year'] ) - if (auditDate) { - content += `### External audit (${externalAudit.externalOrganisation}) - ${auditDate}\n` - if (externalAudit.issuesDiscovered) { - content += externalAudit.issuesDiscovered + '\n\n' - } - if (externalAudit.accessibilityReport && externalAuditFile) { - content += `[Download the accessibility report](/${externalAuditFile.path})\n` - } - } + + content += `### External audit + +* Conducted by: ${externalAudit.externalOrganisation} +* Date: ${externalAuditDate} + +#### Audit findings + +${externalAudit.issuesDiscovered}\r\n` } - if (internalAudit && internalAudit.internalOrganisation) { - const auditDate = formatDate( + if (internalAudit) { + const internalAuditDate = formatDate( internalAudit['auditDate-day'], internalAudit['auditDate-month'], internalAudit['auditDate-year'] ) - if (auditDate) { - content += `### Internal audit (${internalAudit.internalOrganisation}) - ${auditDate}\n` - if (internalAudit.issuesDiscovered) { - content += internalAudit.issuesDiscovered + '\n\n' - } - if (internalAudit.accessibilityReport && internalAuditFile) { - content += `[Download the accessibility report](/${internalAuditFile.path})\n` - } - } + content += `### Internal review + +* By: ${internalAudit.internalOrganisation} +* Date: ${internalAuditDate} + +#### Review findings + +${internalAudit.issuesDiscovered}\r\n` } if (assistiveTech) { @@ -113,94 +103,130 @@ ${figma?.figmaLinkAdditionalInformation || ''} assistiveTech['testingDate-month'], assistiveTech['testingDate-year'] ) - if (testingDate) { - content += `### Assistive Technology audit - ${testingDate}\n` - if (assistiveTech.issuesDiscovered) { - content += assistiveTech.issuesDiscovered + '\n\n' - } - if (assistiveTech.accessibilityReport && assistiveTechReport) { - content += `[Download the accessibility report](/${assistiveTechReport.path})\n` - } - } + content += `### Assistive Technology testing + +Date: ${testingDate} + +#### Testing details + +${assistiveTech.issuesDiscovered}\r\n` } - return content ? contentHeading + content : noContent - } + if (externalAudit || internalAudit || assistiveTech) { + content = `Accessibility findings have been added for this component. There may be more findings in the ${githubDiscussionLink(sanitizedComponentName)}.\r\n + +${content}\r\n` + } else { + content = `No accessibility findings were included when this component was added. There may be more information in the ${githubDiscussionLink(sanitizedComponentName)}.\r\n` + } + + content += `## Contribute accessibility findings - const generateComponentCodeSection = (data) => { - const noCode = - 'No code has been added for this component. If you have examples of how you have used this component in your service then you could help the community. Most users are looking for HTML, Nunjucks, Javascript and CSS or SASS.\n' + If you have accessibility findings that are relevant to this component you can add them to the ${githubDiscussionLink()}. This helps other people to use it in their service.` + return content + } + + const generateCodeTabContent = (data) => { + const hljsLang = (lang) => { + switch(lang.toLowerCase()) { + case 'html': + case 'css': + case 'scss': + case 'javascript': + case 'typescript': + return lang + case 'nunjucks': + return 'njk' + case 'sass': + return 'scss' + case 'react': + return 'javascript' + default: + return '' + } + } let content = '' - let n = 1 - while (data[`/component-code${n > 1 ? `-${n}` : ''}`]) { - const componentCodeDetails = - data[`/component-code-details${n > 1 ? `-${n}` : ''}`] + for(let i=0;i<=MAX_ADD_ANOTHER;i++) { + const code = data[`/component-code-details${i === 0 ? '' : `/${i}`}`] + if(!code) { + break + } + const language = + code?.componentCodeLanguage === 'other' + ? code?.componentCodeLanguageOther + : code?.componentCodeLanguage + + content += ` +### Example ${i + 1}: ${language} -### ${componentCodeDetails?.componentCodeLanguage || ''} +\`\`\`${hljsLang(language)} +{% raw %} +${code?.componentCode || ''} +{% endraw %} +\`\`\` -### ${componentCodeDetails?.componentCodeLanguageOther || ''} +${code?.componentCodeUsage || ''}\r\n\r\n` + } -
+ if (data['/component-code-details']) { + content = `Code has been added for this component. There may be other code examples in the ${githubDiscussionLink(sanitizedComponentName)}. -\`\`\`html -${componentCodeDetails?.componentCode || ''} -\`\`\` +${content} -
-${componentCodeDetails?.componentCodeUsage || ''} -` +## Contribute code for this component - n++ +If you have code that is relevant to this component you can add it to the ${githubDiscussionLink()}. This helps other people to use it in their service.` + } else { + content = `No code was included when this contribution was added. + +You can use the ${githubDiscussionLink(sanitizedComponentName)} to: + +* view other code examples +* add relevant code` } - return content.length ? content : noCode + + console.log(content) + return content } - const content = `--- -layout: layouts/tabbed-component.njk -title: ${componentName} -type: component +const content = `--- +title: ${ucFirst(componentName)} +tabs: true status: Experimental statusDate: ${moment().format('MMMM YYYY')} -eleventyNavigation: - key: ${componentName} - parent: Components - excerpt: "${details?.briefDescription || ''}" +excerpt: "${details?.briefDescription || ''}" +lede: "${details?.briefDescription || ''}" +githuburl: https://github.com/ministryofjustice/moj-frontend/discussions/xxx +${data['/your-details']?.fullName === 'Not shared' ? '' : `contributorName: ${data['/your-details']?.fullName}`} +${data['/your-details']?.teamName === 'Not shared' ? '' : `contributorTeam: ${data['/your-details']?.teamName}`} --- -{% tabs "Contents" %} - +{% tabs "paginate" %} {% tab "Overview" %} -## Overview -
- ${files?.['/component-image'] ? '![' + componentName + '](/' + files['/component-image'].path + ')' : ''} + ${componentName}
+## Overview ${details?.componentOverview || ''} -## How the component is currently used +### How the component is currently used ${details?.howIsTheComponentUsed || ''} -{% endtab %} - -{% tab "Code" %} - -## Help develop existing building blocks in GitHub - -After a new building block is published in the design system, you, and other users, have the chance to continue enhancing it. This is done with users adding more information and resources to the component via GitHub. +### Contribute to this component +You can help develop this component by adding information to the Github discussion. This helps other people to use it in their service. -To do this you should: +{% endtab %} -- go to the GitHub conversation -- add your comments, information and resources about the building block +{% tab "Designs" %} -## Code +## Designs -${generateComponentCodeSection(data)} +${generateDesignsTabContent(data)} {% endtab %} @@ -208,15 +234,15 @@ ${generateComponentCodeSection(data)} ## Accessibility -${generateAccessibilityReportSection(data, files)} +${generateAccessibilityTabContent(data)} {% endtab %} -{% tab "Links" %} +{% tab "Code" %} -## Links +## Code -${generateLinksSection(data)} +${generateCodeTabContent(data)} {% endtab %} diff --git a/middleware/notify-email.js b/middleware/notify-email.js index e98b92978..9ece1dfd5 100644 --- a/middleware/notify-email.js +++ b/middleware/notify-email.js @@ -4,46 +4,30 @@ const { NOTIFY_TOKEN, NOTIFY_PR_TEMPLATE, NOTIFY_SUBMISSION_TEMPLATE, + NOTIFY_VERIFICATION_TEMPLATE, NOTIFY_EMAIL, NOTIFY_EMAIL_RETRY_MS, - NOTIFY_EMAIL_MAX_RETRIES + NOTIFY_EMAIL_MAX_RETRIES, + APP_URL } = require('../config') const notifyClient = new NotifyClient(NOTIFY_TOKEN) -const emailAddress = NOTIFY_EMAIL +const dsTeamEmail = NOTIFY_EMAIL const sendEmail = async ( templateId, - prLink = null, - previewLink = null, - fileBuffer = null, - markdown = null, + email, + personalisation = {}, retries = NOTIFY_EMAIL_MAX_RETRIES, backoff = NOTIFY_EMAIL_RETRY_MS ) => { - const personalisation = {} - - if (prLink) { - personalisation.pr_link = prLink - } - if (previewLink) { - personalisation.preview_link = previewLink - } - - if (fileBuffer) { - personalisation.link_to_file = notifyClient.prepareUpload(fileBuffer) - } - - if (markdown) { - personalisation.markdown = markdown - } for (let attempt = 1; attempt <= retries; attempt++) { try { console.log( - `Sending email to ${emailAddress} using template ${templateId}, attempt ${attempt}` + `Sending email to ${email} using template ${templateId}, attempt ${attempt}` ) - const response = await notifyClient.sendEmail(templateId, emailAddress, { + const response = await notifyClient.sendEmail(templateId, email, { personalisation }) console.log('Email sent successfully.') @@ -63,12 +47,36 @@ const sendEmail = async ( } const sendSubmissionEmail = async (fileBuffer = null, markdown = null) => { - return sendEmail(NOTIFY_SUBMISSION_TEMPLATE, null, null, fileBuffer, markdown) + const personalisation = {} + + if (fileBuffer) { + personalisation.link_to_file = notifyClient.prepareUpload(fileBuffer) + } + + if (markdown) { + personalisation.markdown = markdown + } + + return sendEmail(NOTIFY_SUBMISSION_TEMPLATE, dsTeamEmail, personalisation) } const sendPrEmail = async ({ url, number }) => { - const previewUrl = `https://moj-frontend-pr-${number}.apps.live.cloud-platform.service.justice.gov.uk` - return sendEmail(NOTIFY_PR_TEMPLATE, url, previewUrl) + const personalisation = {} + + if (url) { + personalisation.pr_link = url + } + if (number) { + personalisation.preview_link = `https://moj-frontend-pr-${number}.apps.live.cloud-platform.service.justice.gov.uk` + } + + return sendEmail(NOTIFY_PR_TEMPLATE, dsTeamEmail, personalisation) +} + +const sendVerificationEmail = async (email, token) => { + const personalisation = {} + personalisation.token_link = `${APP_URL}/contribute/add-new-component/email/verify/${token}` + return sendEmail(NOTIFY_VERIFICATION_TEMPLATE, email, personalisation) } const handleEmailError = (error) => { @@ -87,5 +95,6 @@ const handleEmailError = (error) => { module.exports = { sendSubmissionEmail, - sendPrEmail + sendPrEmail, + sendVerificationEmail } diff --git a/middleware/process-subission-data.js b/middleware/process-subission-data.js index 534177e4b..db56dcb54 100644 --- a/middleware/process-subission-data.js +++ b/middleware/process-subission-data.js @@ -1,3 +1,5 @@ +const { generateMarkdown } = require('../middleware/generate-documentation') + const redis = require('../helpers/redis-client') const imageDirectory = 'assets/images' @@ -48,13 +50,16 @@ const getUniqueFilename = (originalName, existingFilenames) => { return uniqueName } -const processSubmissionFiles = async (sessionData, submissionRef) => { +const processSubmissionFiles = async (req) => { + console.log('processing files') + console.log(req) + const { session, submissionRef } = req const submissionFiles = {} const existingFilenames = new Set() - for (const key of Object.keys(sessionData)) { + for (const key of Object.keys(session)) { if (!['cookie', 'csrfToken'].includes(key)) { - const fileData = sessionData[key] + const fileData = session[key] if ( fileData?.componentImage?.redisKey || fileData?.accessibilityReport?.redisKey @@ -82,7 +87,11 @@ const processSubmissionFiles = async (sessionData, submissionRef) => { return submissionFiles } -const processSubmissionData = (sessionData, submissionFiles, submissionRef) => { +const processSubmissionData = (req, res, next) => { + console.log('processing submission data') + const sessionData = { ...req.session, ...req.markdown } + console.log(sessionData) + const { submissionFiles, submissionRef } = req const submissionData = {} for (const key in sessionData) { @@ -94,30 +103,9 @@ const processSubmissionData = (sessionData, submissionFiles, submissionRef) => { const filename = extractFilename(key) if (filename.endsWith('.md')) { // Documentation should be outside of the submission folder - submissionData[filename] = sessionData[key] + submissionData[`docs/components/${filename}`] = sessionData[key] } else { const data = Object.assign({}, sessionData[key]) - if (key === '/your-details') { - // Remove personal data - data.fullName = 'Not shared' - data.teamName = 'Not shared' - - // Add back in personal details if permission is given - if ( - sessionData[key]?.shareYourDetails?.includes( - 'addNameToComponentPage' - ) - ) { - data.fullName = sessionData[key].fullName - } - if ( - sessionData[key]?.shareYourDetails?.includes( - 'addTeamNameToComponentPage' - ) - ) { - data.teamName = sessionData[key].teamName - } - } if (key.startsWith('/component-code-details')) { const exampleNum = key.split('/').at(2) ? `-${key.split('/').at(2)}` @@ -147,7 +135,49 @@ const processSubmissionData = (sessionData, submissionFiles, submissionRef) => { } } - return submissionData + req.submissionData = submissionData + next() } -module.exports = { processSubmissionData, processSubmissionFiles } +const processPersonalData = (req, res, next) => { + console.log('excluding personal data') + const personalData = req.session['/your-details'] + const data = Object.assign({}, personalData) + // Remove personal data + data.fullName = 'Not shared' + data.teamName = 'Not shared' + + // Add back in personal details if permission is given + if (personalData?.shareYourDetails?.includes('addNameToComponentPage')) { + data.fullName = personalData.fullName + } + if (personalData?.shareYourDetails?.includes('addTeamNameToComponentPage')) { + data.teamName = personalData.teamName + } + req.session['/your-details'] = data + next() +} + +const buildComponentPage = (req, res, next) => { + console.log('building component page') + const { filename: markdownFilename, content: markdownContent } = + generateMarkdown(req.session, req.submissionFiles) + req.markdown = {} + req.markdownFilename = markdownFilename + req.markdownContent = markdownContent + req.markdown[markdownFilename] = markdownContent + next() +} + +const generateSubmissionRef = (req,res,next) => { + req.submissionRef = `submission-${Date.now()}` + next() +} + +module.exports = { + processSubmissionData, + processSubmissionFiles, + processPersonalData, + buildComponentPage, + generateSubmissionRef +} diff --git a/package-lock.json b/package-lock.json index da27aac13..398675ef3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2204,6 +2204,8 @@ }, "node_modules/@es-joy/jsdoccomment": { "version": "0.50.2", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.50.2.tgz", + "integrity": "sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==", "dev": true, "license": "MIT", "dependencies": { @@ -3880,6 +3882,8 @@ }, "node_modules/@puppeteer/browsers": { "version": "2.10.5", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz", + "integrity": "sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5574,6 +5578,8 @@ }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", "dev": true, "license": "MIT" }, @@ -6106,6 +6112,8 @@ }, "node_modules/@types/yauzl": { "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, "license": "MIT", "optional": true, @@ -6353,6 +6361,8 @@ }, "node_modules/acorn": { "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -6612,6 +6622,8 @@ }, "node_modules/array-differ": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", "dev": true, "license": "MIT", "engines": { @@ -6664,6 +6676,8 @@ }, "node_modules/array-union": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", "dev": true, "license": "MIT", "dependencies": { @@ -6675,6 +6689,8 @@ }, "node_modules/array-uniq": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", "dev": true, "license": "MIT", "engines": { @@ -6756,6 +6772,8 @@ }, "node_modules/arrify": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", "dev": true, "license": "MIT", "engines": { @@ -6776,6 +6794,8 @@ }, "node_modules/ast-types": { "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", "dev": true, "license": "MIT", "dependencies": { @@ -7112,6 +7132,8 @@ }, "node_modules/bare-fs": { "version": "4.1.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.5.tgz", + "integrity": "sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==", "dev": true, "license": "Apache-2.0", "optional": true, @@ -7134,6 +7156,8 @@ }, "node_modules/bare-os": { "version": "3.6.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", + "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", "dev": true, "license": "Apache-2.0", "optional": true, @@ -7143,6 +7167,8 @@ }, "node_modules/bare-path": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", "dev": true, "license": "Apache-2.0", "optional": true, @@ -7152,6 +7178,8 @@ }, "node_modules/bare-stream": { "version": "2.6.5", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", "dev": true, "license": "Apache-2.0", "optional": true, @@ -7200,6 +7228,8 @@ }, "node_modules/basic-ftp": { "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", "dev": true, "license": "MIT", "engines": { @@ -8652,6 +8682,8 @@ }, "node_modules/chromium-bidi/node_modules/mitt": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true, "license": "MIT" }, @@ -8984,6 +9016,8 @@ }, "node_modules/comment-parser": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", "dev": true, "license": "MIT", "engines": { @@ -9076,6 +9110,8 @@ }, "node_modules/component-emitter": { "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "dev": true, "license": "MIT", "funding": { @@ -9443,6 +9479,8 @@ }, "node_modules/cookiejar": { "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "dev": true, "license": "MIT" }, @@ -9634,6 +9672,8 @@ }, "node_modules/css-declaration-sorter": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", + "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", "dev": true, "license": "ISC", "engines": { @@ -9981,6 +10021,8 @@ }, "node_modules/data-uri-to-buffer": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", "dev": true, "license": "MIT", "engines": { @@ -10055,6 +10097,8 @@ }, "node_modules/debug": { "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -10390,6 +10434,8 @@ }, "node_modules/degenerator": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10459,6 +10505,8 @@ }, "node_modules/dependency-graph": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", + "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", "dev": true, "license": "MIT", "engines": { @@ -12775,6 +12823,8 @@ }, "node_modules/extract-zip": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -12794,6 +12844,8 @@ }, "node_modules/extract-zip/node_modules/get-stream": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "license": "MIT", "dependencies": { @@ -12866,6 +12918,8 @@ }, "node_modules/fast-safe-stringify": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true, "license": "MIT" }, @@ -13669,6 +13723,8 @@ }, "node_modules/get-uri": { "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", "dev": true, "license": "MIT", "dependencies": { @@ -15063,6 +15119,8 @@ }, "node_modules/htmlparser2": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", @@ -15081,6 +15139,8 @@ }, "node_modules/htmlparser2/node_modules/entities": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -15833,6 +15893,8 @@ }, "node_modules/ip-address": { "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", "dev": true, "license": "MIT", "dependencies": { @@ -15845,6 +15907,8 @@ }, "node_modules/ip-address/node_modules/sprintf-js": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "dev": true, "license": "BSD-3-Clause" }, @@ -16206,6 +16270,8 @@ }, "node_modules/is-json": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", + "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", "dev": true, "license": "ISC" }, @@ -18228,11 +18294,15 @@ }, "node_modules/jsbn": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", "dev": true, "license": "MIT" }, "node_modules/jsdoc-type-pratt-parser": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", + "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", "dev": true, "license": "MIT", "engines": { @@ -18986,6 +19056,8 @@ }, "node_modules/maximatch": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz", + "integrity": "sha512-9ORVtDUFk4u/NFfo0vG/ND/z7UQCVZBL539YW0+U1I7H1BkZwizcPx5foFv7LCPcBnm2U6RjFnQOsIvN4/Vm2A==", "dev": true, "license": "MIT", "dependencies": { @@ -19054,6 +19126,8 @@ }, "node_modules/meow": { "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", "dev": true, "license": "MIT", "engines": { @@ -19151,6 +19225,8 @@ }, "node_modules/mime": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "dev": true, "license": "MIT", "bin": { @@ -19162,6 +19238,8 @@ }, "node_modules/mime-db": { "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", "engines": { @@ -19278,6 +19356,8 @@ }, "node_modules/morphdom": { "version": "2.7.5", + "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.5.tgz", + "integrity": "sha512-z6bfWFMra7kBqDjQGHud1LSXtq5JJC060viEkQFMBX6baIecpkNr2Ywrn2OQfWP3rXiNFQRPoFjD8/TvJcWcDg==", "dev": true, "license": "MIT" }, @@ -19451,6 +19531,8 @@ }, "node_modules/netmask": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", "dev": true, "license": "MIT", "engines": { @@ -23082,6 +23164,8 @@ }, "node_modules/pac-proxy-agent": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", "dev": true, "license": "MIT", "dependencies": { @@ -23100,6 +23184,8 @@ }, "node_modules/pac-proxy-agent/node_modules/agent-base": { "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "dev": true, "license": "MIT", "engines": { @@ -23108,6 +23194,8 @@ }, "node_modules/pac-proxy-agent/node_modules/http-proxy-agent": { "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", "dependencies": { @@ -23120,6 +23208,8 @@ }, "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { @@ -23132,6 +23222,8 @@ }, "node_modules/pac-resolver": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", "dev": true, "license": "MIT", "dependencies": { @@ -23637,6 +23729,8 @@ }, "node_modules/please-upgrade-node": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", "dev": true, "license": "MIT", "dependencies": { @@ -23728,6 +23822,8 @@ }, "node_modules/postcss-calc": { "version": "10.1.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", + "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==", "dev": true, "license": "MIT", "dependencies": { @@ -24274,6 +24370,8 @@ }, "node_modules/posthtml": { "version": "0.16.6", + "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz", + "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", "dev": true, "license": "MIT", "dependencies": { @@ -24286,6 +24384,8 @@ }, "node_modules/posthtml-match-helper": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/posthtml-match-helper/-/posthtml-match-helper-2.0.3.tgz", + "integrity": "sha512-p9oJgTdMF2dyd7WE54QI1LvpBIkNkbSiiECKezNnDVYhGhD1AaOnAkw0Uh0y5TW+OHO8iBdSqnd8Wkpb6iUqmw==", "dev": true, "license": "MIT", "engines": { @@ -24297,6 +24397,8 @@ }, "node_modules/posthtml-parser": { "version": "0.11.0", + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", + "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", "dev": true, "license": "MIT", "dependencies": { @@ -24308,6 +24410,8 @@ }, "node_modules/posthtml-render": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", + "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", "dev": true, "license": "MIT", "dependencies": { @@ -24446,6 +24550,8 @@ }, "node_modules/proxy-agent": { "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", "dev": true, "license": "MIT", "dependencies": { @@ -24464,6 +24570,8 @@ }, "node_modules/proxy-agent/node_modules/agent-base": { "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "dev": true, "license": "MIT", "engines": { @@ -24472,6 +24580,8 @@ }, "node_modules/proxy-agent/node_modules/http-proxy-agent": { "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", "dependencies": { @@ -24484,6 +24594,8 @@ }, "node_modules/proxy-agent/node_modules/https-proxy-agent": { "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { @@ -24496,6 +24608,8 @@ }, "node_modules/proxy-agent/node_modules/lru-cache": { "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, "license": "ISC", "engines": { @@ -24560,6 +24674,8 @@ }, "node_modules/puppeteer": { "version": "24.9.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.9.0.tgz", + "integrity": "sha512-L0pOtALIx8rgDt24Y+COm8X52v78gNtBOW6EmUcEPci0TYD72SAuaXKqasRIx4JXxmg2Tkw5ySKcpPOwN8xXnQ==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -24580,6 +24696,8 @@ }, "node_modules/puppeteer-core": { "version": "24.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.9.0.tgz", + "integrity": "sha512-HFdCeH/wx6QPz8EncafbCqJBqaCG1ENW75xg3cLFMRUoqZDgByT6HSueiumetT2uClZxwqj0qS4qMVZwLHRHHw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -26249,6 +26367,8 @@ }, "node_modules/semantic-release/node_modules/marked": { "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", "bin": { @@ -26351,6 +26471,8 @@ }, "node_modules/semver-compare": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", "dev": true, "license": "MIT" }, @@ -26411,6 +26533,8 @@ }, "node_modules/send": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "dev": true, "license": "MIT", "dependencies": { @@ -26432,6 +26556,8 @@ }, "node_modules/send/node_modules/fresh": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "dev": true, "license": "MIT", "engines": { @@ -26440,6 +26566,8 @@ }, "node_modules/send/node_modules/mime-types": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "dev": true, "license": "MIT", "dependencies": { @@ -26933,6 +27061,8 @@ }, "node_modules/smart-buffer": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, "license": "MIT", "engines": { @@ -27083,6 +27213,8 @@ }, "node_modules/socks": { "version": "2.8.4", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", + "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", "dev": true, "license": "MIT", "dependencies": { @@ -27096,6 +27228,8 @@ }, "node_modules/socks-proxy-agent": { "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, "license": "MIT", "dependencies": { @@ -27109,6 +27243,8 @@ }, "node_modules/socks-proxy-agent/node_modules/agent-base": { "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "dev": true, "license": "MIT", "engines": { @@ -27240,6 +27376,8 @@ }, "node_modules/split2": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "dev": true, "license": "ISC", "engines": { @@ -27253,6 +27391,8 @@ }, "node_modules/ssri": { "version": "11.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-11.0.0.tgz", + "integrity": "sha512-aZpUoMN/Jj2MqA4vMCeiKGnc/8SuSyHbGSBdgFbZxP8OJGF/lFkIuElzPxsN0q8TQQ+prw3P4EDfB3TBHHgfXw==", "dev": true, "license": "ISC", "dependencies": { @@ -28095,6 +28235,8 @@ }, "node_modules/superagent/node_modules/mime": { "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, "license": "MIT", "bin": { @@ -28284,6 +28426,8 @@ }, "node_modules/tar-fs": { "version": "3.0.8", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz", + "integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==", "dev": true, "license": "MIT", "dependencies": { @@ -29307,6 +29451,8 @@ }, "node_modules/urlpattern-polyfill": { "version": "10.1.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", + "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", "dev": true, "license": "MIT" }, @@ -30021,6 +30167,8 @@ }, "node_modules/zod": { "version": "3.25.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.7.tgz", + "integrity": "sha512-YGdT1cVRmKkOg6Sq7vY7IkxdphySKnXhaUmFI4r4FcuFVNgpCb9tZfNwXbT6BPjD5oz0nubFsoo9pIqKrDcCvg==", "dev": true, "license": "MIT", "funding": { diff --git a/routes/add-component.js b/routes/add-component.js index 9b01e4a82..d643b13e9 100644 --- a/routes/add-component.js +++ b/routes/add-component.js @@ -1,14 +1,20 @@ const crypto = require('crypto') +const fs = require('fs') const express = require('express') const { xss } = require('express-xss-sanitizer') const multer = require('multer') -const { COMPONENT_FORM_PAGES, ADD_NEW_COMPONENT_ROUTE } = require('../config') +const { + COMPONENT_FORM_PAGES, + ADD_NEW_COMPONENT_ROUTE, + MESSAGES, + ENV +} = require('../config') const ApplicationError = require('../helpers/application-error') const { checkYourAnswers } = require('../helpers/check-your-answers') const getPrTitleAndDescription = require('../helpers/get-pr-title-and-description') -const sessionData = require('../helpers/mockSessionData/sessionData.js') +const mockSessionData = require('../helpers/mockSessionData/sessionData.js') const { urlToTitleCase } = require('../helpers/text-helper') const { validateFormData, @@ -21,19 +27,27 @@ const { getFormSummaryListForRemove, removeFromSession, sessionStarted, + sessionVerified, validateFormDataFileUpload, saveFileToRedis, - clearSkippedPageData + clearSkippedPageData, + checkEmailDomain, + validatePageParams, + setCsrfToken } = require('../middleware/component-session') const { generateMarkdown } = require('../middleware/generate-documentation') const { pushToGitHub, createPullRequest } = require('../middleware/github-api') const { sendSubmissionEmail, - sendPrEmail + sendPrEmail, + sendVerificationEmail } = require('../middleware/notify-email') const { + processPersonalData, processSubmissionData, - processSubmissionFiles + processSubmissionFiles, + buildComponentPage, + generateSubmissionRef } = require('../middleware/process-subission-data') const verifyCsrf = require('../middleware/verify-csrf') const upload = multer({ @@ -41,44 +55,18 @@ const upload = multer({ limits: { fileSize: 10 * 1024 * 1024 } // 10MB }) const router = express.Router() - -const validatePageParams = (req, res, next) => { - const validPages = Object.keys(COMPONENT_FORM_PAGES) - let valid = true - - if (req.params.page) { - // if page is present it must be in allowlist of configured pages - valid = validPages.includes(req.params.page) - } - - if (req.params.subpage) { - // if subpage is present it must always be a number - valid = /^\d+$/.test(req.params.subpage) - } - - if (valid) { - next() - } else { - const error = new ApplicationError('Page not found', 404) - next(error) - } -} - const checkYourAnswersPath = 'check-your-answers' -const setCsrfToken = (req, res, next) => { - if (req?.session) { - if (!req?.session?.csrfToken) { - req.session.csrfToken = crypto.randomBytes(32).toString('hex') - } - } - next() -} +router.get('/error', (req, res, next) => { + const error = new ApplicationError('ohno') + next(error) +}) router.all('*', setCsrfToken) // TODO: Why is this a get * ? Can it not just be set in the get /checkYourAnswersPath route below? router.get('*', (req, res, next) => { + console.log('setting cya visited') if (req?.session) { if (req?.url.endsWith(checkYourAnswersPath)) { console.log('visited checkYourAnswersPath') @@ -89,11 +77,6 @@ router.get('*', (req, res, next) => { next() }) -router.get('/error', (req, res, next) => { - const error = new ApplicationError('Uh oh!', 500) - next(error) -}) - // Check your answers page router.get( `/${checkYourAnswersPath}`, @@ -104,7 +87,9 @@ router.get( res.render(checkYourAnswersPath, { submitUrl: req.originalUrl, sections, - csrfToken: req?.session?.csrfToken + csrfToken: req?.session?.csrfToken, + isDev: ENV === 'development', + generateLink: `${ADD_NEW_COMPONENT_ROUTE}/generate-markdown` }) } ) @@ -116,7 +101,7 @@ if (process.env.DEV_DUMMY_DATA) { return next(new Error('Session not available')) } - Object.assign(req.session, sessionData) + Object.assign(req.session, mockSessionData) req.session.save((err) => { if (err) { return next(err) @@ -126,8 +111,49 @@ if (process.env.DEV_DUMMY_DATA) { }) } +if (ENV === 'development') { + router.get('/generate-markdown', (req, res, next) => { + if (!req.session) { + return next(new Error('Session not available')) + } + + if (!req.session.checkYourAnswers) { + Object.assign(req.session, mockSessionData) + req.session.save((err) => { + if (err) { + return next(err) + } + }) + } + next() + }, + processPersonalData, + generateSubmissionRef, + async (req, res, next) => { + console.log('processing files') + req.submissionFiles = await processSubmissionFiles(req) + next() + }, + buildComponentPage, + async (req,res) => { + const { markdownFilename: filename, markdownContent: content} = req + fs.writeFile(`docs/components/${filename}`, content, (err) => { + if (err) { + console.error(err) + } else { + // file written successfully + setTimeout(() => { + res.redirect(`/components/${filename.split('.').at(0).toLowerCase()}`) + }, 2000) + } + }) + }) +} + // Start router.get('/start', (req, res) => { + console.log('get start') + delete req.session.checkYourAnswers req.session.started = true console.log('Start session') @@ -135,6 +161,7 @@ router.get('/start', (req, res) => { title: 'Submit a component', csrfToken: req?.session?.csrfToken }) + console.log('after render') }) // Confirmation page @@ -144,12 +171,138 @@ router.get('/confirmation', (req, res) => { }) }) -router.all('*', sessionStarted) // Check that we have a session in progress +router.get('/email/verify/:token', (req, res) => { + if (!req?.session?.emailToken) { + // session expired + req.session.sessionFlash = MESSAGES.emailVerificationExpired + res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/email`) + } else { + if (req.params.token === req.session.emailToken) { + // verified + req.session.verified = true + req.session.sessionFlash = MESSAGES.emailVerificationSuccess + res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/component-details`) + } else { + // token invalid + req.session.sessionFlash = MESSAGES.emailVerificationInvalidToken + res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/email`) + } + } +}) + +router.get('/email', (req, res) => { + req.session.started = true + + if (req.query.reset === 'true') { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete req.session['/email'] + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete req.session.emailToken + } + + res.render('email', { + page: COMPONENT_FORM_PAGES.email, + submitUrl: req.originalUrl, + csrfToken: req?.session?.csrfToken + }) +}) + +// For all following routed we must have a session in progress +router.all('*', sessionStarted) router.post('/start', verifyCsrf, (req, res) => { - res.redirect('/contribute/add-new-component/component-details') + if ( + process.env.SKIP_VERIFICATION === 'true' && + process.env.DEV_VERIFIED_EMAIL + ) { + req.session['/email'] = { emailAddress: process.env.DEV_VERIFIED_EMAIL } + req.session.emailDomainAllowed = true + req.session.verified = true + res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/component-details`) + } else { + res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/email`) + } }) +router.post( + '/email', + xss(), + verifyCsrf, + validateFormData, + checkEmailDomain, + (req, res, next) => { + if (req.emailDomainAllowed) { + saveSession(req, res, next) + } else { + res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/email/not-allowed`) + } + }, + // generate token + (req, res, next) => { + req.session.emailToken = crypto.randomBytes(32).toString('hex') + next() + }, + // send email + async (req, res) => { + if (req.emailDomainAllowed) { + res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/email/check`) + const token = req?.session?.emailToken + const email = req?.session?.['/email']?.emailAddress + if (token && email) { + try { + await sendVerificationEmail(email, token) + } catch (error) { + console.error(`Error sending verification email: ${error}`) + } + } + } + } +) + +router.get('/email/check', (req, res) => { + res.render('email-check', { + page: { + title: 'Check your email', + email: req?.session?.['/email']?.emailAddress + } + }) +}) + +router.get('/email/resend', (req, res) => { + res.render('email-resend', { + submitUrl: req.originalUrl, + csrfToken: req?.session?.csrfToken, + page: { + title: 'If you’re having problems with the email', + email: req?.session?.['/email']?.emailAddress + } + }) +}) + +router.post('/email/resend', xss(), verifyCsrf, async (req, res) => { + res.redirect(`${ADD_NEW_COMPONENT_ROUTE}/email/check`) + const token = req?.session?.emailToken + const email = req?.session?.['/email']?.emailAddress + if (token && email) { + try { + await sendVerificationEmail(email, token) + } catch (error) { + console.error(`Error sending verification email: ${error}`) + } + } +}) + +router.get('/email/not-allowed', (req, res) => { + res.render('email-not-allowed', { + page: { + title: 'You cannot submit a component' + } + }) +}) + +// For all following routed we must have verified an email address +router.all('*', sessionVerified) + // Remove form page router.get( ['/remove/:page', '/remove/:page/:subpage'], @@ -209,6 +362,7 @@ router.get( canAddAnother, getBackLink, (req, res) => { + console.log('get page/subpage') console.log(`CYA: ${req?.session?.checkYourAnswers}`) res.render(`${req.params.page}`, { page: COMPONENT_FORM_PAGES[req.params.page], @@ -224,31 +378,28 @@ router.get( } ) - - // "Check Your Answers" form submission router.post( `/${checkYourAnswersPath}`, verifyCsrf, getRawSessionText, + processPersonalData, + generateSubmissionRef, + async (req, res, next) => { + console.log('processing files') + req.submissionFiles = await processSubmissionFiles(req) + next() + }, + buildComponentPage, + processSubmissionData, async (req, res) => { - const submissionRef = `submission-${Date.now()}` - const submissionFiles = await processSubmissionFiles( - req.session, - submissionRef - ) - // console.log(submissionFiles) - const { filename: markdownFilename, content: markdownContent } = - generateMarkdown(req.session, submissionFiles) - const markdown = {} - markdown[markdownFilename] = markdownContent - const { sessionText } = req - const session = { ...req.session, ...markdown } - const sessionData = processSubmissionData( + const { session, - submissionFiles, - submissionRef - ) + sessionText, + submissionData, + submissionRef, + markdownContent + } = req req.session.regenerate((err) => { if (err) { @@ -258,7 +409,7 @@ router.post( }) try { - const branchName = await pushToGitHub(sessionData, submissionRef) + const branchName = await pushToGitHub(submissionData, submissionRef) const { title, description } = getPrTitleAndDescription(session) const pr = await createPullRequest(branchName, title, description) await sendPrEmail(pr) @@ -283,10 +434,9 @@ router.post( validateFormData, (req, res, next) => { if (req.file) { - req.session.sessionFlash = { - type: 'success', - message: `File ‘${req.file.originalname}’ has been uploaded.` - } + req.session.sessionFlash = MESSAGES.componentImageUploaded( + req.file.originalname + ) saveSession(req, res, next) } else { // Skipping saving as no new file uploaded diff --git a/schema/email.schema.js b/schema/email.schema.js new file mode 100644 index 000000000..46aaa0a70 --- /dev/null +++ b/schema/email.schema.js @@ -0,0 +1,15 @@ +const Joi = require('joi') + +const schema = Joi.object({ + emailAddress: Joi.string() + .email({ tlds: { allow: false } }) + .required() + .messages({ + 'string.email': + 'Enter an email address ending @justice.gov.uk', + 'string.empty': 'Enter your email address', + 'any.required': 'Enter your email address' + }) +}) + +module.exports = schema diff --git a/views/common/base.njk b/views/common/base.njk index 607007095..59fe855f6 100644 --- a/views/common/base.njk +++ b/views/common/base.njk @@ -38,9 +38,9 @@ -
+
{% block beforeContent %}{% endblock %} -
+
{% block content %} {{ content | safe }} {% endblock %} diff --git a/views/common/contributions.njk b/views/common/contributions.njk new file mode 100644 index 000000000..29d5d59d7 --- /dev/null +++ b/views/common/contributions.njk @@ -0,0 +1,49 @@ +{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} +{% from "govuk/components/back-link/macro.njk" import govukBackLink %} +{% from "moj/components/alert/macro.njk" import mojAlert %}{% extends "base.njk" %} +{% from "govuk/components/button/macro.njk" import govukButton %} +{% from "macros/page-title.njk" import pageTitle %} +{% from "macros/form.njk" import form %} + +{% block beforeContent %} + +{% endblock %} + +{% block content %} +
+ {% if errorList %} + {{ govukErrorSummary({ + titleText: "There is a problem", + errorList: errorList + }) }} + {% endif %} + + {% if sessionFlash.text %} + {{ mojAlert({ + variant: sessionFlash.type, + title: sessionFlash.title, + text: sessionFlash.text, + showTitleAsHeading: true, + role: "alert" + })}} + {% endif %} + + {% block pageTitle %} + {{ pageTitle({ + caption: 'Submit a component', + title: page.title + })}} + {% endblock %} + + {% block form %}{% endblock %} + +
+ +{% endblock %} diff --git a/views/common/macros/form.njk b/views/common/macros/form.njk new file mode 100644 index 000000000..67f1cad6a --- /dev/null +++ b/views/common/macros/form.njk @@ -0,0 +1,8 @@ +{% from "govuk/macros/attributes.njk" import govukAttributes %} +{% macro form(params) %} +
+ + + {{ caller()}} +
+{% endmacro %} diff --git a/views/common/macros/page-title.njk b/views/common/macros/page-title.njk new file mode 100644 index 000000000..63781f75b --- /dev/null +++ b/views/common/macros/page-title.njk @@ -0,0 +1,6 @@ +{% macro pageTitle(params)%} + {% if params.title %} + {{ params.caption }} + {% endif %} +

{{ params.title }}

+{% endmacro %} diff --git a/views/common/partials/side-navigation.njk b/views/common/partials/side-navigation.njk index 0bbc1c3b4..5f2af32ae 100644 --- a/views/common/partials/side-navigation.njk +++ b/views/common/partials/side-navigation.njk @@ -406,13 +406,6 @@ -
  • - Setting up coded prototypes
  • - - - - -
  • Deploying coded prototypes
  • diff --git a/views/community/pages/accessibility-findings-more.njk b/views/community/pages/accessibility-findings-more.njk deleted file mode 100644 index 0b5bd5126..000000000 --- a/views/community/pages/accessibility-findings-more.njk +++ /dev/null @@ -1,46 +0,0 @@ -{% from "govuk/components/character-count/macro.njk" import govukCharacterCount %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% extends "base.njk" %} -{% block content %} -
    - {% if backLink %} - {{ govukBackLink({ - text: "Back", - href: backLink - }) }} - {% endif %} - {% if errorList %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorList - }) }} - {% endif %} -
    - Submit a new component -

    Accessibility findings

    - {{ govukCharacterCount({ - name: "accessibilityTellUsMore", - id: "accessibility-tell-us-more", - maxwords: 500, - label: { - text: "Tell us more about how it has been accessibility tested and the findings", - classes: "govuk-label--m", - isPageHeading: true - }, - errorMessage: formErrors.accessibilityTellUsMore, - value: formData.accessibilityTellUsMore - }) }} - - - -
    - {{ govukButton({ - text: 'Continue' - }) }} -
    -
    -
    - -{% endblock %} diff --git a/views/community/pages/accessibility-findings.njk b/views/community/pages/accessibility-findings.njk index a336462d2..8402d85e2 100644 --- a/views/community/pages/accessibility-findings.njk +++ b/views/community/pages/accessibility-findings.njk @@ -1,120 +1,104 @@ {% from "govuk/components/radios/macro.njk" import govukRadios %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% extends "base.njk" %} -{% block content %} -
    - {% if backLink %} - {{ govukBackLink({ - text: "Back", - href: backLink - }) }} - {% endif %} - {% if errorList %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorList - }) }} - {% endif %} -
    - Submit new component -

    Accessibility findings

    -

    Sharing accessibility findings helps people understand how to use the component. It also helps the Design System team to review it.

    - {{ govukRadios({ - name: "hasComponentBeenTestedExternalAccessibility", - hint: { - text: page.fields.hasComponentBeenTestedExternalAccessibility.hint +{% extends "contributions.njk" %} + +{% block form %} + {% call form({ + action: submitUrl, + csrfToken: csrfToken + }) %} + +

    Sharing accessibility findings helps people understand how to use the component. It also helps the Design System team to review it.

    + + {{ govukRadios({ + name: "hasComponentBeenTestedExternalAccessibility", + hint: { + text: page.fields.hasComponentBeenTestedExternalAccessibility.hint + }, + fieldset: { + attributes: { + id: "has-component-been-tested-external-accessibility" }, - fieldset: { - attributes: { - id: "has-component-been-tested-external-accessibility" - }, - legend: { - text: page.fields.hasComponentBeenTestedExternalAccessibility.label, - classes: "govuk-fieldset__legend--m" - } + legend: { + text: page.fields.hasComponentBeenTestedExternalAccessibility.label, + classes: "govuk-fieldset__legend--m" + } + }, + items: [ + { + value: "yes", + text: "Yes" }, - items: [ - { - value: "yes", - text: "Yes" - }, - { - value: "no", - text: "No" - } - ], - errorMessage: formErrors.hasComponentBeenTestedExternalAccessibility, - value: formData.hasComponentBeenTestedExternalAccessibility - }) }} + { + value: "no", + text: "No" + } + ], + errorMessage: formErrors.hasComponentBeenTestedExternalAccessibility, + value: formData.hasComponentBeenTestedExternalAccessibility + }) }} - {{ govukRadios({ - name: "hasComponentBeenTestedInternalAudit", - hint: { - text: page.fields.hasComponentBeenTestedInternalAudit.hint + {{ govukRadios({ + name: "hasComponentBeenTestedInternalAudit", + hint: { + text: page.fields.hasComponentBeenTestedInternalAudit.hint + }, + fieldset: { + attributes: { + id: "has-component-been-tested-internal-audit" }, - fieldset: { - attributes: { - id: "has-component-been-tested-internal-audit" - }, - legend: { - text: page.fields.hasComponentBeenTestedInternalAudit.label, - classes: "govuk-fieldset__legend--m" - } + legend: { + text: page.fields.hasComponentBeenTestedInternalAudit.label, + classes: "govuk-fieldset__legend--m" + } + }, + items: [ + { + value: "yes", + text: "Yes" }, - items: [ - { - value: "yes", - text: "Yes" - }, - { - value: "no", - text: "No" - } - ], - errorMessage: formErrors.hasComponentBeenTestedInternalAudit, - value: formData.hasComponentBeenTestedInternalAudit - }) }} + { + value: "no", + text: "No" + } + ], + errorMessage: formErrors.hasComponentBeenTestedInternalAudit, + value: formData.hasComponentBeenTestedInternalAudit + }) }} - {{ govukRadios({ - name: "hasComponentBeenTestedUsingAssistiveTechnology", - hint: { - text: page.fields.hasComponentBeenTestedUsingAssistiveTechnology.hint + {{ govukRadios({ + name: "hasComponentBeenTestedUsingAssistiveTechnology", + hint: { + text: page.fields.hasComponentBeenTestedUsingAssistiveTechnology.hint + }, + fieldset: { + attributes: { + id: "has-component-been-tested-using-assistive-technology" }, - fieldset: { - attributes: { - id: "has-component-been-tested-using-assistive-technology" - }, - legend: { - text: page.fields.hasComponentBeenTestedUsingAssistiveTechnology.label, - classes: "govuk-fieldset__legend--m" - } + legend: { + text: page.fields.hasComponentBeenTestedUsingAssistiveTechnology.label, + classes: "govuk-fieldset__legend--m" + } + }, + items: [ + { + value: "yes", + text: "Yes" }, - items: [ - { - value: "yes", - text: "Yes" - }, - { - value: "no", - text: "No" - } - ], - errorMessage: formErrors.hasComponentBeenTestedUsingAssistiveTechnology, - value: formData.hasComponentBeenTestedUsingAssistiveTechnology - }) }} - - - -
    - {{ govukButton({ - text: 'Continue', - preventDoubleClick: true - }) }} -
    -
    -
    + { + value: "no", + text: "No" + } + ], + errorMessage: formErrors.hasComponentBeenTestedUsingAssistiveTechnology, + value: formData.hasComponentBeenTestedUsingAssistiveTechnology + }) }} +
    + {{ govukButton({ + text: 'Continue', + preventDoubleClick: true + }) }} +
    + {% endcall %} {% endblock %} diff --git a/views/community/pages/add-assistive-tech.njk b/views/community/pages/add-assistive-tech.njk index 41d009e03..ee143083d 100644 --- a/views/community/pages/add-assistive-tech.njk +++ b/views/community/pages/add-assistive-tech.njk @@ -2,88 +2,68 @@ {% from "govuk/components/date-input/macro.njk" import govukDateInput %} {% from "govuk/components/file-upload/macro.njk" import govukFileUpload %} {% from "govuk/components/character-count/macro.njk" import govukCharacterCount %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} -{% from "govuk/components/tag/macro.njk" import govukTag %} -{% extends "base.njk" %} -{% block content %} -
    - {% if backLink %} - {{ govukBackLink({ - text: "Back", - href: backLink - }) }} - {% endif %} - {% if errorList %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorList - }) }} - {% endif %} -
    - Submit a new component -

    Testing with assistive technology

    +{% extends "contributions.njk" %} - {{ govukDateInput({ - id: "testing-date", - namePrefix: "testingDate", - name: "testing-date", - errorMessage: formErrors.testingDate, - value: formData.testingDate, - items: [ - { - classes: (formErrorStyles['testingDate-day'] | default('')) + " govuk-input--width-2", - name: "day", - value: formData['testingDate-day'] - }, - { - classes: (formErrorStyles['testingDate-month'] | default('')) + " govuk-input--width-2", - name: "month", - value: formData['testingDate-month'] - }, - { - classes: (formErrorStyles['testingDate-year'] | default('')) + " govuk-input--width-4", - name: "year", - value: formData['testingDate-year'] - } - ], - fieldset: { - legend: { - text: page.fields.testingDate.label, - isPageHeading: true, - classes: "govuk-fieldset__legend--m" - } - }, - hint: { - text: page.fields.testingDate.hint - } - }) }} +{% block form %} + {% call form({ + action: submitUrl, + csrfToken: csrfToken + }) %} - {{ govukCharacterCount({ - id: "issues-discovered", - name: "issuesDiscovered", - maxwords: 250, - label: { - text: page.fields.issuesDiscovered.label, - classes: "govuk-label--m", - isPageHeading: true + {{ govukDateInput({ + id: "testing-date", + namePrefix: "testingDate", + name: "testing-date", + errorMessage: formErrors.testingDate, + value: formData.testingDate, + items: [ + { + classes: (formErrorStyles['testingDate-day'] | default('')) + " govuk-input--width-2", + name: "day", + value: formData['testingDate-day'] }, - errorMessage: formErrors.issuesDiscovered, - value: formData.issuesDiscovered - }) }} - - + { + classes: (formErrorStyles['testingDate-month'] | default('')) + " govuk-input--width-2", + name: "month", + value: formData['testingDate-month'] + }, + { + classes: (formErrorStyles['testingDate-year'] | default('')) + " govuk-input--width-4", + name: "year", + value: formData['testingDate-year'] + } + ], + fieldset: { + legend: { + text: page.fields.testingDate.label, + isPageHeading: true, + classes: "govuk-fieldset__legend--m" + } + }, + hint: { + text: page.fields.testingDate.hint + } + }) }} -
    - {{ govukButton({ - text: 'Continue', - preventDoubleClick: true - }) }} -
    -
    -
    + {{ govukCharacterCount({ + id: "issues-discovered", + name: "issuesDiscovered", + maxwords: 250, + label: { + text: page.fields.issuesDiscovered.label, + classes: "govuk-label--m", + isPageHeading: true + }, + errorMessage: formErrors.issuesDiscovered, + value: formData.issuesDiscovered + }) }} +
    + {{ govukButton({ + text: 'Continue', + preventDoubleClick: true + }) }} +
    + {% endcall %} {% endblock %} diff --git a/views/community/pages/add-external-audit.njk b/views/community/pages/add-external-audit.njk index 01e157faf..a1d14e892 100644 --- a/views/community/pages/add-external-audit.njk +++ b/views/community/pages/add-external-audit.njk @@ -1,98 +1,78 @@ {% from "govuk/components/input/macro.njk" import govukInput %} {% from "govuk/components/date-input/macro.njk" import govukDateInput %} -{% from "govuk/components/file-upload/macro.njk" import govukFileUpload %} {% from "govuk/components/character-count/macro.njk" import govukCharacterCount %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} -{% from "govuk/components/tag/macro.njk" import govukTag %} -{% extends "base.njk" %} -{% block content %} -
    - {% if backLink %} - {{ govukBackLink({ - text: "Back", - href: backLink - }) }} - {% endif %} - {% if errorList %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorList - }) }} - {% endif %} -
    - Submit a new component -

    External accessibility audit

    +{% extends "contributions.njk" %} - {{ govukInput({ - label: { - text: page.fields.externalOrganisation.label, - classes: "govuk-label--m" - }, - id: "external-organisation", - name: "externalOrganisation", - errorMessage: formErrors.externalOrganisation, - value: formData.externalOrganisation - }) }} +{% block form %} + {% call form({ + action: submitUrl, + csrfToken: csrfToken + }) %} - {{ govukDateInput({ - id: "audit-date", - namePrefix: "auditDate", - name: "audit-date", - errorMessage: formErrors.auditDate, - value: formData.auditDate, - items: [ - { - classes: (formErrorStyles['auditDate-day'] | default('')) + " govuk-input--width-2", - name: "day", - value: formData['auditDate-day'] - }, - { - classes: (formErrorStyles['auditDate-month'] | default('')) + " govuk-input--width-2", - name: "month", - value: formData['auditDate-month'] - }, - { - classes: (formErrorStyles['auditDate-year'] | default('')) + " govuk-input--width-4", - name: "year", - value: formData['auditDate-year'] - } - ], - fieldset: { - legend: { - text: page.fields.auditDate.label, - classes: "govuk-fieldset__legend--m" - } - }, - hint: { - text: page.fields.auditDate.hint - } - }) }} + {{ govukInput({ + label: { + text: page.fields.externalOrganisation.label, + classes: "govuk-label--m" + }, + id: "external-organisation", + name: "externalOrganisation", + errorMessage: formErrors.externalOrganisation, + value: formData.externalOrganisation + }) }} - {{ govukCharacterCount({ - id: "issues-discovered", - name: "issuesDiscovered", - maxwords: 250, - label: { - text: page.fields.issuesDiscovered.label, - classes: "govuk-label--m" + {{ govukDateInput({ + id: "audit-date", + namePrefix: "auditDate", + name: "audit-date", + errorMessage: formErrors.auditDate, + value: formData.auditDate, + items: [ + { + classes: (formErrorStyles['auditDate-day'] | default('')) + " govuk-input--width-2", + name: "day", + value: formData['auditDate-day'] }, - errorMessage: formErrors.issuesDiscovered, - value: formData.issuesDiscovered - }) }} + { + classes: (formErrorStyles['auditDate-month'] | default('')) + " govuk-input--width-2", + name: "month", + value: formData['auditDate-month'] + }, + { + classes: (formErrorStyles['auditDate-year'] | default('')) + " govuk-input--width-4", + name: "year", + value: formData['auditDate-year'] + } + ], + fieldset: { + legend: { + text: page.fields.auditDate.label, + classes: "govuk-fieldset__legend--m" + } + }, + hint: { + text: page.fields.auditDate.hint + } + }) }} - + {{ govukCharacterCount({ + id: "issues-discovered", + name: "issuesDiscovered", + maxwords: 250, + label: { + text: page.fields.issuesDiscovered.label, + classes: "govuk-label--m" + }, + errorMessage: formErrors.issuesDiscovered, + value: formData.issuesDiscovered + }) }} -
    - {{ govukButton({ - text: 'Continue', - preventDoubleClick: true - }) }} -
    -
    -
    +
    + {{ govukButton({ + text: 'Continue', + preventDoubleClick: true + }) }} +
    + {% endcall %} {% endblock %} diff --git a/views/community/pages/add-internal-audit.njk b/views/community/pages/add-internal-audit.njk index 739bc1c1c..1886659de 100644 --- a/views/community/pages/add-internal-audit.njk +++ b/views/community/pages/add-internal-audit.njk @@ -1,100 +1,79 @@ {% from "govuk/components/input/macro.njk" import govukInput %} {% from "govuk/components/date-input/macro.njk" import govukDateInput %} -{% from "govuk/components/file-upload/macro.njk" import govukFileUpload %} {% from "govuk/components/character-count/macro.njk" import govukCharacterCount %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} -{% from "govuk/components/tag/macro.njk" import govukTag %} -{% extends "base.njk" %} -{% block content %} -
    - {% if backLink %} - {{ govukBackLink({ - text: "Back", - href: backLink - }) }} - {% endif %} - {% if errorList %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorList - }) }} - {% endif %} -
    - Submit a new component -

    Internal accessibility review

    +{% extends "contributions.njk" %} - {{ govukInput({ - label: { - text: page.fields.internalOrganisation.label, - classes: "govuk-label--m" - }, - id: "internal-organisation", - name: "internalOrganisation", - errorMessage: formErrors.internalOrganisation, - value: formData.internalOrganisation - }) }} +{% block form %} + {% call form({ + action: submitUrl, + csrfToken: csrfToken + }) %} - {{ govukDateInput({ - id: "audit-date", - namePrefix: "auditDate", - name: "audit-date", - errorMessage: formErrors.auditDate, - value: formData.auditDate, - items: [ - { - classes: (formErrorStyles['auditDate-day'] | default('')) + " govuk-input--width-2", - name: "day", - value: formData['auditDate-day'] - }, - { - classes: (formErrorStyles['auditDate-month'] | default('')) + " govuk-input--width-2", - name: "month", - value: formData['auditDate-month'] - }, - { - classes: (formErrorStyles['auditDate-year'] | default('')) + " govuk-input--width-4", - name: "year", - value: formData['auditDate-year'] - } - ], - fieldset: { - legend: { - text: page.fields.auditDate.label, - isPageHeading: true, - classes: "govuk-fieldset__legend--m" - } - }, - hint: { - text: page.fields.auditDate.hint - } - }) }} + {{ govukInput({ + label: { + text: page.fields.internalOrganisation.label, + classes: "govuk-label--m" + }, + id: "internal-organisation", + name: "internalOrganisation", + errorMessage: formErrors.internalOrganisation, + value: formData.internalOrganisation + }) }} - {{ govukCharacterCount({ - id: "issues-discovered", - name: "issuesDiscovered", - maxwords: 250, - label: { - text: page.fields.issuesDiscovered.label, - classes: "govuk-label--m", - isPageHeading: true + {{ govukDateInput({ + id: "audit-date", + namePrefix: "auditDate", + name: "audit-date", + errorMessage: formErrors.auditDate, + value: formData.auditDate, + items: [ + { + classes: (formErrorStyles['auditDate-day'] | default('')) + " govuk-input--width-2", + name: "day", + value: formData['auditDate-day'] }, - errorMessage: formErrors.issuesDiscovered, - value: formData.issuesDiscovered - }) }} - - + { + classes: (formErrorStyles['auditDate-month'] | default('')) + " govuk-input--width-2", + name: "month", + value: formData['auditDate-month'] + }, + { + classes: (formErrorStyles['auditDate-year'] | default('')) + " govuk-input--width-4", + name: "year", + value: formData['auditDate-year'] + } + ], + fieldset: { + legend: { + text: page.fields.auditDate.label, + isPageHeading: true, + classes: "govuk-fieldset__legend--m" + } + }, + hint: { + text: page.fields.auditDate.hint + } + }) }} -
    - {{ govukButton({ - text: 'Continue', - preventDoubleClick: true - }) }} -
    -
    -
    + {{ govukCharacterCount({ + id: "issues-discovered", + name: "issuesDiscovered", + maxwords: 250, + label: { + text: page.fields.issuesDiscovered.label, + classes: "govuk-label--m", + isPageHeading: true + }, + errorMessage: formErrors.issuesDiscovered, + value: formData.issuesDiscovered + }) }} +
    + {{ govukButton({ + text: 'Continue', + preventDoubleClick: true + }) }} +
    + {% endcall %} {% endblock %} diff --git a/views/community/pages/check-your-answers.njk b/views/community/pages/check-your-answers.njk index 16dfc7cf9..afedcd928 100644 --- a/views/community/pages/check-your-answers.njk +++ b/views/community/pages/check-your-answers.njk @@ -1,42 +1,68 @@ {% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} -{% from "macros/summary-card.njk" import summaryCard %} {% from "govuk/components/button/macro.njk" import govukButton %} +{% from "macros/summary-card.njk" import summaryCard %} +{% from "macros/page-title.njk" import pageTitle %} +{% from "macros/form.njk" import form %} + {% extends "base.njk" %} + +{% block beforeContent %} + +{% endblock %} + {% block content %}
    -
    - Submit a new component -

    Check your answers

    + {{ pageTitle({ + caption: 'Submit a new component', + title: 'Check your answers' + })}} - {% for section in sections %} - {% set cardParams = { - title: { - text: section.title - }, - actions: { - items: section.actions - } + {% for section in sections %} + {% set cardParams = { + title: { + text: section.title + }, + actions: { + items: section.actions } - %} - {% if section.answerRows.length > 0 %} - {{ govukSummaryList({ - card:cardParams, - rows: section.answerRows - }) }} - {% else %} - {% call summaryCard(cardParams) -%} -

    None

    - {%- endcall %} - {% endif %} - {% endfor %} + } + %} - + {% if section.answerRows.length > 0 %} + {{ govukSummaryList({ + card:cardParams, + rows: section.answerRows + }) }} + {% else %} + {% call summaryCard(cardParams) -%} +

    None

    + {%- endcall %} + {% endif %} + {% endfor %} -
    + {% call form({ + csrfToken: csrfToken, + action: submitUrl + }) %} +
    {{ govukButton({ text: 'Accept and send' }) }} + {% if isDev %} + {{ govukButton({ + text: "Generate markdown", + classes: "govuk-button--secondary", + href: generateLink + }) }} + {% endif %}
    - + {% endcall %}
    {% endblock %} diff --git a/views/community/pages/component-code-details.njk b/views/community/pages/component-code-details.njk index 641df4f21..4443bbc30 100644 --- a/views/community/pages/component-code-details.njk +++ b/views/community/pages/component-code-details.njk @@ -1,29 +1,18 @@ {% from "govuk/components/input/macro.njk" import govukInput %} {% from "govuk/components/radios/macro.njk" import govukRadios %} {% from "govuk/components/character-count/macro.njk" import govukCharacterCount %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% extends "base.njk" %} -{% block content %} -
    - {% if backLink %} - {{ govukBackLink({ - text: "Back", - href: backLink - }) }} - {% endif %} - {% if errorList %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorList - }) }} - {% endif %} -
    - Submit a new component -

    Component code

    -

    You should only send the component code. It should not contain any personally identifiable or sensitive information. You can add more than one code example.

    - {% set otherHtml %} + +{% extends "contributions.njk" %} + +{% block form %} + {% call form({ + action: submitUrl, + csrfToken: csrfToken + }) %} + +

    You should only send the component code. It should not contain any personally identifiable or sensitive information. You can add more than one code example.

    + + {% set otherHtml %} {{ govukInput({ id: "component-code-language-other", name: "componentCodeLanguageOther", @@ -35,94 +24,91 @@ text: page.fields.componentCodeLanguageOther.label } }) }} - {% endset -%} - {{ govukRadios({ - id: "component-code-language", - name: "componentCodeLanguage", - errorMessage: formErrors.componentCodeLanguage, - value: formData.componentCodeLanguage, - hint: { - text: page.fields.componentCodeLanguage.hint - }, - fieldset: { - legend: { - text: page.fields.componentCodeLanguage.label, - isPageHeading: true, - classes: "govuk-fieldset__legend--l" - } - }, - items: [ - { - value: "html", - text: "HTML" - }, - { - value: "nunjucks", - text: "Nunjucks" - }, - { - value: "css", - text: "CSS" - }, - { - value: "javascript", - text: "JavaScript" - }, - { - value: "other", - text: "Other", - conditional: { - html: otherHtml - } - } - ] - }) }} + {% endset -%} - {{ govukCharacterCount({ - name: "componentCode", - id: "component-code", - maxlength: 10000, - classes: 'font-monospace', - rows: 30, - label: { - text: "Add the code", - classes: "govuk-label--m" + {{ govukRadios({ + id: "component-code-language", + name: "componentCodeLanguage", + errorMessage: formErrors.componentCodeLanguage, + value: formData.componentCodeLanguage, + hint: { + text: page.fields.componentCodeLanguage.hint + }, + fieldset: { + legend: { + text: page.fields.componentCodeLanguage.label, + isPageHeading: true, + classes: "govuk-fieldset__legend--l" + } + }, + items: [ + { + value: "html", + text: "HTML" }, - errorMessage: formErrors.componentCode, - value: formData.componentCode - }) }} - - {{ govukCharacterCount({ - name: "componentCodeUsage", - id: "component-code-usage", - maxwords: 250, - label: { - text: page.fields.componentCodeUsage.label, - classes: "govuk-label--m" + { + value: "nunjucks", + text: "Nunjucks" }, - hint: { - text: page.fields.componentCodeUsage.hint + { + value: "css", + text: "CSS" }, - errorMessage: formErcomponentCodeLanguages.componentCodeUsage, - value: formData.componentCodeUsage - }) }} + { + value: "javascript", + text: "JavaScript" + }, + { + value: "other", + text: "Other", + conditional: { + html: otherHtml + } + } + ] + }) }} - + {{ govukCharacterCount({ + name: "componentCode", + id: "component-code", + maxlength: 10000, + classes: 'font-monospace', + rows: 30, + label: { + text: "Add the code", + classes: "govuk-label--m" + }, + errorMessage: formErrors.componentCode, + value: formData.componentCode + }) }} -
    - {{ govukButton({ - text: 'Continue', - preventDoubleClick: true - }) }} - {% if showAddAnother %} + {{ govukCharacterCount({ + name: "componentCodeUsage", + id: "component-code-usage", + maxwords: 250, + label: { + text: page.fields.componentCodeUsage.label, + classes: "govuk-label--m" + }, + hint: { + text: page.fields.componentCodeUsage.hint + }, + errorMessage: formErcomponentCodeLanguages.componentCodeUsage, + value: formData.componentCodeUsage + }) }} +
    + {{ govukButton({ + text: 'Continue', + preventDoubleClick: true + }) }} + {% if showAddAnother %} {{ govukButton({ text: "Add another code example", classes: "govuk-button--secondary", name: "addAnother" }) }} - {% endif %} -
    - -
    + {% endif %} +
    + {% endcall %} {% endblock %} diff --git a/views/community/pages/component-code.njk b/views/community/pages/component-code.njk index f045a0ec4..355036a8c 100644 --- a/views/community/pages/component-code.njk +++ b/views/community/pages/component-code.njk @@ -1,60 +1,43 @@ {% from "govuk/components/radios/macro.njk" import govukRadios %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% extends "base.njk" %} -{% block content %} -
    - {% if backLink %} - {{ govukBackLink({ - text: "Back", - href: backLink - }) }} - {% endif %} - {% if errorList %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorList - }) }} - {% endif %} -
    - Submit a new component -

    Component code

    -

    You should only send the component code and not the full page of code. This ensures it's easy for others to understand and use. The code should not contain any personally identifiable or sensitive information.

    - {{ govukRadios({ - name: "componentCodeAvailable", - fieldset: { - attributes: { - id: "component-code-available" - }, - legend: { - text: page.fields.componentCodeAvailable.label, - classes: "govuk-fieldset__legend--m" - } - }, - items: [ - { - value: "yes", - text: "Yes" - }, - { - value: "no", - text: "No" - } - ], - errorMessage: formErrors.componentCodeAvailable, - value: formData.componentCodeAvailable - }) }} - +{% extends "contributions.njk" %} -
    - {{ govukButton({ - text: 'Continue', - preventDoubleClick: true - }) }} +{% block form %} + {% call form({ + action: submitUrl, + csrfToken: csrfToken + }) %} - -
    +

    You should only send the component code and not the full page of code. This ensures it's easy for others to understand and use. The code should not contain any personally identifiable or sensitive information.

    + {{ govukRadios({ + name: "componentCodeAvailable", + fieldset: { + attributes: { + id: "component-code-available" + }, + legend: { + text: page.fields.componentCodeAvailable.label, + classes: "govuk-fieldset__legend--m" + } + }, + items: [ + { + value: "yes", + text: "Yes" + }, + { + value: "no", + text: "No" + } + ], + errorMessage: formErrors.componentCodeAvailable, + value: formData.componentCodeAvailable + }) }} +
    + {{ govukButton({ + text: 'Continue', + preventDoubleClick: true + }) }} + {% endcall %} {% endblock %} diff --git a/views/community/pages/component-details.njk b/views/community/pages/component-details.njk index 811ec9ee9..b20761ab0 100644 --- a/views/community/pages/component-details.njk +++ b/views/community/pages/component-details.njk @@ -1,71 +1,55 @@ {% from "govuk/components/input/macro.njk" import govukInput %} {% from "govuk/components/character-count/macro.njk" import govukCharacterCount %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% extends "base.njk" %} -{% block content %} -
    - {% if backLink %} - {{ govukBackLink({ - text: "Back", - href: backLink - }) }} - {% endif %} - {% if errorList %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorList - }) }} - {% endif %} -
    - Submit a new component -

    Add details about the component

    - {{ govukInput({ - label: { - text: page.fields.componentName.label, - classes: "govuk-label--m" - }, - id: "component-name", - name: "componentName", - errorMessage: formErrors.componentName, - value: formData.componentName - }) }} +{% extends "contributions.njk" %} - {{ govukCharacterCount({ - name: "componentOverview", - id: "component-overview", - maxwords: 250, - label: { - text: page.fields.componentOverview.label, - classes: "govuk-label--m" - }, - errorMessage: formErrors.componentOverview, - value: formData.componentOverview - }) }} +{% block form %} + {% call form({ + action: submitUrl, + csrfToken: csrfToken + }) %} - {{ govukCharacterCount({ - name: "howIsTheComponentUsed", - id: "how-is-the-component-used", - maxwords: 250, - label: { - text: page.fields.howIsTheComponentUsed.label, - classes: "govuk-label--m" - }, + {{ govukInput({ + label: { + text: page.fields.componentName.label, + classes: "govuk-label--m" + }, + id: "component-name", + name: "componentName", + errorMessage: formErrors.componentName, + value: formData.componentName + }) }} - errorMessage: formErrors.howIsTheComponentUsed, - value: formData.howIsTheComponentUsed - }) }} + {{ govukCharacterCount({ + name: "componentOverview", + id: "component-overview", + maxwords: 250, + label: { + text: page.fields.componentOverview.label, + classes: "govuk-label--m" + }, + errorMessage: formErrors.componentOverview, + value: formData.componentOverview + }) }} - + {{ govukCharacterCount({ + name: "howIsTheComponentUsed", + id: "how-is-the-component-used", + maxwords: 250, + label: { + text: page.fields.howIsTheComponentUsed.label, + classes: "govuk-label--m" + }, -
    - {{ govukButton({ - text: 'Continue', - preventDoubleClick: true - }) }} - -
    + errorMessage: formErrors.howIsTheComponentUsed, + value: formData.howIsTheComponentUsed + }) }} +
    + {{ govukButton({ + text: 'Continue', + preventDoubleClick: true + }) }} +
    + {% endcall %} {% endblock %} diff --git a/views/community/pages/component-image.njk b/views/community/pages/component-image.njk index 7f641fdb4..fbdd3b52e 100644 --- a/views/community/pages/component-image.njk +++ b/views/community/pages/component-image.njk @@ -1,101 +1,76 @@ {% from "govuk/components/file-upload/macro.njk" import govukFileUpload %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} {% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} {% from "govuk/components/tag/macro.njk" import govukTag %} -{% from "moj/components/alert/macro.njk" import mojAlert %} -{% extends "base.njk" %} +{% extends "contributions.njk" %} {% set uploadedFileName = formData.componentImage.originalname %} -{% block content %} -
    - {% if backLink %} - {{ govukBackLink({ - text: "Back", - href: backLink - }) }} - {% endif %} - {% if errorList %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorList - }) }} - {% endif %} - {% if sessionFlash.message%} - {{ mojAlert({ - variant: sessionFlash.type, - title: sessionFlash.message, - role: "alert" - })}} - {% endif %} -
    - Submit a new component -

    Upload an image of the component

    -

    Ensure that the component is clear from the image. For example, remove other parts of the page and ensure it's on a white background.

    +{% block form %} + {% call form({ + action: submitUrl, + csrfToken: csrfToken, + attributes: { + enctype: "multipart/form-data" + } + }) %} +

    Ensure that the component is clear from the image. For example, remove other parts of the page and ensure it's on a white background.

    + {% if uploadedFileName %} + + {{ govukSummaryList({ + classes: 'govuk-summary-list--long-key', + rows: [ + { + key: { + text: "File" + }, + value: { + text: uploadedFileName + }, + actions: { + items: [ + { + href: changeUrl, + text: "Remove", + visuallyHiddenText: uploadedFileName + } + ] + } + } + ] + }) }} + {% else %} + {{ govukFileUpload({ + id: "component-image", + name: "componentImage", + label: { + text: page.fields.componentImage.label, + classes: "govuk-label--m" + }, + hint: { + text: page.fields.componentImage.hint + }, + attributes: { + accept: ".jpg, .jpeg, .bmp, .png, .tif, .tiff, .pdf" + }, + errorMessage: formErrors.componentImage + }) }} + {% endif %} +
    {% if uploadedFileName %} - - {{ govukSummaryList({ - classes: 'govuk-summary-list--long-key', - rows: [ - { - key: { - text: "File" - }, - value: { - text: uploadedFileName - }, - actions: { - items: [ - { - href: changeUrl, - text: "Remove", - visuallyHiddenText: uploadedFileName - } - ] - } - } - ] - }) }} + {{ govukButton({ + text: 'Continue', + preventDoubleClick: true + }) }} {% else %} - {{ govukFileUpload({ - id: "component-image", - name: "componentImage", - label: { - text: page.fields.componentImage.label, - classes: "govuk-label--m" - }, - hint: { - text: page.fields.componentImage.hint - }, - attributes: { - accept: ".jpg, .jpeg, .bmp, .png, .tif, .tiff, .pdf" - }, - errorMessage: formErrors.componentImage + {{ govukButton({ + text: 'Upload', + preventDoubleClick: true }) }} {% endif %} - - - - -
    - {% if uploadedFileName %} - {{ govukButton({ - text: 'Continue', - preventDoubleClick: true - }) }} - {% else %} - {{ govukButton({ - text: 'Upload', - preventDoubleClick: true - }) }} - {% endif %} -
    - -
    +
    + {% endcall %} {% endblock %} diff --git a/views/community/pages/confirmation.njk b/views/community/pages/confirmation.njk index 5b4f29639..3f09e3f3f 100644 --- a/views/community/pages/confirmation.njk +++ b/views/community/pages/confirmation.njk @@ -1,42 +1,46 @@ {% from "govuk/components/button/macro.njk" import govukButton %} {% from "govuk/components/notification-banner/macro.njk" import govukNotificationBanner %} -{% extends "base.njk" %} -{% block content %} -
    -
    +{% extends "base.njk" %} -
    - Submit a new component +{% block beforeContent %} + +{% endblock %} - {% set html %} -

    - You've submitted a component -

    - {% endset %} +{% block content %} +
    + Submit a new component - {{ govukNotificationBanner({ - classes: "govuk-!-margin-top-5", - html: html, - titleText: "Component received", - type: "success" - }) }} + {% set html %} +

    + You've submitted a component +

    + {% endset %} -

    What happens next

    + {{ govukNotificationBanner({ + classes: "govuk-!-margin-top-5", + html: html, + titleText: "Component received", + type: "success" + }) }} -

    The Design System team reviews the component

    -

    The team will give the component a sense check.

    -

    If there are no queries, it'll be added to the Design System within 10 working days. It will have an experimental status, which means that it's early in development. You can read more about the MoJ Design System statuses.

    -

    You'll get an email when the component is live.

    +

    What happens next

    -

    Collaborating on the component

    -

    Each community component will have a Github discussion. This enables it to continue to develop through discussions and contributions. Later in 2025, people in MoJ and UK government will be able to use the documentation site to add to the component.

    +

    The Design System team reviews the component

    +

    The team will give the component a sense check.

    +

    If there are no queries, it'll be added to the Design System within 10 working days. It will have an experimental status, which means that it's early in development. You can read more about the MoJ Design System statuses.

    +

    You'll get an email when the component is live.

    -

    - Back to the Design System homepage -

    -
    +

    Collaborating on the component

    +

    Each community component will have a Github discussion. This enables it to continue to develop through discussions and contributions. Later in 2025, people in MoJ and UK government will be able to use the documentation site to add to the component.

    -
    +

    Back to the Design System homepage

    {% endblock %} diff --git a/views/community/pages/email-check.njk b/views/community/pages/email-check.njk new file mode 100644 index 000000000..ef77f104f --- /dev/null +++ b/views/community/pages/email-check.njk @@ -0,0 +1,11 @@ +{% from "govuk/components/details/macro.njk" import govukDetails %} + +{% extends "contributions.njk" %} + +{% block form %} +

    We've sent an email to: {{page.email}}

    +

    Select the link in the email to continue submitting a component. The link will open in a new window, so you can close this one.

    + {% call govukDetails({ summaryText: "Problems with the email?" }) %} + We can send the email again or you can use a different email address. + {% endcall %} +{% endblock %} diff --git a/views/community/pages/email-not-allowed.njk b/views/community/pages/email-not-allowed.njk new file mode 100644 index 000000000..c377a9632 --- /dev/null +++ b/views/community/pages/email-not-allowed.njk @@ -0,0 +1,5 @@ +{% extends "contributions.njk" %} +{% block form %} +

    You need to work at MoJ to contribute to the MoJ Design System.

    +

    Go back to the MoJ Design System

    +{% endblock %} diff --git a/views/community/pages/email-resend.njk b/views/community/pages/email-resend.njk new file mode 100644 index 000000000..3b5753eb9 --- /dev/null +++ b/views/community/pages/email-resend.njk @@ -0,0 +1,26 @@ +{% extends "contributions.njk" %} + +{% block form %} + {% call form({ + action: submitUrl, + csrfToken: csrfToken + }) %} +
      +
    1. Check that you entered your email address correctly: {{page.email}}
    2. +
    3. Check your junk or spam folder.
    4. +
    5. Wait 5 minutes for it to arrive.
    6. +
    +

    If you’ve still not received the verification email after step 2 you can either:

    + + +
    + {{ govukButton({ + text: 'Resend verification email' + }) }} +
    +

    If none of this works, contact us + {% endcall %} +{% endblock %} diff --git a/views/community/pages/email.njk b/views/community/pages/email.njk new file mode 100644 index 000000000..b2fa6070f --- /dev/null +++ b/views/community/pages/email.njk @@ -0,0 +1,37 @@ +{% from "govuk/components/input/macro.njk" import govukInput %} + +{% extends "contributions.njk" %} + +{% block form %} + {% call form({ + action: submitUrl, + csrfToken: csrfToken + }) %} + +

    You need to work for MoJ to submit a new component to the Design System.

    + + {{ govukInput({ + id: "email-address", + name: "emailAddress", + hint: { + text: page.fields.emailAddress.hint + }, + errorMessage: formErrors.emailAddress, + value: formData.emailAddress, + attributes: { + 'aria-labelledby': 'page-title' + }, + formGroup: { + classes: 'govuk-!-margin-top-0' + } + }) }} + +

    You’ll be sent an email with a verification link.

    + +
    + {{ govukButton({ + text: 'Continue' + }) }} +
    + {% endcall %} +{% endblock %} diff --git a/views/community/pages/figma-link.njk b/views/community/pages/figma-link.njk index 3670e7c38..72e12e338 100644 --- a/views/community/pages/figma-link.njk +++ b/views/community/pages/figma-link.njk @@ -1,64 +1,49 @@ {% from "govuk/components/input/macro.njk" import govukInput %} {% from "govuk/components/character-count/macro.njk" import govukCharacterCount %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% extends "base.njk" %} -{% block content %} -
    - {% if backLink %} - {{ govukBackLink({ - text: "Back", - href: backLink - }) }} - {% endif %} - {% if errorList %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorList - }) }} - {% endif %} -
    - Submit a new component -

    Figma design

    - {{ govukInput({ - hint: { - text: page.fields.figmaLink.hint - }, - label: { - text: page.fields.figmaLink.label, - classes: "govuk-label--m" - }, - id: "figma-link", - name: "figmaLink", - errorMessage: formErrors.figmaLink, - value: formData.figmaLink, - autocapitalize: false - }) }} - {{ govukCharacterCount({ - name: "figmaLinkAdditionalInformation", - id: "figmaLink-additional-information", - maxwords: 250, - hint: { - text: page.fields.figmaLinkAdditionalInformation.hint - }, - label: { - text: page.fields.figmaLinkAdditionalInformation.label, - classes: "govuk-label--m" - }, - errorMessage: formErrors.figmaLinkAdditionalInformation, - value: formData.figmaLinkAdditionalInformation - }) }} +{% extends "contributions.njk" %} - +{% block form %} + {% call form({ + action: submitUrl, + csrfToken: csrfToken + }) %} -
    - {{ govukButton({ - text: 'Continue', - preventDoubleClick: true - }) }} -
    -
    -
    + {{ govukInput({ + hint: { + text: page.fields.figmaLink.hint + }, + label: { + text: page.fields.figmaLink.label, + classes: "govuk-label--m" + }, + id: "figma-link", + name: "figmaLink", + errorMessage: formErrors.figmaLink, + value: formData.figmaLink, + autocapitalize: false + }) }} + + {{ govukCharacterCount({ + name: "figmaLinkAdditionalInformation", + id: "figmaLink-additional-information", + maxwords: 250, + hint: { + text: page.fields.figmaLinkAdditionalInformation.hint + }, + label: { + text: page.fields.figmaLinkAdditionalInformation.label, + classes: "govuk-label--m" + }, + errorMessage: formErrors.figmaLinkAdditionalInformation, + value: formData.figmaLinkAdditionalInformation + }) }} + +
    + {{ govukButton({ + text: 'Continue', + preventDoubleClick: true + }) }} +
    + {% endcall %} {% endblock %} diff --git a/views/community/pages/figma.njk b/views/community/pages/figma.njk index 44649210d..43579076b 100644 --- a/views/community/pages/figma.njk +++ b/views/community/pages/figma.njk @@ -1,58 +1,43 @@ {% from "govuk/components/radios/macro.njk" import govukRadios %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% extends "base.njk" %} -{% block content %} -
    - {% if backLink %} - {{ govukBackLink({ - text: "Back", - href: backLink - }) }} - {% endif %} - {% if errorList %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorList - }) }} - {% endif %} -
    - Submit a new component -

    Figma design

    - {{ govukRadios({ - name: "figmaUrl", - fieldset: { - attributes: { - id: "figma-url" - }, - legend: { - text: page.fields.figmaUrl.label, - classes: "govuk-fieldset__legend--m" - } - }, - items: [ - { - value: "yes", - text: "Yes" - }, - { - value: "no", - text: "No" - } - ], - errorMessage: formErrors.figmaUrl, - value: formData.figmaUrl - }) }} - +{% extends "contributions.njk" %} -
    - {{ govukButton({ - text: 'Continue', - preventDoubleClick: true - }) }} +{% block form %} + {% call form({ + action: submitUrl, + csrfToken: csrfToken + }) %} - -
    + {{ govukRadios({ + name: "figmaUrl", + fieldset: { + attributes: { + id: "figma-url" + }, + legend: { + text: page.fields.figmaUrl.label, + classes: "govuk-fieldset__legend--m" + } + }, + items: [ + { + value: "yes", + text: "Yes" + }, + { + value: "no", + text: "No" + } + ], + errorMessage: formErrors.figmaUrl, + value: formData.figmaUrl + }) }} + +
    + {{ govukButton({ + text: 'Continue', + preventDoubleClick: true + }) }} +
    + {% endcall %} {% endblock %} diff --git a/views/community/pages/prototype-url.njk b/views/community/pages/prototype-url.njk deleted file mode 100644 index de5e8f83e..000000000 --- a/views/community/pages/prototype-url.njk +++ /dev/null @@ -1,77 +0,0 @@ -{% from "govuk/components/input/macro.njk" import govukInput %} -{% from "govuk/components/character-count/macro.njk" import govukCharacterCount %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% extends "base.njk" %} -{% block content %} -
    - {% if backLink %} - {{ govukBackLink({ - text: "Back", - href: backLink - }) }} - {% endif %} - {% if errorList %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorList - }) }} - {% endif %} -
    - Submit a new component -

    Prototype designs

    -

    Before sharing a prototype you need to:

    -
      -
    • make the link shareable
    • -
    • remove any personally identifiable or sensitive information
    • -
    - - {{ govukInput({ - label: { - text: page.fields.prototypeUrl.label, - classes: "govuk-label--m" - }, - id: "prototype-url", - name: "prototypeUrl", - errorMessage: formErrors.prototypeUrl, - value: formData.prototypeUrl, - autocapitalize: false, - hint: { - text: page.fields.prototypeUrl.hint - } - }) }} - - {{ govukCharacterCount({ - name: "prototypeUrlAdditionalInformation", - id: "prototypeUrl-additional-information", - maxwords: 250, - hint: { - text: page.fields.prototypeUrlAdditionalInformation.hint - }, - label: { - text: page.fields.prototypeUrlAdditionalInformation.label, - classes: "govuk-label--m" - }, - errorMessage: formErrors.prototypeUrlAdditionalInformation, - value: formData.prototypeUrlAdditionalInformation - }) }} - - - -
    - {{ govukButton({ - text: 'Continue', - preventDoubleClick: true - }) }} - {% if showAddAnother %} - {{ govukButton({ - text: "Add another link", - classes: "govuk-button--secondary", - name: "addAnother" - }) }} - {% endif %} -
    -
    -
    -{% endblock %} diff --git a/views/community/pages/prototype.njk b/views/community/pages/prototype.njk deleted file mode 100644 index 5137c56b2..000000000 --- a/views/community/pages/prototype.njk +++ /dev/null @@ -1,64 +0,0 @@ -{% from "govuk/components/radios/macro.njk" import govukRadios %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% extends "base.njk" %} -{% block content %} -
    - {% if backLink %} - {{ govukBackLink({ - text: "Back", - href: backLink - }) }} - {% endif %} - {% if errorList %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorList - }) }} - {% endif %} -
    - Submit a new component -

    Add a prototype

    -

    A prototype showing the component helps the Design System team to review the component.

    - - {{ govukRadios({ - name: "componentPrototypeUrl", - hint: { - text: page.fields.componentPrototypeUrl.hint - }, - fieldset: { - attributes: { - id: "component-prototype-url" - }, - legend: { - text: page.fields.componentPrototypeUrl.label, - isPageHeading: true, - classes: "govuk-fieldset__legend--m" - } - }, - items: [ - { - value: "yes", - text: "Yes" - }, - { - value: "no", - text: "No" - } - ], - errorMessage: formErrors.componentPrototypeUrl, - value: formData.componentPrototypeUrl - }) }} - - - -
    - {{ govukButton({ - text: 'Continue', - preventDoubleClick: true - }) }} - - -
    -{% endblock %} diff --git a/views/community/pages/remove.njk b/views/community/pages/remove.njk index d5e6b172e..4a4ece462 100644 --- a/views/community/pages/remove.njk +++ b/views/community/pages/remove.njk @@ -1,37 +1,36 @@ -{% from "govuk/components/button/macro.njk" import govukButton %} {% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% extends "base.njk" %} -{% block content %} -
    - {% if backLink %} - {{ govukBackLink({ - text: "Back", - href: backLink - }) }} - {% endif %} -
    - Submit a new component -

    Are you sure you want to remove this information?

    - {{ govukSummaryList({ - rows: summaryRows - }) }} +{% extends "contributions.njk" %} + +{% block pageTitle %} + {{ pageTitle({ + caption: 'Submit a new component', + title: "Are you sure you want to remove this information?" + })}} +{% endblock %} - +{% block form %} + {% call form({ + action: submitUrl, + csrfToken: csrfToken + }) %} -
    - {{ govukButton({ - text: 'Delete answers', - preventDoubleClick: true, - classes: "govuk-button--warning" - }) }} + {{ govukSummaryList({ + rows: summaryRows + }) }} - {{ govukButton({ - text: "Cancel", - classes: "govuk-button--secondary", - href: backLink - }) }} - -
    +
    + {{ govukButton({ + text: 'Delete answers', + preventDoubleClick: true, + classes: "govuk-button--warning" + }) }} + + {{ govukButton({ + text: "Cancel", + classes: "govuk-button--secondary", + href: backLink + }) }} +
    + {% endcall %} {% endblock %} diff --git a/views/community/pages/start.njk b/views/community/pages/start.njk index 2d5c209af..1dbcf94df 100644 --- a/views/community/pages/start.njk +++ b/views/community/pages/start.njk @@ -1,31 +1,39 @@ {% from "govuk/components/button/macro.njk" import govukButton %} +{% from "macros/page-title.njk" import pageTitle %} +{% from "macros/form.njk" import form %} + {% extends "base.njk" %} +{% block beforeContent %} + +{% endblock %} {% block content %}
    - Submit a new component -

    Before you start

    - -

    You can now add a component to the MoJ Design System. You might want to do this to: - -

      + {{ pageTitle({ + caption: 'Submit a new component', + title: 'Before you start' + })}} +

      Anyone at MoJ can now add a component to the MoJ Design System. You might want to do this to: -

    • make it easier for teams to find your work
    • -
    • get contributions from other people to help the component develop
    • -
    • help your team meet the Government Design Principles
    • -
    -

    - -
    -Later in 2025 you'll also be able to add a pattern or page to the MoJ Design System. -
    +
      +
    • make it easier for teams to find your work
    • +
    • get contributions from other people across government to help the component develop
    • +
    • help your team meet the Government Design Principles
    • +
    -

    You can add a component that has not been thoroughly developed or tested. If you have an image and you can briefly describe it, it’s ready to be added.

    +

    You can add a component that has not been thoroughly developed or tested. If you have an image and you can briefly describe it, it’s ready to be added.

    What you'll need

    -

    To submit a component to the Design System you'll need: +

    To submit a component to the Design System you'll need:

    • the component name
    • @@ -36,21 +44,18 @@ Later in 2025 you'll also be able to add a pattern or page to the MoJ Design Sys

      This helps people to understand what the component is and how to use it.

      -

      You’ll be asked for your details. This is so that you can be contacted when the component is live or if there queries. You can ask for your name or team name to be added to the documentation, but this is optional.

      +

      You’ll be asked for your email address to verify that you work for MoJ. We'll also use this to contact you when the component is live, or if there queries. You can ask for your name or team name to be added to the documentation, but this is optional.

      After you submit a component

      -

      The MoJ Design System team will briefly check the component and then it'll be live within 10 working days. People may add comments to the component's Github discussion. -

      - -
      - +

      The MoJ Design System team will briefly check the component and then it'll be live within 10 working days. People may add comments to the component's Github discussion.

      -
      - {{ govukButton({ - text: 'Continue' - }) }} -
      -
      + {% call form({csrfToken: csrfToken}) %} +
      + {{ govukButton({ + text: 'Continue' + }) }} +
      + {% endcall %}
    {% endblock %} diff --git a/views/community/pages/your-details.njk b/views/community/pages/your-details.njk index 26b595d6c..c37c2b8d9 100644 --- a/views/community/pages/your-details.njk +++ b/views/community/pages/your-details.njk @@ -1,9 +1,7 @@ {% from "govuk/components/input/macro.njk" import govukInput %} {% from "govuk/components/checkboxes/macro.njk" import govukCheckboxes %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% extends "base.njk" %} + +{% extends "contributions.njk" %} {% set shareYourDetails = [] %} @@ -18,96 +16,82 @@ {% set addNameToComponentPage = "addNameToComponentPage" in shareYourDetails %} {% set addTeamNameToComponentPage = "addTeamNameToComponentPage" in shareYourDetails %} -{% block content %} +{% block form %} + {% call form({ + action: submitUrl, + csrfToken: csrfToken + }) %} -
    - {% if backLink %} - {{ govukBackLink({ - text: "Back", - href: backLink - }) }} - {% endif %} - {% if errorList %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorList - }) }} - {% endif %} -
    - Submit a new component -

    Your details

    -

    Add your details to enable the MoJ Design System team to let you know when the component is live. They might also need to contact you with questions about the component.

    -

    You can request for your details to be added to the component page. This is to show that you added it to the Design System.

    - {{ govukInput({ - label: { - text: page.fields.fullName.label, - classes: "govuk-label--m" - }, - id: "full-name", - name: "fullName", - errorMessage: formErrors.fullName, - value: formData.fullName - }) }} +

    Add your details to enable the MoJ Design System team to let you know when the component is live. They might also need to contact you with questions about the component.

    +

    You can request for your details to be added to the component page. This is to show that you added it to the Design System.

    - {{ govukInput({ - label: { - text: page.fields.emailAddress.label, - classes: "govuk-label--m" - }, - id: "email-address", - name: "emailAddress", - errorMessage: formErrors.emailAddress, - value: formData.emailAddress - }) }} + {{ govukInput({ + label: { + text: page.fields.fullName.label, + classes: "govuk-label--m" + }, + id: "full-name", + name: "fullName", + errorMessage: formErrors.fullName, + value: formData.fullName + }) }} - {{ govukInput({ + {{ govukInput({ label: { - text: page.fields.teamName.label, - classes: "govuk-label--m" + text: page.fields.emailAddress.label, + classes: "govuk-label--m" }, - id: "team-name", - name: "teamName", - errorMessage: formErrors.teamName, - value: formData.teamName - }) }} + id: "email-address", + name: "emailAddress", + errorMessage: formErrors.emailAddress, + value: formData.emailAddress + }) }} - + {{ govukInput({ + label: { + text: page.fields.teamName.label, + classes: "govuk-label--m" + }, + id: "team-name", + name: "teamName", + errorMessage: formErrors.teamName, + value: formData.teamName + }) }} - {{ govukCheckboxes({ - name: "shareYourDetails", - id: 'share-your-details', - fieldset: { - legend: { - text: page.fields.shareYourDetails.label, - classes: "govuk-fieldset__legend--m" - } - }, - hint: { - text: page.fields.shareYourDetails.hint - }, - items: [ - { - value: "addNameToComponentPage", - text: "Add my name to the component page", - checked: addNameToComponentPage - }, - { - value: "addTeamNameToComponentPage", - text: "Add my team name to the component page", - checked: addTeamNameToComponentPage - } - ], - errorMessage: formErrors.shareYourDetails, - value: formData.shareYourDetails - }) }} + - + {{ govukCheckboxes({ + name: "shareYourDetails", + id: 'share-your-details', + fieldset: { + legend: { + text: page.fields.shareYourDetails.label, + classes: "govuk-fieldset__legend--m" + } + }, + hint: { + text: page.fields.shareYourDetails.hint + }, + items: [ + { + value: "addNameToComponentPage", + text: "Add my name to the component page", + checked: addNameToComponentPage + }, + { + value: "addTeamNameToComponentPage", + text: "Add my team name to the component page", + checked: addTeamNameToComponentPage + } + ], + errorMessage: formErrors.shareYourDetails, + value: formData.shareYourDetails + }) }} -
    - {{ govukButton({ - text: 'Continue' - }) }} -
    -
    -
    +
    + {{ govukButton({ + text: 'Continue' + }) }} +
    + {% endcall %} {% endblock %}