Skip to content

Commit

Permalink
HOFF-738: Session timeout warning(sign in required)
Browse files Browse the repository at this point in the history
- add showSaveAndExit locals to session-timeout-warning.html to set content to be used by 'save and exit' forms
- add default save-and-exit html template
- set showSaveAndExit locals to true on /dob step in sandbox/index.js for demo purposes.
- add save-and-exit.html to sandbox for demo purposes
- amend sandbox/pages.json to include flag for save and exit content
- add config to customise content save-and-exit page
- make exit and save-and-exit steps customisable
- update readme to include guidance on customising exit and save-and-exit steps
- add relevant tests
- amend sessionDialog.js so it can pick up configured warning text instead of it being hard coded
- amend changelog
- add baseurl to exit href
- add base url to /session-timeout in session-timeout-warning.html
  • Loading branch information
Rhodine-orleans-lindsay committed Jan 17, 2025
1 parent 8bfbc1b commit 0c6d28f
Show file tree
Hide file tree
Showing 18 changed files with 201 additions and 18 deletions.
11 changes: 7 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
## 2025-01-20, Version 22.0.0, @Rhodine-orleans-lindsay
* Adds session timeout warning
- user can stay on page or exit form
- adds exit html
- updates confirmation html to a static page
- allows for customisation of session timeout warning dialog content and exit page content
- user can stay on page or exit form
- adds exit html
- user can stay signed in or save and exit the form if the form is a save and exit form
- adds default save-and-exit html
- updates confirmation html to a static page
- allows for customisation of session timeout warning dialog content, exit and save-and-exit page content, and exit and save-and-exit steps
- Potential **_breaking change_**: Static pages should use the ```{{<layout}}...{{/layout}}``` tags instead of the ```{{<partials-page}}...{{/partials-page}}``` tags if the timeout warning should not be displayed.
* Fixes accessibility issues
* Sandbox area for testing hof changes
* Updates patch and minor dependency versions
Expand Down
49 changes: 43 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1261,7 +1261,7 @@ This feature allows you to customise the content related to the session timeout
### Usage
To enable and customize the session timeout behavior, you need to set the component in your project's `hof.settings.json` file:
To enable and customise the session timeout behavior, you need to set the component in your project's `hof.settings.json` file:
```js
"behaviours": [
"hof/components/session-timeout-warning"
Expand All @@ -1274,12 +1274,13 @@ By default, the framework uses the standard content provided by HOF. If you wish
behaviours: [
require('../').components.sessionTimeoutWarning
],
sessionTimeoutWarningContent: true,
exitFormContent: true
sessionTimeoutWarningContent: true, // allows you to customise the content in the session timeout dialog box
exitFormContent: true // allows you to customise the content on the exit page
saveExitFormContent: true // allows you to customise the content on the save-and-exit page
```
### Customising content in `pages.json`
Once the variables are set, you can customize the session timeout warning and exit messages in your project's pages.json:
Once the variables are set, you can customise the session timeout warning and exit messages in your project's pages.json:
```json
"exit": {
Expand All @@ -1291,17 +1292,53 @@ Once the variables are set, you can customize the session timeout warning and ex
"timeout-continue-button": "Stay on this page",
"dialog-exit-link": "Exit this form"
}
"save-and-exit": {
"message": "Any answers you saved have not been affected. You can sign back in to your application at any time by returning to the start page."
},
```
### Editing content on the Exit Page Header and Title
To edit the exit page's header and title, create an `exit.json` file in your project and set the desired content:
### Editing content on the Exit and Save-and-exit Page Header and Title
To edit the exit or save-and-exit pages' header and title, create an `exit.json` or `save-and-exit.json` file in your project and set the desired content:
```json
{
"header": "You have left this form",
"title": "You have left this form"
}
```
### Customising exit and save-and-exit steps
You can customise the `exit` and `save-and-exit` steps by setting the `exitStep` or `saveAndExitStep` properties in the `apps/<app_name>/index.js` to the desired path name:
```js
// customising exit step name
module.exports = {
name: 'sandbox',
exitStep: '/leave',
steps: {
...
'/leave': {
template: 'exit'
}
}
...
}
```
```js
// customising save-and-exit step name
module.exports = {
name: 'sandbox',
saveAndExitStep: '/sign-out',
steps: {
...
'/sign-out': {
template: 'save-and-exit'
}
}
...
}
```
# UTILITIES
# Autofill Utility
Expand Down
11 changes: 11 additions & 0 deletions components/session-timeout-warning/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ module.exports = superclass => class extends superclass {
superLocals.message = req.translate('exit.message');
return superLocals;
}

// set the content on /save-and-exit page
if (req.form.options.route === '/save-and-exit' && config.saveExitFormContent === true) {
superLocals.saveExitFormContent = true;
return superLocals;
} else if (req.form.options.route === '/save-and-exit' && config.saveExitFormContent === false) {
superLocals.header = req.translate('save-and-exit.header');
superLocals.title = req.translate('save-and-exit.title');
superLocals.message = req.translate('save-and-exit.message');
return superLocals;
}
return superLocals;
}
};
1 change: 1 addition & 0 deletions config/hof-defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const defaults = {
getAccessibility: false,
sessionTimeoutWarningContent: false,
exitFormContent: false,
saveExitFormContent: false,
viewEngine: 'html',
protocol: process.env.PROTOCOL || 'http',
noCache: process.env.NO_CACHE || false,
Expand Down
4 changes: 4 additions & 0 deletions controller/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,14 @@ module.exports = class Controller extends BaseController {
// only include fields that aren't dependents to mitigate duplicate fields on the page
fields = fields.filter(field => !req.form.options.fields[field.key].dependent);

const exitStep = req.form.options.exitStep || '/exit';
const saveAndExitStep = req.form.options.saveAndExitStep || '/save-and-exit';
return _.extend({}, locals, {
fields,
route,
baseUrl: req.baseUrl,
exitStep,
saveAndExitStep,
skipToMain: this.getFirstFormItem(req.form.options.fields),
title: this.getTitle(route, lookup, req.form.options.fields, res.locals),
journeyHeaderURL: this.getJourneyHeaderURL(req.baseUrl),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"header": "You have been signed out",
"message": "Any answers you saved have not been affected. You can sign back in to your application by returning to the <a href='/' class='govuk-link'>start page</a>."
}
2 changes: 1 addition & 1 deletion frontend/template-partials/views/partials/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@
{{/deIndex}}
<meta name="format-detection" content="telephone=no">
<noscript>
<meta http-equiv="refresh" content="{{sessionTimeOut}};url='/session-timeout'"/>
<meta http-equiv="refresh" content="{{sessionTimeOut}};url='{{baseUrl}}/session-timeout'"/>
</noscript>
<link rel="stylesheet" href="{{assetPath}}/css/app.css">
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,31 @@
data-url-redirect="/session-timeout" class="modal-dialog dialog" role="dialog"
aria-live="polite" aria-labelledby="dialog-title" aria-describedby="at-timer">
<div class="modal-dialog__inner">
{{^showSaveAndExit}}
<h2 id="dialog-title" class="govuk-heading-l">
{{#dialogTitle}}{{#t}}pages.session-timeout-warning.dialog-title{{/t}}{{/dialogTitle}}{{^dialogTitle}}Your page will time out soon{{/dialogTitle}}
</h2>
<div class="govuk-body">
<div id="timer" class="timer" aria-hidden="true" aria-relevant="additions"></div>
<div id="at-timer" class="at-timer govuk-visually-hidden" role="status"></div>
<p class="dialog-text-prefix visually-hidden">To protect your information, this page will time out in </p>
<p class="dialog-text visually-hidden">{{#dialogText}}{{#t}}pages.session-timeout-warning.dialog-text{{/t}}{{/dialogText}}{{^dialogText}}If that happens, your progress will not be saved.{{/dialogText}}</p>
</div>
<button class="govuk-button dialog-button js-dialog-close" id="timeout-continue-button" data-module="govuk-button">{{#timeoutContinueButton}}{{#t}}pages.session-timeout-warning.timeout-continue-button{{/t}}{{/timeoutContinueButton}}{{^timeoutContinueButton}}Stay on this page{{/timeoutContinueButton}}</button>
<a href="/exit" class="govuk-link dialog-exit-link" role="button">{{#dialogExitLink}}{{#t}}pages.session-timeout-warning.dialog-exit-link{{/t}}{{/dialogExitLink}}{{^dialogExitLink}}Exit this form{{/dialogExitLink}}</a>
<a href="{{baseUrl}}{{exitStep}}" class="govuk-link dialog-exit-link" role="button">{{#dialogExitLink}}{{#t}}pages.session-timeout-warning.dialog-exit-link{{/t}}{{/dialogExitLink}}{{^dialogExitLink}}Exit this form{{/dialogExitLink}}</a>
{{/showSaveAndExit}}
{{#showSaveAndExit}}
<h2 id="dialog-title" class="govuk-heading-l">
{{#dialogTitle}}{{#t}}pages.session-timeout-warning.dialog-title{{/t}}{{/dialogTitle}}{{^dialogTitle}}You will be signed out soon{{/dialogTitle}}
</h2>
<div class="govuk-body">
<div id="timer" class="timer" aria-hidden="true" aria-relevant="additions"></div>
<div id="at-timer" class="at-timer govuk-visually-hidden" role="status"></div>
<p class="dialog-text-prefix visually-hidden">To protect your information, you will be signed out in </p>
<p class="dialog-text visually-hidden">{{#dialogText}}{{#t}}pages.session-timeout-warning.dialog-text{{/t}}{{/dialogText}}{{^dialogText}}Any answers you have saved will not be affected, but your progress on this page will not be saved.{{/dialogText}}</p>
</div>
<button class="govuk-button dialog-button js-dialog-close" id="timeout-continue-button" data-module="govuk-button">{{#timeoutContinueButton}}{{#t}}pages.session-timeout-warning.timeout-continue-button{{/t}}{{/timeoutContinueButton}}{{^timeoutContinueButton}}Stay signed in{{/timeoutContinueButton}}</button>
<a href="{{baseUrl}}{{saveAndExitStep}}" class="govuk-link dialog-exit-link" role="button">{{#dialogExitLink}}{{#t}}pages.session-timeout-warning.dialog-exit-link{{/t}}{{/dialogExitLink}}{{^dialogExitLink}}Sign out{{/dialogExitLink}}</a>
{{/showSaveAndExit}}
</div>
</dialog>
17 changes: 17 additions & 0 deletions frontend/template-partials/views/save-and-exit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{{<layout}}
{{$journeyHeader}}
{{#t}}journey.header{{/t}}
{{/journeyHeader}}

{{$propositionHeader}}
{{> partials-navigation}}
{{/propositionHeader}}

{{$header}}
{{header}}
{{/header}}

{{$content}}
<p>{{#saveExitFormContent}}{{#t}}pages.save-and-exit.message{{/t}}{{/saveExitFormContent}}{{^saveExitFormContent}}{{{message}}}{{/saveExitFormContent}}</p>
{{/content}}
{{/layout}}
2 changes: 1 addition & 1 deletion frontend/themes/gov-uk/client-js/session-timeout-dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ window.GOVUK.sessionDialog = {
$fallBackElement: $('.govuk-timeout-warning-fallback'),
dialogIsOpenClass: 'dialog-is-open',
timers: [],
warningTextPrefix: 'To protect your information, this page will time out in ',
warningTextPrefix: $('.dialog-text-prefix').text(),
warningTextSuffix: '.',
warningText: $('.dialog-text').text(),
warningTextExtra: '',
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ function bootstrap(options) {
res.locals.sessionTimeOutWarning = config.sessionTimeOutWarning;
res.locals.sessionTimeoutWarningContent = config.sessionTimeoutWarningContent;
res.locals.exitFormContent = config.exitFormContent;
res.locals.saveExitFormContent = config.saveExitFormContent;
next();
});

Expand Down
2 changes: 2 additions & 0 deletions lib/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ function getWizardConfig(config) {
// whitelist properties from the route's config that should be passed into the form wizard
const props = [
'confirmStep',
'exitStep',
'saveAndExitStep',
'params'
];
Object.assign(wizardConfig, _.pick(config.route, props));
Expand Down
4 changes: 3 additions & 1 deletion sandbox/apps/sandbox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module.exports = {
},
'/dob': {
fields: ['dateOfBirth'],
locals: { showSaveAndExit: true },
next: '/address'
},
'/address': {
Expand Down Expand Up @@ -92,6 +93,7 @@ module.exports = {
next: '/confirm'
},
'/session-timeout': {},
'/exit': {}
'/exit': {},
'/save-and-exit': {}
}
};
14 changes: 10 additions & 4 deletions sandbox/apps/sandbox/translations/src/en/pages.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,15 @@
"message": "We have cleared your information to keep it secure. Your information has not been saved."
},
"session-timeout-warning": {
"dialog-title": "Your application will close soon",
"dialog-text": "If that happens, your progress will not be saved.",
"timeout-continue-button": "Stay on this page",
"dialog-exit-link": "Exit this form"
"dialog-title": "{{^showSaveAndExit}}Your application will close soon{{/showSaveAndExit}}{{#showSaveAndExit}}You will be signed out soon{{/showSaveAndExit}}",
"dialog-text": "{{^showSaveAndExit}}If that happens, your progress will not be saved.{{/showSaveAndExit}}{{#showSaveAndExit}}Any answers you have saved will not be affected, but your progress on this page will not be saved.{{/showSaveAndExit}}",
"timeout-continue-button": "{{^showSaveAndExit}}Stay on this page{{/showSaveAndExit}}{{#showSaveAndExit}}Stay signed in{{/showSaveAndExit}}",
"dialog-exit-link": "{{^showSaveAndExit}}Exit this form{{/showSaveAndExit}}{{#showSaveAndExit}}Sign out{{/showSaveAndExit}}"
},
"save-and-exit": {
"header": "You have been signed out",
"paragraph-1": "Your form doesn't appear to have been worked on for 30 minutes so we closed it for security.",
"paragraph-2": "Any answers you saved have not been affected.",
"paragraph-3": "You can sign back in to your application at any time by returning to the <a href='/' class='govuk-link'>start page</a>."
}
}
19 changes: 19 additions & 0 deletions sandbox/apps/sandbox/views/save-and-exit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{{<layout}}
{{$journeyHeader}}
{{#t}}journey.header{{/t}}
{{/journeyHeader}}

{{$propositionHeader}}
{{> partials-navigation}}
{{/propositionHeader}}

{{$header}}
{{header}}
{{/header}}

{{$content}}
<p>{{#t}}pages.save-and-exit.paragraph-1{{/t}}</p>
<p>{{#t}}pages.save-and-exit.paragraph-2{{/t}}</p>
<p>{{#t}}pages.save-and-exit.paragraph-3{{/t}}</p>
{{/content}}
{{/layout}}
24 changes: 24 additions & 0 deletions test/components/session-timeout-warning.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,30 @@ describe('session timeout warning component', () => {
locals.should.have.property('message').and.deep.equal('exit.message');
});

it('sets the custom content to true on the save-and-exit page if saveExitFormContent is set to true', () => {
config.saveExitFormContent = true;
req.form = {
options: {
route: '/save-and-exit'
}
};
const locals = instance.locals(req, res);
locals.should.have.property('saveExitFormContent').and.deep.equal(true);
});

it('does sets the default content on the save-and-exit page if saveExitFormContent is set to false', () => {
config.saveExitFormContent = false;
req.form = {
options: {
route: '/save-and-exit'
}
};
const locals = instance.locals(req, res);
locals.should.have.property('header').and.deep.equal('save-and-exit.header');
locals.should.have.property('title').and.deep.equal('save-and-exit.title');
locals.should.have.property('message').and.deep.equal('save-and-exit.message');
});

afterEach(() => {
Base.prototype.locals.restore();
});
Expand Down
34 changes: 34 additions & 0 deletions test/integration/bootstrap.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,5 +200,39 @@ describe('bootstrap()', () => {
});
behaviourOptions.confirmStep.should.equal('/summary');
});

it('can pass the exit step to controllers', () => {
bs = bootstrap({
fields: 'fields',
appConfig: appConfig,
routes: [{
views: `${root}/apps/app_1/views`,
exitStep: '/leave',
steps: {
'/one': {
behaviours: behaviour
}
}
}]
});
behaviourOptions.exitStep.should.equal('/leave');
});

it('can pass the exit step to controllers', () => {
bs = bootstrap({
fields: 'fields',
appConfig: appConfig,
routes: [{
views: `${root}/apps/app_1/views`,
saveAndExitStep: '/sign-out',
steps: {
'/one': {
behaviours: behaviour
}
}
}]
});
behaviourOptions.saveAndExitStep.should.equal('/sign-out');
});
});
});
2 changes: 2 additions & 0 deletions wizard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ const Wizard = (steps, fields, setts) => {
options.route = route;
options.appConfig = settings.appConfig;
options.confirmStep = settings.confirmStep;
options.exitStep = settings.exitStep;
options.saveAndExitStep = settings.saveAndExitStep;
options.clearSession = options.clearSession || false;
options.fieldsConfig = _.cloneDeep(fields);
options.sanitiseInputs = settings.sanitiseInputs;
Expand Down

0 comments on commit 0c6d28f

Please sign in to comment.