hof 20.0.0-beta.2 → 20.0.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/automate-publish.yml +1 -1
 - package/.github/workflows/automate-tag.yml +1 -1
 - package/README.md +329 -256
 - package/build/lib/mkdir.js +2 -2
 - package/components/date/index.js +37 -26
 - package/components/date/templates/date.html +3 -3
 - package/components/emailer/index.js +49 -41
 - package/components/emailer/transports/debug.js +1 -2
 - package/components/index.js +2 -1
 - package/components/notify/index.js +60 -0
 - package/components/notify/notify.js +25 -0
 - package/components/summary/index.js +18 -0
 - package/config/hof-defaults.js +5 -3
 - package/config/rate-limits.js +20 -0
 - package/config/sanitisation-rules.js +29 -0
 - package/controller/base-controller.js +26 -8
 - package/controller/controller.js +11 -15
 - package/frontend/govuk-template/build/config.js +1 -1
 - package/frontend/template-mixins/mixins/template-mixins.js +12 -9
 - package/frontend/template-mixins/partials/forms/checkbox-group.html +12 -3
 - package/frontend/template-mixins/partials/forms/input-text-date.html +1 -1
 - package/frontend/template-mixins/partials/forms/input-text-group.html +3 -3
 - package/frontend/template-mixins/partials/forms/option-group.html +12 -3
 - package/frontend/template-mixins/partials/forms/select.html +3 -3
 - package/frontend/template-mixins/partials/forms/textarea-group.html +3 -3
 - package/frontend/template-mixins/partials/mixins/panel.html +1 -2
 - package/frontend/template-partials/translations/src/en/errors.json +12 -0
 - package/frontend/template-partials/views/partials/form.html +2 -1
 - package/frontend/template-partials/views/rate-limit-error.html +10 -0
 - package/frontend/themes/gov-uk/client-js/govuk-cookies.js +43 -44
 - package/frontend/themes/gov-uk/client-js/index.js +2 -2
 - package/frontend/themes/gov-uk/client-js/skip-to-main.js +18 -17
 - package/frontend/themes/gov-uk/styles/govuk.scss +4 -0
 - package/frontend/themes/gov-uk/styles/modules/_validation.scss +2 -2
 - package/frontend/toolkit/assets/javascript/form-focus.js +10 -1
 - package/frontend/toolkit/assets/javascript/validation.js +6 -1
 - package/index.js +9 -4
 - package/lib/router.js +2 -1
 - package/lib/settings.js +9 -8
 - package/middleware/errors.js +32 -0
 - package/middleware/index.js +2 -1
 - package/middleware/rate-limiter.js +98 -0
 - package/package.json +6 -6
 - package/sandbox/apps/sandbox/fields.js +11 -12
 - package/sandbox/apps/sandbox/index.js +1 -5
 - package/sandbox/assets/scss/app.scss +0 -52
 - package/sandbox/package.json +2 -0
 - package/sandbox/public/css/app.css +4908 -4965
 - package/sandbox/public/js/bundle.js +79 -65
 - package/sandbox/server.js +5 -0
 - package/sandbox/yarn.lock +25 -1
 - package/transpiler/lib/write-files.js +1 -2
 - package/utilities/helpers/index.js +16 -1
 - package/wizard/index.js +1 -0
 - package/.nyc_output/65af88d9-aebe-4d1b-a21d-6fbf7f2bbda4.json +0 -1
 - package/.nyc_output/processinfo/65af88d9-aebe-4d1b-a21d-6fbf7f2bbda4.json +0 -1
 - package/.nyc_output/processinfo/index.json +0 -1
 - package/.vscode/settings.json +0 -6
 - package/sandbox/.env +0 -1
 
    
        package/middleware/errors.js
    CHANGED
    
    | 
         @@ -1,6 +1,8 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            /* eslint-disable no-unused-vars */
         
     | 
| 
       2 
2 
     | 
    
         
             
            'use strict';
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
      
 4 
     | 
    
         
            +
            const rateLimitsConfig = require('../config/rate-limits');
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
       4 
6 
     | 
    
         
             
            const errorTitle = code => `${code}_ERROR`;
         
     | 
| 
       5 
7 
     | 
    
         
             
            const errorMsg = code => `There is a ${code}_ERROR`;
         
     | 
| 
       6 
8 
     | 
    
         
             
            // eslint-disable-next-line complexity
         
     | 
| 
         @@ -21,6 +23,36 @@ const getContent = (err, translate) => { 
     | 
|
| 
       21 
23 
     | 
    
         
             
                content.message = (translate && translate('errors.cookies-required.message'));
         
     | 
| 
       22 
24 
     | 
    
         
             
              }
         
     | 
| 
       23 
25 
     | 
    
         | 
| 
      
 26 
     | 
    
         
            +
              if (err.code === 'DDOS_RATE_LIMIT') {
         
     | 
| 
      
 27 
     | 
    
         
            +
                err.status = 429;
         
     | 
| 
      
 28 
     | 
    
         
            +
                err.template = 'rate-limit-error';
         
     | 
| 
      
 29 
     | 
    
         
            +
                err.title = (translate && translate('errors.ddos-rate-limit.title'));
         
     | 
| 
      
 30 
     | 
    
         
            +
                err.message = (translate && translate('errors.ddos-rate-limit.message'));
         
     | 
| 
      
 31 
     | 
    
         
            +
                err.preTimeToWait = (translate && translate('errors.ddos-rate-limit.pre-time-to-wait'));
         
     | 
| 
      
 32 
     | 
    
         
            +
                err.timeToWait = rateLimitsConfig.rateLimits.requests.windowSizeInMinutes;
         
     | 
| 
      
 33 
     | 
    
         
            +
                err.postTimeToWait = (translate && translate('errors.ddos-rate-limit.post-time-to-wait'));
         
     | 
| 
      
 34 
     | 
    
         
            +
                content.title = (translate && translate('errors.ddos-rate-limit.title'));
         
     | 
| 
      
 35 
     | 
    
         
            +
                content.message = (translate && translate('errors.ddos-rate-limit.message'));
         
     | 
| 
      
 36 
     | 
    
         
            +
                content.preTimeToWait = (translate && translate('errors.ddos-rate-limit.pre-time-to-wait'));
         
     | 
| 
      
 37 
     | 
    
         
            +
                content.timeToWait = rateLimitsConfig.rateLimits.requests.windowSizeInMinutes;
         
     | 
| 
      
 38 
     | 
    
         
            +
                content.postTimeToWait = (translate && translate('errors.ddos-rate-limit.post-time-to-wait'));
         
     | 
| 
      
 39 
     | 
    
         
            +
              }
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              if (err.code === 'SUBMISSION_RATE_LIMIT') {
         
     | 
| 
      
 42 
     | 
    
         
            +
                err.status = 429;
         
     | 
| 
      
 43 
     | 
    
         
            +
                err.template = 'rate-limit-error';
         
     | 
| 
      
 44 
     | 
    
         
            +
                err.title = (translate && translate('errors.submission-rate-limit.title'));
         
     | 
| 
      
 45 
     | 
    
         
            +
                err.message = (translate && translate('errors.submission-rate-limit.message'));
         
     | 
| 
      
 46 
     | 
    
         
            +
                err.preTimeToWait = (translate && translate('errors.submission-rate-limit.pre-time-to-wait'));
         
     | 
| 
      
 47 
     | 
    
         
            +
                err.timeToWait = rateLimitsConfig.rateLimits.submissions.windowSizeInMinutes;
         
     | 
| 
      
 48 
     | 
    
         
            +
                err.postTimeToWait = (translate && translate('errors.submission-rate-limit.post-time-to-wait'));
         
     | 
| 
      
 49 
     | 
    
         
            +
                content.title = (translate && translate('errors.submission-rate-limit.title'));
         
     | 
| 
      
 50 
     | 
    
         
            +
                content.message = (translate && translate('errors.submission-rate-limit.message'));
         
     | 
| 
      
 51 
     | 
    
         
            +
                content.preTimeToWait = (translate && translate('errors.submission-rate-limit.pre-time-to-wait'));
         
     | 
| 
      
 52 
     | 
    
         
            +
                content.timeToWait = rateLimitsConfig.rateLimits.submissions.windowSizeInMinutes;
         
     | 
| 
      
 53 
     | 
    
         
            +
                content.postTimeToWait = (translate && translate('errors.submission-rate-limit.post-time-to-wait'));
         
     | 
| 
      
 54 
     | 
    
         
            +
              }
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
       24 
56 
     | 
    
         
             
              err.code = err.code || 'UNKNOWN';
         
     | 
| 
       25 
57 
     | 
    
         
             
              err.status = err.status || 500;
         
     | 
| 
       26 
58 
     | 
    
         | 
    
        package/middleware/index.js
    CHANGED
    
    
| 
         @@ -0,0 +1,98 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
             
     | 
| 
      
 2 
     | 
    
         
            +
            const moment = require('moment');
         
     | 
| 
      
 3 
     | 
    
         
            +
            const redis = require('redis');
         
     | 
| 
      
 4 
     | 
    
         
            +
            const config = require('./../config/hof-defaults');
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module.exports = (options, rateLimitType) => {
         
     | 
| 
      
 7 
     | 
    
         
            +
              // eslint-disable-next-line no-console
         
     | 
| 
      
 8 
     | 
    
         
            +
              const logger = options.logger || { log: (func, msg) => console[func](msg) };
         
     | 
| 
      
 9 
     | 
    
         
            +
              const rateLimits = options.rateLimits[rateLimitType];
         
     | 
| 
      
 10 
     | 
    
         
            +
              const timestampName = `${rateLimitType}TimeStamp`;
         
     | 
| 
      
 11 
     | 
    
         
            +
              const countName = `${rateLimitType}Count`;
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              const WINDOW_SIZE_IN_MINUTES = rateLimits.windowSizeInMinutes;
         
     | 
| 
      
 14 
     | 
    
         
            +
              const MAX_WINDOW_REQUEST_COUNT = rateLimits.maxWindowRequestCount;
         
     | 
| 
      
 15 
     | 
    
         
            +
              const WINDOW_LOG_INTERVAL_IN_MINUTES = rateLimits.windowLogIntervalInMinutes;
         
     | 
| 
      
 16 
     | 
    
         
            +
              const ERROR_CODE = rateLimits.errCode;
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              return async (req, res, next) => {
         
     | 
| 
      
 19 
     | 
    
         
            +
                const redisClient = redis.createClient(config.redis);
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                // check that redis client exists
         
     | 
| 
      
 22 
     | 
    
         
            +
                if (!redisClient) {
         
     | 
| 
      
 23 
     | 
    
         
            +
                  logger.log('error', 'Redis client does not exist!');
         
     | 
| 
      
 24 
     | 
    
         
            +
                  return next();
         
     | 
| 
      
 25 
     | 
    
         
            +
                }
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                const closeConnection = async err => {
         
     | 
| 
      
 28 
     | 
    
         
            +
                  await redisClient.quit();
         
     | 
| 
      
 29 
     | 
    
         
            +
                  return next(err);
         
     | 
| 
      
 30 
     | 
    
         
            +
                };
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                try {
         
     | 
| 
      
 33 
     | 
    
         
            +
                  // fetch records of current user using IP address, returns null when no record is found
         
     | 
| 
      
 34 
     | 
    
         
            +
                  return await redisClient.get(req.ip, async (err, record) => {
         
     | 
| 
      
 35 
     | 
    
         
            +
                    if (err) {
         
     | 
| 
      
 36 
     | 
    
         
            +
                      logger.log('error', `Error with requesting redis session for rate limiting: ${err}`);
         
     | 
| 
      
 37 
     | 
    
         
            +
                      return await closeConnection();
         
     | 
| 
      
 38 
     | 
    
         
            +
                    }
         
     | 
| 
      
 39 
     | 
    
         
            +
                    const currentRequestTime = moment();
         
     | 
| 
      
 40 
     | 
    
         
            +
                    const windowStartTimestamp = moment().subtract(WINDOW_SIZE_IN_MINUTES, 'minutes').unix();
         
     | 
| 
      
 41 
     | 
    
         
            +
                    let oldRecord = false;
         
     | 
| 
      
 42 
     | 
    
         
            +
                    let data;
         
     | 
| 
      
 43 
     | 
    
         
            +
                    //  if no record is found , create a new record for user and store to redis
         
     | 
| 
      
 44 
     | 
    
         
            +
                    if (record) {
         
     | 
| 
      
 45 
     | 
    
         
            +
                      data = JSON.parse(record);
         
     | 
| 
      
 46 
     | 
    
         
            +
                      oldRecord = data[data.length - 1][timestampName] < windowStartTimestamp;
         
     | 
| 
      
 47 
     | 
    
         
            +
                    }
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                    if (!record || oldRecord) {
         
     | 
| 
      
 50 
     | 
    
         
            +
                      const newRecord = [];
         
     | 
| 
      
 51 
     | 
    
         
            +
                      const requestLog = {
         
     | 
| 
      
 52 
     | 
    
         
            +
                        [timestampName]: currentRequestTime.unix(),
         
     | 
| 
      
 53 
     | 
    
         
            +
                        [countName]: 1
         
     | 
| 
      
 54 
     | 
    
         
            +
                      };
         
     | 
| 
      
 55 
     | 
    
         
            +
                      newRecord.push(requestLog);
         
     | 
| 
      
 56 
     | 
    
         
            +
                      await redisClient.set(req.ip, JSON.stringify(newRecord));
         
     | 
| 
      
 57 
     | 
    
         
            +
                      return await closeConnection();
         
     | 
| 
      
 58 
     | 
    
         
            +
                    }
         
     | 
| 
      
 59 
     | 
    
         
            +
                    // if record is found, parse it's value and calculate number of requests users has made within the last window
         
     | 
| 
      
 60 
     | 
    
         
            +
                    const requestsWithinWindow = data.filter(entry => entry[timestampName] > windowStartTimestamp);
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    const totalWindowRequestsCount = requestsWithinWindow.reduce((accumulator, entry) => {
         
     | 
| 
      
 63 
     | 
    
         
            +
                      return accumulator + entry[countName];
         
     | 
| 
      
 64 
     | 
    
         
            +
                    }, 0);
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                    if (!options.rateLimits.env || options.rateLimits.env === 'development') {
         
     | 
| 
      
 67 
     | 
    
         
            +
                      const requestsRemaining = MAX_WINDOW_REQUEST_COUNT - totalWindowRequestsCount;
         
     | 
| 
      
 68 
     | 
    
         
            +
                      const msg = `Requests made by client: ${totalWindowRequestsCount}\nRequests remaining: ${requestsRemaining}`;
         
     | 
| 
      
 69 
     | 
    
         
            +
                      logger.log('info', msg);
         
     | 
| 
      
 70 
     | 
    
         
            +
                    }
         
     | 
| 
      
 71 
     | 
    
         
            +
                    // if number of requests made is greater than or equal to the desired maximum, return error
         
     | 
| 
      
 72 
     | 
    
         
            +
                    if (totalWindowRequestsCount >= MAX_WINDOW_REQUEST_COUNT) {
         
     | 
| 
      
 73 
     | 
    
         
            +
                      return await closeConnection({ code: ERROR_CODE });
         
     | 
| 
      
 74 
     | 
    
         
            +
                    }
         
     | 
| 
      
 75 
     | 
    
         
            +
                    // if number of requests made is less than allowed maximum, log new entry
         
     | 
| 
      
 76 
     | 
    
         
            +
                    const lastRequestLog = data[data.length - 1];
         
     | 
| 
      
 77 
     | 
    
         
            +
                    const potentialCurrentWindowIntervalStartTimeStamp = currentRequestTime
         
     | 
| 
      
 78 
     | 
    
         
            +
                      .subtract(WINDOW_LOG_INTERVAL_IN_MINUTES, 'minutes')
         
     | 
| 
      
 79 
     | 
    
         
            +
                      .unix();
         
     | 
| 
      
 80 
     | 
    
         
            +
                    //  if interval has not passed since last request log, increment counter
         
     | 
| 
      
 81 
     | 
    
         
            +
                    if (lastRequestLog[timestampName] > potentialCurrentWindowIntervalStartTimeStamp) {
         
     | 
| 
      
 82 
     | 
    
         
            +
                      lastRequestLog[countName]++;
         
     | 
| 
      
 83 
     | 
    
         
            +
                      data[data.length - 1] = lastRequestLog;
         
     | 
| 
      
 84 
     | 
    
         
            +
                    } else {
         
     | 
| 
      
 85 
     | 
    
         
            +
                      //  if interval has passed, log new entry for current user and timestamp
         
     | 
| 
      
 86 
     | 
    
         
            +
                      data.push({
         
     | 
| 
      
 87 
     | 
    
         
            +
                        [timestampName]: currentRequestTime.unix(),
         
     | 
| 
      
 88 
     | 
    
         
            +
                        [countName]: 1
         
     | 
| 
      
 89 
     | 
    
         
            +
                      });
         
     | 
| 
      
 90 
     | 
    
         
            +
                    }
         
     | 
| 
      
 91 
     | 
    
         
            +
                    await redisClient.set(req.ip, JSON.stringify(data));
         
     | 
| 
      
 92 
     | 
    
         
            +
                    return await closeConnection();
         
     | 
| 
      
 93 
     | 
    
         
            +
                  });
         
     | 
| 
      
 94 
     | 
    
         
            +
                } catch (err) {
         
     | 
| 
      
 95 
     | 
    
         
            +
                  return await closeConnection(err);
         
     | 
| 
      
 96 
     | 
    
         
            +
                }
         
     | 
| 
      
 97 
     | 
    
         
            +
              };
         
     | 
| 
      
 98 
     | 
    
         
            +
            };
         
     | 
    
        package/package.json
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            {
         
     | 
| 
       2 
2 
     | 
    
         
             
              "name": "hof",
         
     | 
| 
       3 
3 
     | 
    
         
             
              "description": "A bootstrap for HOF projects",
         
     | 
| 
       4 
     | 
    
         
            -
              "version": "20.0.0-beta. 
     | 
| 
      
 4 
     | 
    
         
            +
              "version": "20.0.0-beta.20",
         
     | 
| 
       5 
5 
     | 
    
         
             
              "license": "MIT",
         
     | 
| 
       6 
6 
     | 
    
         
             
              "main": "index.js",
         
     | 
| 
       7 
7 
     | 
    
         
             
              "author": "HomeOffice",
         
     | 
| 
         @@ -70,16 +70,16 @@ 
     | 
|
| 
       70 
70 
     | 
    
         
             
                "lodash": "^4.17.21",
         
     | 
| 
       71 
71 
     | 
    
         
             
                "markdown-it": "^12.3.2",
         
     | 
| 
       72 
72 
     | 
    
         
             
                "minimatch": "^3.0.3",
         
     | 
| 
       73 
     | 
    
         
            -
                "minimist": "^1.2. 
     | 
| 
      
 73 
     | 
    
         
            +
                "minimist": "^1.2.6",
         
     | 
| 
       74 
74 
     | 
    
         
             
                "mixwith": "^0.1.1",
         
     | 
| 
       75 
     | 
    
         
            -
                " 
     | 
| 
       76 
     | 
    
         
            -
                "moment": "^2.24.0",
         
     | 
| 
      
 75 
     | 
    
         
            +
                "moment": "^2.29.2",
         
     | 
| 
       77 
76 
     | 
    
         
             
                "morgan": "^1.10.0",
         
     | 
| 
       78 
77 
     | 
    
         
             
                "mustache": "^2.3.0",
         
     | 
| 
       79 
78 
     | 
    
         
             
                "nodemailer": "^6.6.3",
         
     | 
| 
       80 
79 
     | 
    
         
             
                "nodemailer-ses-transport": "^1.5.0",
         
     | 
| 
       81 
80 
     | 
    
         
             
                "nodemailer-smtp-transport": "^2.7.4",
         
     | 
| 
       82 
81 
     | 
    
         
             
                "nodemailer-stub-transport": "^1.1.0",
         
     | 
| 
      
 82 
     | 
    
         
            +
                "notifications-node-client": "^5.1.1",
         
     | 
| 
       83 
83 
     | 
    
         
             
                "redis": "^3.1.2",
         
     | 
| 
       84 
84 
     | 
    
         
             
                "reqres": "^3.0.1",
         
     | 
| 
       85 
85 
     | 
    
         
             
                "request": "^2.79.0",
         
     | 
| 
         @@ -88,8 +88,8 @@ 
     | 
|
| 
       88 
88 
     | 
    
         
             
                "serve-static": "^1.14.1",
         
     | 
| 
       89 
89 
     | 
    
         
             
                "uglify-js": "^3.14.3",
         
     | 
| 
       90 
90 
     | 
    
         
             
                "underscore": "^1.12.1",
         
     | 
| 
       91 
     | 
    
         
            -
                "urijs": "^1.19. 
     | 
| 
       92 
     | 
    
         
            -
                "winston": "^3. 
     | 
| 
      
 91 
     | 
    
         
            +
                "urijs": "^1.19.11",
         
     | 
| 
      
 92 
     | 
    
         
            +
                "winston": "^3.7.2"
         
     | 
| 
       93 
93 
     | 
    
         
             
              },
         
     | 
| 
       94 
94 
     | 
    
         
             
              "devDependencies": {
         
     | 
| 
       95 
95 
     | 
    
         
             
                "@cucumber/cucumber": "^7.3.0",
         
     | 
| 
         @@ -8,7 +8,7 @@ module.exports = { 
     | 
|
| 
       8 
8 
     | 
    
         
             
              'landing-page-radio': {
         
     | 
| 
       9 
9 
     | 
    
         
             
                mixin: 'radio-group',
         
     | 
| 
       10 
10 
     | 
    
         
             
                validate: ['required'],
         
     | 
| 
       11 
     | 
    
         
            -
                 
     | 
| 
      
 11 
     | 
    
         
            +
                isPageHeading: true,
         
     | 
| 
       12 
12 
     | 
    
         
             
                // Design system says to avoid in-line unless it's two options,
         
     | 
| 
       13 
13 
     | 
    
         
             
                // so just added as an example below but by default it isn't
         
     | 
| 
       14 
14 
     | 
    
         
             
                className: ['govuk-radios--inline'],
         
     | 
| 
         @@ -16,12 +16,12 @@ module.exports = { 
     | 
|
| 
       16 
16 
     | 
    
         
             
              },
         
     | 
| 
       17 
17 
     | 
    
         
             
              name: {
         
     | 
| 
       18 
18 
     | 
    
         
             
                validate: ['required', 'notUrl', { type: 'maxlength', arguments: 200 }],
         
     | 
| 
      
 19 
     | 
    
         
            +
                // need to remove this for the heading to go
         
     | 
| 
       19 
20 
     | 
    
         
             
                labelClassName: ['govuk-label--l'],
         
     | 
| 
       20 
     | 
    
         
            -
                 
     | 
| 
      
 21 
     | 
    
         
            +
                isPageHeading: 'true'
         
     | 
| 
       21 
22 
     | 
    
         
             
              },
         
     | 
| 
       22 
23 
     | 
    
         
             
              'dateOfBirth': dateComponent('dateOfBirth', {
         
     | 
| 
       23 
     | 
    
         
            -
                 
     | 
| 
       24 
     | 
    
         
            -
                controlType: 'date-input',
         
     | 
| 
      
 24 
     | 
    
         
            +
                isPageHeading: 'true',
         
     | 
| 
       25 
25 
     | 
    
         
             
                validate: [
         
     | 
| 
       26 
26 
     | 
    
         
             
                  'required',
         
     | 
| 
       27 
27 
     | 
    
         
             
                  'date',
         
     | 
| 
         @@ -29,28 +29,24 @@ module.exports = { 
     | 
|
| 
       29 
29 
     | 
    
         
             
                ]
         
     | 
| 
       30 
30 
     | 
    
         
             
              }),
         
     | 
| 
       31 
31 
     | 
    
         
             
              building: {
         
     | 
| 
       32 
     | 
    
         
            -
                noHeading: 'true',
         
     | 
| 
       33 
32 
     | 
    
         
             
                validate: ['required', 'notUrl', { type: 'maxlength', arguments: 100 }]
         
     | 
| 
       34 
33 
     | 
    
         
             
              },
         
     | 
| 
       35 
34 
     | 
    
         
             
              street: {
         
     | 
| 
       36 
     | 
    
         
            -
                noHeading: 'true',
         
     | 
| 
       37 
35 
     | 
    
         
             
                validate: ['notUrl', { type: 'maxlength', arguments: 50 }],
         
     | 
| 
       38 
36 
     | 
    
         
             
                labelClassName: 'visuallyhidden'
         
     | 
| 
       39 
37 
     | 
    
         
             
              },
         
     | 
| 
       40 
38 
     | 
    
         
             
              townOrCity: {
         
     | 
| 
       41 
     | 
    
         
            -
                noHeading: 'true',
         
     | 
| 
       42 
39 
     | 
    
         
             
                validate: ['required', 'notUrl',
         
     | 
| 
       43 
40 
     | 
    
         
             
                  { type: 'regex', arguments: /^([^0-9]*)$/ },
         
     | 
| 
       44 
41 
     | 
    
         
             
                  { type: 'maxlength', arguments: 100 }
         
     | 
| 
       45 
42 
     | 
    
         
             
                ]
         
     | 
| 
       46 
43 
     | 
    
         
             
              },
         
     | 
| 
       47 
44 
     | 
    
         
             
              postcode: {
         
     | 
| 
       48 
     | 
    
         
            -
                noHeading: 'true',
         
     | 
| 
       49 
45 
     | 
    
         
             
                validate: ['required', 'postcode'],
         
     | 
| 
       50 
46 
     | 
    
         
             
                formatter: ['removespaces', 'uppercase']
         
     | 
| 
       51 
47 
     | 
    
         
             
              },
         
     | 
| 
       52 
48 
     | 
    
         
             
              incomeTypes: {
         
     | 
| 
       53 
     | 
    
         
            -
                 
     | 
| 
      
 49 
     | 
    
         
            +
                isPageHeading: 'true',
         
     | 
| 
       54 
50 
     | 
    
         
             
                mixin: 'checkbox-group',
         
     | 
| 
       55 
51 
     | 
    
         
             
                labelClassName: 'visuallyhidden',
         
     | 
| 
       56 
52 
     | 
    
         
             
                validate: ['required'],
         
     | 
| 
         @@ -63,7 +59,7 @@ module.exports = { 
     | 
|
| 
       63 
59 
     | 
    
         
             
                ]
         
     | 
| 
       64 
60 
     | 
    
         
             
              },
         
     | 
| 
       65 
61 
     | 
    
         
             
              countryOfHearing: {
         
     | 
| 
       66 
     | 
    
         
            -
                 
     | 
| 
      
 62 
     | 
    
         
            +
                isPageHeading: 'true',
         
     | 
| 
       67 
63 
     | 
    
         
             
                mixin: 'radio-group',
         
     | 
| 
       68 
64 
     | 
    
         
             
                validate: ['required'],
         
     | 
| 
       69 
65 
     | 
    
         
             
                options: [
         
     | 
| 
         @@ -73,7 +69,7 @@ module.exports = { 
     | 
|
| 
       73 
69 
     | 
    
         
             
                ]
         
     | 
| 
       74 
70 
     | 
    
         
             
              },
         
     | 
| 
       75 
71 
     | 
    
         
             
              email: {
         
     | 
| 
       76 
     | 
    
         
            -
                 
     | 
| 
      
 72 
     | 
    
         
            +
                isPageHeading: 'true',
         
     | 
| 
       77 
73 
     | 
    
         
             
                labelClassName: ['govuk-label--l'],
         
     | 
| 
       78 
74 
     | 
    
         
             
                validate: ['required', 'email']
         
     | 
| 
       79 
75 
     | 
    
         
             
              },
         
     | 
| 
         @@ -86,6 +82,7 @@ module.exports = { 
     | 
|
| 
       86 
82 
     | 
    
         
             
              },
         
     | 
| 
       87 
83 
     | 
    
         
             
              countrySelect: {
         
     | 
| 
       88 
84 
     | 
    
         
             
                mixin: 'select',
         
     | 
| 
      
 85 
     | 
    
         
            +
                isPageHeading: 'true',
         
     | 
| 
       89 
86 
     | 
    
         
             
                className: ['typeahead'],
         
     | 
| 
       90 
87 
     | 
    
         
             
                options:[''].concat(require('homeoffice-countries').allCountries),
         
     | 
| 
       91 
88 
     | 
    
         
             
                legend: {
         
     | 
| 
         @@ -97,12 +94,13 @@ module.exports = { 
     | 
|
| 
       97 
94 
     | 
    
         
             
                mixin: 'textarea',
         
     | 
| 
       98 
95 
     | 
    
         
             
                // we want to ignore default formatters as we want
         
     | 
| 
       99 
96 
     | 
    
         
             
                // to preserve white space
         
     | 
| 
      
 97 
     | 
    
         
            +
                isPageHeading: 'true',
         
     | 
| 
       100 
98 
     | 
    
         
             
                'ignore-defaults': true,
         
     | 
| 
       101 
99 
     | 
    
         
             
                // apply the other default formatters
         
     | 
| 
       102 
100 
     | 
    
         
             
                formatter: ['trim', 'hyphens'],
         
     | 
| 
       103 
101 
     | 
    
         
             
                labelClassName: ['govuk-label--l'],
         
     | 
| 
       104 
102 
     | 
    
         
             
                // attributes here are passed to the field element
         
     | 
| 
       105 
     | 
    
         
            -
                validate: ['required', { type: 'maxlength', arguments:  
     | 
| 
      
 103 
     | 
    
         
            +
                validate: ['required', { type: 'maxlength', arguments: 10 }],
         
     | 
| 
       106 
104 
     | 
    
         
             
                attributes: [{
         
     | 
| 
       107 
105 
     | 
    
         
             
                  attribute: 'rows',
         
     | 
| 
       108 
106 
     | 
    
         
             
                  value: 8
         
     | 
| 
         @@ -110,6 +108,7 @@ module.exports = { 
     | 
|
| 
       110 
108 
     | 
    
         
             
              },
         
     | 
| 
       111 
109 
     | 
    
         
             
              appealStages: {
         
     | 
| 
       112 
110 
     | 
    
         
             
                mixin: 'select',
         
     | 
| 
      
 111 
     | 
    
         
            +
                isPageHeading: 'true',
         
     | 
| 
       113 
112 
     | 
    
         
             
                validate: ['required'],
         
     | 
| 
       114 
113 
     | 
    
         
             
                options: [{
         
     | 
| 
       115 
114 
     | 
    
         
             
                  value: '',
         
     | 
| 
         @@ -30,11 +30,7 @@ module.exports = { 
     | 
|
| 
       30 
30 
     | 
    
         
             
                },
         
     | 
| 
       31 
31 
     | 
    
         
             
                '/dob': {
         
     | 
| 
       32 
32 
     | 
    
         
             
                  fields: ['dateOfBirth'],
         
     | 
| 
       33 
     | 
    
         
            -
                  next: '/address' 
     | 
| 
       34 
     | 
    
         
            -
                  locals: {
         
     | 
| 
       35 
     | 
    
         
            -
                    step: 'dob',
         
     | 
| 
       36 
     | 
    
         
            -
                    labelClassName: 'govuk-input'
         
     | 
| 
       37 
     | 
    
         
            -
                  }
         
     | 
| 
      
 33 
     | 
    
         
            +
                  next: '/address'
         
     | 
| 
       38 
34 
     | 
    
         
             
                },
         
     | 
| 
       39 
35 
     | 
    
         
             
                '/address': {
         
     | 
| 
       40 
36 
     | 
    
         
             
                  fields: ['building', 'street', 'townOrCity', 'postcode'],
         
     | 
| 
         @@ -1,6 +1,5 @@ 
     | 
|
| 
       1 
1 
     | 
    
         | 
| 
       2 
2 
     | 
    
         
             
            @import "../../../frontend/themes/gov-uk/styles/govuk";
         
     | 
| 
       3 
     | 
    
         
            -
            @import "../../../node_modules/govuk-frontend/govuk/all";
         
     | 
| 
       4 
3 
     | 
    
         | 
| 
       5 
4 
     | 
    
         
             
            //autocomplete styling
         
     | 
| 
       6 
5 
     | 
    
         
             
            .tt-menu {
         
     | 
| 
         @@ -26,54 +25,3 @@ 
     | 
|
| 
       26 
25 
     | 
    
         
             
            .twitter-typeahead {
         
     | 
| 
       27 
26 
     | 
    
         
             
              width: 100%;
         
     | 
| 
       28 
27 
     | 
    
         
             
            }
         
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
            // govuk cookie banner styling
         
     | 
| 
       31 
     | 
    
         
            -
            .govuk-banner--success {
         
     | 
| 
       32 
     | 
    
         
            -
              border-color: #00703c;
         
     | 
| 
       33 
     | 
    
         
            -
              color: #00703c;
         
     | 
| 
       34 
     | 
    
         
            -
            }
         
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
            .govuk-banner {
         
     | 
| 
       37 
     | 
    
         
            -
              border: 5px solid #1d70b8;
         
     | 
| 
       38 
     | 
    
         
            -
              font-size: 0;
         
     | 
| 
       39 
     | 
    
         
            -
              margin-bottom: 30px;
         
     | 
| 
       40 
     | 
    
         
            -
              padding: 10px;
         
     | 
| 
       41 
     | 
    
         
            -
            }
         
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
            .govuk-banner__icon{
         
     | 
| 
       44 
     | 
    
         
            -
              display: inline-block;
         
     | 
| 
       45 
     | 
    
         
            -
            }
         
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
            .govuk-banner__message {
         
     | 
| 
       48 
     | 
    
         
            -
              font-family: "GDS Transport", Arial, sans-serif;
         
     | 
| 
       49 
     | 
    
         
            -
              -webkit-font-smoothing: antialiased;
         
     | 
| 
       50 
     | 
    
         
            -
              font-weight: 400;
         
     | 
| 
       51 
     | 
    
         
            -
              font-size: 1rem;
         
     | 
| 
       52 
     | 
    
         
            -
              line-height: 1.25;
         
     | 
| 
       53 
     | 
    
         
            -
              color: #0b0c0c;
         
     | 
| 
       54 
     | 
    
         
            -
              display: block;
         
     | 
| 
       55 
     | 
    
         
            -
              overflow: hidden;
         
     | 
| 
       56 
     | 
    
         
            -
              display: inline-block;
         
     | 
| 
       57 
     | 
    
         
            -
              margin-left: 10px;
         
     | 
| 
       58 
     | 
    
         
            -
            }
         
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
       60 
     | 
    
         
            -
            .govuk-banner__assistive {
         
     | 
| 
       61 
     | 
    
         
            -
              position: absolute !important;
         
     | 
| 
       62 
     | 
    
         
            -
              width: 1px !important;
         
     | 
| 
       63 
     | 
    
         
            -
              height: 1px !important;
         
     | 
| 
       64 
     | 
    
         
            -
              margin: 0 !important;
         
     | 
| 
       65 
     | 
    
         
            -
              padding: 0 !important;
         
     | 
| 
       66 
     | 
    
         
            -
              overflow: hidden !important;
         
     | 
| 
       67 
     | 
    
         
            -
              clip: rect(0 0 0 0) !important;
         
     | 
| 
       68 
     | 
    
         
            -
              clip-path: inset(50%) !important;
         
     | 
| 
       69 
     | 
    
         
            -
              border: 0 !important;
         
     | 
| 
       70 
     | 
    
         
            -
              white-space: nowrap !important;
         
     | 
| 
       71 
     | 
    
         
            -
              }
         
     | 
| 
       72 
     | 
    
         
            -
             
     | 
| 
       73 
     | 
    
         
            -
            .cookie-table-holder > table > tbody > tr > td:first-child{
         
     | 
| 
       74 
     | 
    
         
            -
              font-weight:bold;
         
     | 
| 
       75 
     | 
    
         
            -
            }
         
     | 
| 
       76 
     | 
    
         
            -
             
     | 
| 
       77 
     | 
    
         
            -
            .js-enabled #global-cookie-message {
         
     | 
| 
       78 
     | 
    
         
            -
              display: none;
         
     | 
| 
       79 
     | 
    
         
            -
            }
         
     |