From a2add05950316bbb606838d66a0d72b1534c7597 Mon Sep 17 00:00:00 2001 From: Nick Colley Date: Thu, 14 Feb 2019 15:05:22 +0000 Subject: [PATCH] Add feedback page Includes full form lifecycle with error validation and confirmation page --- app/__tests__/app.test.js | 36 ++++++ app/app.js | 2 + .../feedback-page/confirm.njk | 19 +++ .../full-page-examples/feedback-page/index.js | 76 +++++++++++ .../feedback-page/index.njk | 121 ++++++++++++++++++ package-lock.json | 24 ++++ package.json | 1 + 7 files changed, 279 insertions(+) create mode 100644 app/views/full-page-examples/feedback-page/confirm.njk create mode 100644 app/views/full-page-examples/feedback-page/index.js create mode 100644 app/views/full-page-examples/feedback-page/index.njk diff --git a/app/__tests__/app.test.js b/app/__tests__/app.test.js index da4ac7186d..de173070cf 100644 --- a/app/__tests__/app.test.js +++ b/app/__tests__/app.test.js @@ -20,6 +20,7 @@ const expectedPages = [ '/examples/template-custom', '/full-page-examples/bank-holidays', '/full-page-examples/check-your-answers', + '/full-page-examples/feedback-page', '/full-page-examples/service-manual-topic', '/full-page-examples/start-page' ] @@ -104,6 +105,41 @@ describe(`http://localhost:${PORT}`, () => { }) }) + describe('/full-page-examples/feedback-page', () => { + it('should not show errors if submit with no input', (done) => { + request.get({ + url: `http://localhost:${PORT}/full-page-examples/feedback-page` + }, (err, res) => { + let $ = cheerio.load(res.body) + + // Check the page responded correctly + expect(res.statusCode).toBe(200) + expect($.html()).toContain('Send your feedback to this service') + + // Check that the error summary is not visible + let $errorSummary = $('[data-module="error-summary"]') + expect($errorSummary.length).toBeFalsy() + done(err) + }) + }) + it('should show errors if form is submitted with no input', (done) => { + request.post({ + url: `http://localhost:${PORT}/full-page-examples/feedback-page` + }, (err, res) => { + let $ = cheerio.load(res.body) + + // Check the page responded correctly + expect(res.statusCode).toBe(200) + expect($.html()).toContain('Send your feedback to this service') + + // Check that the error summary is visible + let $errorSummary = $('[data-module="error-summary"]') + expect($errorSummary.length).toBeTruthy() + done(err) + }) + }) + }) + describe('/examples/template-custom', () => { const templatePath = '/examples/template-custom' diff --git a/app/app.js b/app/app.js index 0853f06c0f..f1653ded13 100644 --- a/app/app.js +++ b/app/app.js @@ -181,6 +181,8 @@ module.exports = (options) => { }) }) + require('./views/full-page-examples/feedback-page')(app) + app.get('/robots.txt', function (req, res) { res.type('text/plain') res.send('User-agent: *\nDisallow: /') diff --git a/app/views/full-page-examples/feedback-page/confirm.njk b/app/views/full-page-examples/feedback-page/confirm.njk new file mode 100644 index 0000000000..a2ce40cba8 --- /dev/null +++ b/app/views/full-page-examples/feedback-page/confirm.njk @@ -0,0 +1,19 @@ +{# This example is based of the live "Send your feedback to GOV.UK Verify" start page: https://www.signin.service.gov.uk/feedback #} +{% extends "_generic.njk" %} + +{% from "panel/macro.njk" import govukPanel %} + +{% set pageTitle = "Thank you for your feedback" %} +{% block pageTitle %}GOV.UK - {{ pageTitle }}{% endblock %} + +{% set mainClasses = "govuk-main-wrapper--l" %} + +{% block content %} +
+
+ {{ govukPanel({ + titleText: pageTitle + }) }} +
+
+{% endblock %} \ No newline at end of file diff --git a/app/views/full-page-examples/feedback-page/index.js b/app/views/full-page-examples/feedback-page/index.js new file mode 100644 index 0000000000..3819a52404 --- /dev/null +++ b/app/views/full-page-examples/feedback-page/index.js @@ -0,0 +1,76 @@ +const { body, validationResult } = require('express-validator/check') + +// To make it easier to use in the view, might be nicer as a Nunjucks function +function getErrors (errorsInstance) { + if (errorsInstance.isEmpty()) { + return false + } + const errors = errorsInstance.array() + const formattedErrors = {} + errors.forEach(error => { + formattedErrors[error.param] = { + href: '#' + error.param, + value: error.value, + text: error.msg + } + }) + return formattedErrors +} + +module.exports = (app) => { + app.post( + '/full-page-examples/feedback-page', + [ + body('what-were-you-trying-to-do') + .exists() + .not().isEmpty().withMessage('Enter what you were trying to do') + .isLength({ max: 3000 }).withMessage('What were you trying to do must be 3000 characters or less'), + body('detail') + .exists() + .not().isEmpty().withMessage('Enter details of your question, problem or feedback') + .isLength({ max: 3000 }).withMessage('Details of your question, problem or feedback must be 3000 characters or less'), + body('do-you-want-a-reply') + .exists() + .not().isEmpty().withMessage('Select yes if you want a reply'), + body('name') + .custom((value, { req: request }) => { + // See https://github.com/express-validator/express-validator/pull/658 + const wantsReply = request.body['do-you-want-a-reply'] === 'yes' + if (!wantsReply) { + return true + } + if (!value) { + throw new Error('Enter your name') + } + return true + }), + body('email') + .custom((value, { req: request }) => { + // See https://github.com/express-validator/express-validator/pull/658 + const wantsReply = request.body['do-you-want-a-reply'] === 'yes' + if (!wantsReply) { + return true + } + if (!value) { + throw new Error('Enter your email address') + } + if (!value.includes('@')) { + throw new Error('Enter an email address in the correct format, like name@example.com') + } + return true + }) + + ], + (request, response) => { + const errors = getErrors(validationResult(request)) + if (errors) { + return response.render('./full-page-examples/feedback-page/index', { + errors, + errorSummary: Object.values(errors), + values: request.body // In production this should sanitized. + }) + } + response.render('./full-page-examples/feedback-page/confirm') + } + ) +} diff --git a/app/views/full-page-examples/feedback-page/index.njk b/app/views/full-page-examples/feedback-page/index.njk new file mode 100644 index 0000000000..9ed71eb25a --- /dev/null +++ b/app/views/full-page-examples/feedback-page/index.njk @@ -0,0 +1,121 @@ +{# This example is based of the live "Send your feedback to GOV.UK Verify" start page: https://www.signin.service.gov.uk/feedback #} +{% extends "_generic.njk" %} + +{% from "radios/macro.njk" import govukRadios %} +{% from "input/macro.njk" import govukInput %} +{% from "character-count/macro.njk" import govukCharacterCount %} +{% from "button/macro.njk" import govukButton %} +{% from "error-summary/macro.njk" import govukErrorSummary %} + +{% set pageTitle = "Send your feedback to this service" %} +{% block pageTitle %}GOV.UK - {{ pageTitle }}{% endblock %} + +{% set mainClasses = "govuk-main-wrapper--l" %} + +{% block content %} +
+
+
+ {% if errorSummary.length > 0 %} + {{ govukErrorSummary({ + titleText: "There is a problem", + errorList: errorSummary + }) }} + {% endif %} + +

{{ pageTitle }}

+ +

+ Use this form to ask a question, report a problem or suggest an improvement this service can make. +

+ +

+ Don't include any personal or financial information like your National Insurance number or credit card details. +

+ + {{ govukCharacterCount({ + name: "what-were-you-trying-to-do", + id: "what-were-you-trying-to-do", + maxlength: 3000, + label: { + text: "What were you trying to do?" + }, + value: values["what-were-you-trying-to-do"], + errorMessage: errors["what-were-you-trying-to-do"] + }) }} + + {{ govukCharacterCount({ + name: "detail", + id: "detail", + maxlength: 3000, + label: { + text: "Please provide details of your question, problem or feedback" + }, + value: values["detail"], + errorMessage: errors["detail"] + }) }} + + {% set yesHTML %} +

Leave your details below if you'd like a response from this service.

+ + {{ govukInput({ + id: "name", + name: "name", + label: { + text: "Name" + }, + value: values["name"], + errorMessage: errors["name"] + }) }} + + {{ govukInput({ + id: "email", + name: "email", + type: "email", + label: { + text: "Email address" + }, + hint: { + text: "We’ll only use this to reply to your message." + }, + value: values["email"], + errorMessage: errors["email"] + }) }} + +

By sending this message, you consent to us using your information as detailed in the privacy notice.

+ {% endset -%} + + {{ govukRadios({ + idPrefix: "do-you-want-a-reply", + name: "do-you-want-a-reply", + fieldset: { + legend: { + text: "Do you want a reply?" + } + }, + items: [ + { + id: "do-you-want-a-reply", + value: "yes", + text: "Yes", + checked: values["do-you-want-a-reply"] === "yes", + conditional: { + html: yesHTML + } + }, + { + value: "no", + text: "No", + checked: values["do-you-want-a-reply"] === "no" + } + ], + errorMessage: errors["do-you-want-a-reply"] + }) }} + + {{ govukButton({ + text: "Send message" + }) }} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c89c8cf2eb..b3ed260700 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3271,6 +3271,24 @@ } } }, + "express-validator": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-5.3.1.tgz", + "integrity": "sha512-g8xkipBF6VxHbO1+ksC7nxUU7+pWif0+OZXjZTybKJ/V0aTVhuCoHbyhIPgSYVldwQLocGExPtB2pE0DqK4jsw==", + "dev": true, + "requires": { + "lodash": "4.17.11", + "validator": "10.11.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + } + } + }, "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", @@ -14729,6 +14747,12 @@ "spdx-expression-parse": "1.0.4" } }, + "validator": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==", + "dev": true + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 7685eef807..6b21d1752d 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "cookie-parser": "^1.4.4", "cssnano": "^4.1.7", "express": "^4.16.4", + "express-validator": "^5.3.1", "glob": "^7.1.3", "gulp": "^3.9.1", "gulp-better-rollup": "3.1.0",