Skip to content
Open
8 changes: 7 additions & 1 deletion app/assets/sass/prototype-specific/_ios-dynamic-text.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
// apple ios dynamic text
// Scoped to .nhs-app-native only (set in app/views/layout-app.html for the
// native app webview) so this doesn't override typography site-wide -
// otherwise Safari/WebKit (including desktop Safari) applies the system
// Dynamic Type body size to every page, making text on the NHS website
// journey (and any other non-native-app page) render smaller than it
// should.
@supports (font: -apple-system-body) {
html {
.nhs-app-native {
font: -apple-system-body !important;
}
}
Expand Down
95 changes: 95 additions & 0 deletions app/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,101 @@
}
})

// Website (non-app) opt-out journey routes

router.post('/website/pages/opt-out/do-you-know-nhs-number-answer', function (req, res) {
const input = req.session.data.knowsNhsNumber

if (input === 'Yes') {
res.redirect('/website/pages/opt-out/enter-your-nhs-number')
} else if (input === 'No') {
res.redirect('/website/pages/opt-out/enter-your-name')
} else {
// Validation fallback if they select nothing
res.render('website/pages/opt-out/do-you-know-nhs-number', {
error: true
})
}
})

router.post('/website/pages/opt-out/enter-your-nhs-number-answer', function (req, res) {
const input = req.session.data.nhsNumber

if (!input || input.trim() === '') {
res.render('website/pages/opt-out/enter-your-nhs-number', {
error: true
})
} else {
res.redirect('/website/pages/opt-out/enter-your-postcode')
}
})

router.post('/website/pages/opt-out/enter-your-name-answer', function (req, res) {
const { firstName, lastName } = req.session.data

if (!firstName || !lastName) {
res.render('website/pages/opt-out/enter-your-name', {
error: true
})
} else {
res.redirect('/website/pages/opt-out/enter-your-postcode')
}
})

// The postcode page is shared by both the NHS number and name/date of
// birth routes through identity verification - branch based on whether
// an NHS number was already given.
router.post('/website/pages/opt-out/enter-your-postcode-answer', function (req, res) {
const { postcode, knowsNhsNumber } = req.session.data

if (!postcode || postcode.trim() === '') {
res.render('website/pages/opt-out/enter-your-postcode', {
error: true
})
} else if (knowsNhsNumber === 'Yes') {
res.redirect('/website/pages/opt-out/check-your-details-nhs-number')
} else {
res.redirect('/website/pages/opt-out/enter-date-of-birth')
}
})

router.post('/website/pages/opt-out/check-your-details-nhs-number-answer', function (req, res) {
// Simulate an identity check against the example patient record
const postcode = (req.session.data.postcode || '').replace(/\s+/g, '').toUpperCase()

if (postcode !== 'SW12CV') {
res.redirect('/website/pages/opt-out/identity-details-error')
} else {
res.redirect('/website/pages/opt-out/confirm-unsubscribe-request')
}
})

router.post('/website/pages/opt-out/check-your-details-answer', function (req, res) {
// Simulate an identity check against the example patient record
const postcode = (req.session.data.postcode || '').replace(/\s+/g, '').toUpperCase()

if (postcode !== 'SW12CV') {
res.redirect('/website/pages/opt-out/identity-details-error')
} else {
res.redirect('/website/pages/opt-out/confirm-unsubscribe-request')
}
})

router.post('/website/pages/opt-out/confirm-unsubscribe-request-answer', function (req, res) {
const input = req.session.data.confirmUnsubscribe

if (input === 'Unsubscribe from age-based messages') {
res.redirect('/website/pages/opt-out/confirmation-saved-yes')
} else if (input === 'Keep receiving age-based messages') {
res.redirect('/website/pages/opt-out/confirmation-saved-no')
} else {
// Validation fallback if they select nothing
res.render('website/pages/opt-out/confirm-unsubscribe-request', {
error: true
})
}
})

// V6 Routes

router.post('/v6/pages/opt-in/preferences-1-post', function (req, res) {
Expand Down
8 changes: 8 additions & 0 deletions app/views/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ <h1>Digital Child Health NHS App prototype</h1>
<p class="nhsuk-lede-text">
A prototype for Digital Child Health to test new design components and patterns for the NHS App.
</p>
<h2>NHS Website journeys</h2>
<p class="nhsuk-body">A non-app, standard NHS website version of the opt-out flow.</p>
<ul class="nhsuk-list">
<li>
<a href="/website/pages/opt-out/start">Opt out of age-based messages (website)</a>
</li>
</ul>

<h2>V6</h2>
<p class="nhsuk-body">Content designer has been added to the team </p>
<p class="nhsuk-body">21/07/2025 Updated the flow to reflect an "unsubscribe" route. </p>
Expand Down
103 changes: 103 additions & 0 deletions app/views/layout-nhsuk-website.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{% extends 'layout.html' %}

<!--
Layout for the start page of the NHS website opt-out journey only.

Start pages for services like this are typically hosted on the main
NHS.UK website (nhs.uk) - with the full NHS.UK header (search and
navigation) and breadcrumbs placing the page in the wider site - and the
"Start now" button takes the user out of nhs.uk and into the transactional
service itself. See:
https://service-manual.nhs.uk/design-system/patterns/start-page

Once the user clicks "Start now" they move into the transactional service,
which uses the simpler logo + service name header (layout-website.html) -
matching the pattern used by real NHS.UK services like "Find your NHS
number".

Only start.html extends this layout - it does not affect any other page
in this journey, or any other app journey.
-->

{% set mainClasses = "nhsuk-main-wrapper--s" %}

{% block pageTitle %}
Unsubscribe from age-based messages – NHS
{% endblock %}

<!-- Full NHS.UK website header: logo, search and navigation, as this page
is hosted on the main NHS.UK website rather than within the service -->
{% block header %}
{{ header({
search: {
action: "https://www.nhs.uk/search"
},
navigation: {
items: [
{ href: "https://www.nhs.uk/nhs-services/", text: "NHS services" },
{ href: "https://www.nhs.uk/conditions/", text: "Health A to Z" },
{ href: "https://www.nhs.uk/live-well/", text: "Live Well" },
{ href: "https://www.nhs.uk/mental-health/", text: "Mental health" },
{ href: "https://www.nhs.uk/care-and-support/", text: "Care and support" },
{ href: "https://www.nhs.uk/pregnancy/", text: "Pregnancy" }
]
}
}) }}
{% endblock %}

<!-- Breadcrumb placing this start page within the wider NHS.UK website -->
{% block beforeContent %}
{{ breadcrumb({
items: [
{ href: "https://www.nhs.uk", text: "Home" },
{ href: "https://www.nhs.uk/nhs-services/", text: "NHS services" },
{ href: "https://www.nhs.uk/nhs-services/online-services/", text: "Online services" }
]
}) }}
{% endblock %}

<!-- Standard NHS.UK website footer: full navigation footer (not the
simpler service-specific meta footer used once inside the service),
matching https://www.nhs.uk/nhs-services/hospitals/book-an-appointment/ -->
{% block footer %}
{{ footer({
copyright: {
text: "© Crown copyright"
},
navigation: [
{
items: [
{ href: "https://www.nhs.uk/", text: "Home" },
{ href: "https://www.nhs.uk/health-a-to-z/", text: "Health A to Z" },
{ href: "https://www.nhs.uk/nhs-services/", text: "NHS services" },
{ href: "https://www.nhs.uk/live-well/", text: "Healthy living" },
{ href: "https://www.nhs.uk/mental-health/", text: "Mental health" },
{ href: "https://www.nhs.uk/social-care-and-support/", text: "Care and support" }
]
},
{
items: [
{ href: "https://www.nhs.uk/nhs-app/", text: "NHS App" },
{ href: "https://www.nhs.uk/nhs-services/online-services/find-nhs-number/", text: "Find my NHS number" },
{ href: "https://www.nhs.uk/using-the-nhs/about-the-nhs/", text: "About the NHS" },
{ href: "https://www.nhs.uk/using-the-nhs/healthcare-abroad/", text: "Healthcare abroad" }
]
},
{
items: [
{ href: "https://www.nhs.uk/our-policies/manage-nhs-profiles/", text: "Manage NHS profiles" },
{ href: "https://www.nhs.uk/nhs-sites/", text: "Other NHS websites" }
]
},
{
items: [
{ href: "https://www.nhs.uk/about-us/", text: "About us" },
{ href: "https://www.nhs.uk/accessibility-statement/", text: "Accessibility statement" },
{ href: "https://www.nhs.uk/our-policies/", text: "Our policies" },
{ href: "https://www.nhs.uk/our-policies/choose-your-cookie-settings/", text: "Cookies" },
{ href: "/prototype-admin/reset?returnPage=" + (currentPage | urlencode), text: "Reset data" }
]
}
]
}) }}
{% endblock %}
65 changes: 65 additions & 0 deletions app/views/layout-website.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{% extends 'layout.html' %}

<!--
Layout for the standalone NHS website opt-out journey only.
This overrides the header and footer to use the standard NHS.UK
transactional-service pattern (logo + service name, no app navigation)
instead of the NHS App prototype branding used elsewhere in layout.html.
Only views that extend this layout are affected - the app journeys
(layout-app.html and friends) are untouched.
-->

<!-- Question pages use a smaller top main wrapper padding alongside a back link -->
{% set mainClasses = "nhsuk-main-wrapper--s" %}

{% block pageTitle %}
Digital Child Health – NHS
{% endblock %}

<!-- Standard NHS.UK header: logo and service name only, no navigation or
search, as recommended for a transactional service journey -->
{% block header %}
{{ header({
service: {
text: "Digital Child Health",
href: "/website/pages/opt-out/start"
}
}) }}
{% endblock %}

<!-- Standard NHS.UK footer meta links, matching the design -->
{% block footer %}
{{ footer({
copyright: {
text: "© NHS England"
},
meta: {
items: [
{
href: "#",
text: "Accessibility statement"
},
{
href: "#",
text: "Contact us"
},
{
href: "#",
text: "Cookies"
},
{
href: "#",
text: "Privacy policy"
},
{
href: "#",
text: "Terms and conditions"
},
{
href: "/prototype-admin/reset?returnPage=" + (currentPage | urlencode),
text: "Reset data"
}
]
}
}) }}
{% endblock %}
84 changes: 84 additions & 0 deletions app/views/website/pages/opt-out/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# NHS website opt-out journey — flow diagram

Non-app, standard NHS website version of the age-based messages opt-out flow.
Routes live under `/website/pages/opt-out/`, views in
`app/views/website/pages/opt-out/`, and POST handlers in `app/routes.js`.

```mermaid
flowchart TD
start["01 start<br/>Unsubscribe introduction"]
nhsChoice["05 do-you-know-nhs-number<br/>Do you know your NHS number?"]
enterNhs["06 enter-your-nhs-number<br/>What is your NHS number?"]
enterName["09 enter-your-name<br/>Enter your full name"]
postcode["03 enter-your-postcode<br/>What is your post code"]
dob["02 enter-date-of-birth<br/>What is your date of birth?"]
checkNhs["07 check-your-details-nhs-number<br/>Confirm details (NHS number + postcode)"]
checkNoNhs["08 check-your-details<br/>Confirm details (name, DOB, postcode)"]
error["04 identity-details-error<br/>We can't identify you"]
confirmRequest["13 confirm-unsubscribe-request<br/>Keep or unsubscribe?"]
keep["11 confirmation-saved-no<br/>You will continue to receive messages"]
unsub["12 confirmation-saved-yes<br/>You will no longer receive messages"]

start --> nhsChoice
nhsChoice -- Yes --> enterNhs
nhsChoice -- No --> enterName
enterNhs --> postcode
enterName --> postcode
postcode -- "has NHS number" --> checkNhs
postcode -- "no NHS number" --> dob
dob --> checkNoNhs
checkNhs -- "identity matched" --> confirmRequest
checkNhs -- "identity not matched" --> error
checkNoNhs -- "identity matched" --> confirmRequest
checkNoNhs -- "identity not matched" --> error
error -- Continue --> nhsChoice
confirmRequest -- Keep --> keep
confirmRequest -- Unsubscribe --> unsub
```

## Notes

- The postcode step is shared by both identity paths (NHS number, or
name + date of birth).
- The identity check is simulated in `app/routes.js`: enter postcode
`SW1 2CV` to pass verification on either path; anything else routes to
the `identity-details-error` page, whose Continue button loops back to
`do-you-know-nhs-number` to retry.
- Both confirmation pages (`confirmation-saved-yes` / `confirmation-saved-no`)
use the standard `nhsuk-frontend` panel component, not app-specific styling.
- Session data keys use `camelCase` (`knowsNhsNumber`, `nhsNumber`, `firstName`,
`lastName`, `postcode`, `dateOfBirth`, `confirmUnsubscribe`), and radio
values match their visible text exactly, per the
[NHS prototype kit guide](https://github.com/edwardhorsford/NHS-LLM-documentation).
- POST routes use the `-answer` suffix (for example `enter-your-name-answer`)
rather than `-post`, and only exist where branching or validation is
needed — `enter-date-of-birth` posts straight to `check-your-details`
since no route is required there.
- Back links use the real `backLink()` component in a `beforeContent` block.
The rest of this prototype's pages set `{% set backLink = true %}`. which
`layout.html` does not read, so that flag renders nothing — that pattern
is left as-is elsewhere to avoid touching unrelated journeys.
- `start.html` extends a separate layout, `app/views/layout-nhsuk-website.html`,
not `layout-website.html`. Start pages for services like this are usually
hosted on the main NHS.UK website, with the full NHS.UK header (search +
navigation) and breadcrumbs, per the
[start page pattern](https://service-manual.nhs.uk/design-system/patterns/start-page).
The "Start now" button then takes the user off nhs.uk and into the
transactional service itself, which is why every other page in this
journey switches to the simpler logo + service name header
(`layout-website.html`) - matching how real services like "Find your NHS
number" work in production.
- All other pages in this journey extend `app/views/layout-website.html` (not
`layout.html`/`layout-app.html` directly). It overrides only the header
and footer to match the standard NHS.UK transactional-service pattern —
logo + service name, no app navigation, and a footer with Accessibility
statement / Contact us / Cookies / Privacy policy / Terms and conditions
links per the [header](https://service-manual.nhs.uk/design-system/components/header)
and [footer](https://service-manual.nhs.uk/design-system/components/footer)
guidance — and sets `mainClasses = "nhsuk-main-wrapper--s"` as recommended
by the [question pages](https://service-manual.nhs.uk/design-system/patterns/question-pages)
and [start page](https://service-manual.nhs.uk/design-system/patterns/start-page)
patterns, which fixes the excess top padding that showed above the back
link. This override is scoped to `layout-website.html` only, so the app
journeys (which still extend `layout.html`/`layout-app.html`) are
unaffected.
Loading