From b8a41094d9be6a4d3e63fce7ab6bb33b6549a017 Mon Sep 17 00:00:00 2001 From: Colin Rotherham <work@colinr.com> Date: Tue, 14 Nov 2023 15:00:35 +0000 Subject: [PATCH 1/4] Move Express.js params to `app.param()` locals --- packages/govuk-frontend-review/src/app.mjs | 80 +++++++++++++++------- 1 file changed, 56 insertions(+), 24 deletions(-) diff --git a/packages/govuk-frontend-review/src/app.mjs b/packages/govuk-frontend-review/src/app.mjs index df72b045a4..24ac5473bf 100644 --- a/packages/govuk-frontend-review/src/app.mjs +++ b/packages/govuk-frontend-review/src/app.mjs @@ -80,6 +80,55 @@ export default async () => { // Configure nunjucks const env = nunjucks.renderer(app) + // Define parameters + + /** + * Handle parameter :componentName + * + * Finds all component fixtures and default example + */ + app.param('componentName', (req, res, next, componentName) => { + const exampleName = 'default' + + // Find all fixtures for component + const componentFixtures = componentsFixtures.find( + ({ component }) => component === componentName + ) + + // Find default fixture for component + const componentFixture = componentFixtures?.fixtures.find( + ({ name }) => name === exampleName + ) + + // Add response locals + res.locals.componentName = componentName + res.locals.componentFixtures = componentFixtures + res.locals.componentFixture = componentFixture + res.locals.exampleName = 'default' + + next() + }) + + /** + * Handle parameter :exampleName + * + * Finds component fixture for example and updates locals + */ + app.param('exampleName', (req, res, next, exampleName) => { + const { componentFixtures } = res.locals + + // Replace default fixture with named example + const componentFixture = componentFixtures?.fixtures.find( + ({ name }) => nunjucks.filters.slugify(name) === exampleName + ) + + // Update response locals + res.locals.componentFixture = componentFixture + res.locals.exampleName = exampleName + + next() + }) + // Define routes // Index page - render the component list template @@ -92,14 +141,6 @@ export default async () => { }) }) - // Whenever the route includes a :componentName parameter, read the component fixtures - app.param('componentName', function (req, res, next, componentName) { - res.locals.componentFixtures = componentsFixtures.find( - ({ component }) => component === componentName - ) - next() - }) - // All components redirect app.get('/components/all', function (req, res) { res.redirect('./') @@ -119,14 +160,11 @@ export default async () => { app.get( '/components/:componentName/:exampleName?/preview', function (req, res, next) { - const { componentName, exampleName = 'default' } = req.params - - /** @type {ComponentFixtures | undefined} */ - const componentFixtures = res.locals.componentFixtures - - const fixture = componentFixtures?.fixtures.find( - (fixture) => nunjucks.filters.slugify(fixture.name) === exampleName - ) + const { + componentName, + componentFixtures: fixtures, + componentFixture: fixture + } = res.locals if (!fixture) { return next() @@ -153,17 +191,15 @@ export default async () => { res.render('component-preview', { bodyClasses, - componentName, componentView, - exampleName, - previewLayout: componentFixtures.previewLayout + previewLayout: fixtures.previewLayout }) } ) // Example view app.get('/examples/:exampleName', function (req, res) { - const { exampleName } = req.params + const { exampleName } = res.locals res.render(`examples/${exampleName}/index`, { exampleName, @@ -179,10 +215,6 @@ export default async () => { return app } -/** - * @typedef {import('@govuk-frontend/lib/components').ComponentFixtures} ComponentFixtures - */ - /** * @typedef {object} FeatureFlags * @property {boolean} isDeployedToHeroku - Review app using `HEROKU_APP` From 12ce02a777649aa2d8cbfd79705e74eba611b4b2 Mon Sep 17 00:00:00 2001 From: Colin Rotherham <work@colinr.com> Date: Tue, 14 Nov 2023 13:08:04 +0000 Subject: [PATCH 2/4] Add routes and views for 404, 500 errors --- packages/govuk-frontend-review/src/app.mjs | 36 ++++++++++++++++--- .../src/common/lib/files.mjs | 1 + .../src/common/nunjucks/filters/index.mjs | 1 + .../src/common/nunjucks/filters/inspect.mjs | 16 +++++++++ .../nunjucks/globals/get-nunjucks-code.mjs | 11 ++---- .../src/routes/full-page-examples.mjs | 8 ++++- .../src/views/errors/404.njk | 18 ++++++++++ .../src/views/errors/500.njk | 24 +++++++++++++ 8 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 packages/govuk-frontend-review/src/common/nunjucks/filters/inspect.mjs create mode 100644 packages/govuk-frontend-review/src/views/errors/404.njk create mode 100644 packages/govuk-frontend-review/src/views/errors/500.njk diff --git a/packages/govuk-frontend-review/src/app.mjs b/packages/govuk-frontend-review/src/app.mjs index 24ac5473bf..00f63215c8 100644 --- a/packages/govuk-frontend-review/src/app.mjs +++ b/packages/govuk-frontend-review/src/app.mjs @@ -147,8 +147,13 @@ export default async () => { }) // Component examples - app.get('/components/:componentName?', (req, res) => { - const { componentName } = req.params + app.get('/components/:componentName?', (req, res, next) => { + const { componentName } = res.locals + + // Unknown component, continue to page not found + if (componentName && !componentNames.includes(componentName)) { + return next() + } res.render(componentName ? 'component' : 'components', { componentsFixtures, @@ -166,7 +171,8 @@ export default async () => { componentFixture: fixture } = res.locals - if (!fixture) { + // Unknown component or fixture, continue to page not found + if (!componentNames.includes(componentName) || !fixtures || !fixture) { return next() } @@ -198,9 +204,14 @@ export default async () => { ) // Example view - app.get('/examples/:exampleName', function (req, res) { + app.get('/examples/:exampleName', function (req, res, next) { const { exampleName } = res.locals + // Unknown example, continue to page not found + if (!exampleNames.includes(exampleName)) { + return next() + } + res.render(`examples/${exampleName}/index`, { exampleName, @@ -212,6 +223,23 @@ export default async () => { // Full page example views routes.fullPageExamples(app) + /** + * Page not found handler + */ + app.use((req, res) => { + res.status(404).render('errors/404') + }) + + /** + * Error handler + */ + app.use((error, req, res, next) => { + console.error(error) + res.status(500).render('errors/500', { + error + }) + }) + return app } diff --git a/packages/govuk-frontend-review/src/common/lib/files.mjs b/packages/govuk-frontend-review/src/common/lib/files.mjs index 35e973036c..37b08f2ae9 100644 --- a/packages/govuk-frontend-review/src/common/lib/files.mjs +++ b/packages/govuk-frontend-review/src/common/lib/files.mjs @@ -55,6 +55,7 @@ export async function getFullPageExamples() { * * @typedef {object} FullPageExample * @property {string} name - Example name + * @property {string} path - Example directory name * @property {string} [scenario] - Description explaining the example * @property {string} [notes] - Additional notes about the example */ diff --git a/packages/govuk-frontend-review/src/common/nunjucks/filters/index.mjs b/packages/govuk-frontend-review/src/common/nunjucks/filters/index.mjs index 74f82137f8..5991549605 100644 --- a/packages/govuk-frontend-review/src/common/nunjucks/filters/index.mjs +++ b/packages/govuk-frontend-review/src/common/nunjucks/filters/index.mjs @@ -3,6 +3,7 @@ */ export { componentNameToMacroName } from '@govuk-frontend/lib/names' export { highlight } from './highlight.mjs' +export { inspect } from './inspect.mjs' export { markdown } from './markdown.mjs' export { slugify } from './slugify.mjs' export { unslugify } from './unslugify.mjs' diff --git a/packages/govuk-frontend-review/src/common/nunjucks/filters/inspect.mjs b/packages/govuk-frontend-review/src/common/nunjucks/filters/inspect.mjs new file mode 100644 index 0000000000..1b2c25b6f3 --- /dev/null +++ b/packages/govuk-frontend-review/src/common/nunjucks/filters/inspect.mjs @@ -0,0 +1,16 @@ +import util from 'util' + +/** + * Format JavaScript objects as strings + * + * @param {unknown} object - JavaScript object to format + * @returns {string} Formatted string + */ +export function inspect(object) { + return util.inspect(object, { + compact: false, + depth: Infinity, + maxArrayLength: Infinity, + maxStringLength: Infinity + }) +} diff --git a/packages/govuk-frontend-review/src/common/nunjucks/globals/get-nunjucks-code.mjs b/packages/govuk-frontend-review/src/common/nunjucks/globals/get-nunjucks-code.mjs index 95fc9f3a15..6c785affae 100644 --- a/packages/govuk-frontend-review/src/common/nunjucks/globals/get-nunjucks-code.mjs +++ b/packages/govuk-frontend-review/src/common/nunjucks/globals/get-nunjucks-code.mjs @@ -1,9 +1,7 @@ -import { inspect } from 'util' - import prettier from '@prettier/sync' import { outdent } from 'outdent' -import { componentNameToMacroName } from '../filters/index.mjs' +import { inspect, componentNameToMacroName } from '../filters/index.mjs' /** * Component Nunjucks code (formatted) @@ -16,12 +14,7 @@ export function getNunjucksCode(componentName, options) { const macroName = componentNameToMacroName(componentName) // Allow nested HTML strings to wrap at `\n` - const paramsFormatted = inspect(options.context, { - compact: false, - depth: Infinity, - maxArrayLength: Infinity, - maxStringLength: Infinity - }) + const paramsFormatted = inspect(options.context) // Format Nunjucks safely with double quotes const macroFormatted = prettier.format(`${macroName}(${paramsFormatted})`, { diff --git a/packages/govuk-frontend-review/src/routes/full-page-examples.mjs b/packages/govuk-frontend-review/src/routes/full-page-examples.mjs index 90c38d0a75..088c227e0b 100644 --- a/packages/govuk-frontend-review/src/routes/full-page-examples.mjs +++ b/packages/govuk-frontend-review/src/routes/full-page-examples.mjs @@ -2,6 +2,7 @@ import { getFullPageExamples } from '../common/lib/files.mjs' import * as routes from '../views/full-page-examples/index.mjs' const fullPageExamples = await getFullPageExamples() +const fullPageExampleNames = fullPageExamples.map(({ path }) => path) /** * @param {import('express').Application} app @@ -29,9 +30,14 @@ export default (app) => { }) // Display full page examples index by default if not handled already - app.get('/full-page-examples/:exampleName', (req, res) => { + app.get('/full-page-examples/:exampleName', (req, res, next) => { const { exampleName } = req.params + // No matching example so continue to page not found + if (!fullPageExampleNames.includes(exampleName)) { + return next() + } + res.render(`full-page-examples/${exampleName}/index`) }) } diff --git a/packages/govuk-frontend-review/src/views/errors/404.njk b/packages/govuk-frontend-review/src/views/errors/404.njk new file mode 100644 index 0000000000..c7f3a2c7a8 --- /dev/null +++ b/packages/govuk-frontend-review/src/views/errors/404.njk @@ -0,0 +1,18 @@ +{% extends "layouts/layout.njk" %} + +{% block pageTitle %}Page not found - GOV.UK Frontend{% endblock %} + +{% block content %} + <div class="govuk-grid-row"> + <div class="govuk-grid-column-three-quarters govuk-grid-column-two-thirds-from-desktop"> + <h1 class="govuk-heading-l">Page not found</h1> + + <p class="govuk-body">If you typed the web address, check it is correct.</p> + <p class="govuk-body">If you pasted the web address, check you copied the entire address.</p> + + <p class="govuk-body"> + <a class="govuk-link" href="https://design-system.service.gov.uk/get-in-touch/" rel="noreferrer noopener" target="_blank">Contact the Design System team (opens in new tab)</a> if you believe you are seeing this message in error. + </p> + </div> + </div> +{% endblock %} diff --git a/packages/govuk-frontend-review/src/views/errors/500.njk b/packages/govuk-frontend-review/src/views/errors/500.njk new file mode 100644 index 0000000000..f872676087 --- /dev/null +++ b/packages/govuk-frontend-review/src/views/errors/500.njk @@ -0,0 +1,24 @@ +{% extends "layouts/layout.njk" %} + +{% block pageTitle %}Sorry, there is a problem with the service - GOV.UK Frontend{% endblock %} + +{% block content %} + <div class="govuk-grid-row"> + <div class="govuk-grid-column-three-quarters govuk-grid-column-two-thirds-from-desktop"> + <h1 class="govuk-heading-l">Sorry, there is a problem with the service</h1> + + <p class="govuk-body">Try again later.</p> + + <p class="govuk-body"> + <a class="govuk-link" href="https://design-system.service.gov.uk/get-in-touch/" rel="noreferrer noopener" target="_blank">Contact the Design System team (opens in new tab)</a> if you believe you are seeing this message in error. + </p> + </div> + </div> + + <div class="govuk-grid-row"> + <div class="govuk-grid-column-full"> + <pre class="app-code"><code tabindex="0" class="app-code__container"> + {{- error | inspect | safe -}} + </code></pre> + </div> +{% endblock %} From 9ab2a333d867dd5e2c70ddab952235b820b59797 Mon Sep 17 00:00:00 2001 From: Colin Rotherham <work@colinr.com> Date: Mon, 20 Nov 2023 11:52:12 +0000 Subject: [PATCH 3/4] Add breadcrumbs to top-level pages (including errors) Review app error pages include breadcrumbs only because we have no header component with navigation links back to home etc --- .../src/views/component.njk | 8 ++++++-- .../src/views/components.njk | 14 +++++++++++--- .../src/views/errors/404.njk | 16 ++++++++++++++++ .../src/views/errors/500.njk | 16 ++++++++++++++++ .../src/views/full-page-examples/index.njk | 14 +++++++++++--- 5 files changed, 60 insertions(+), 8 deletions(-) diff --git a/packages/govuk-frontend-review/src/views/component.njk b/packages/govuk-frontend-review/src/views/component.njk index f687ded549..a853ab803c 100644 --- a/packages/govuk-frontend-review/src/views/component.njk +++ b/packages/govuk-frontend-review/src/views/component.njk @@ -15,11 +15,15 @@ {% block beforeContent %} {{ govukBreadcrumbs({ - "items": [ + items: [ { - text: 'GOV.UK Frontend', + text: "GOV.UK Frontend", href: "/" }, + { + text: "All components", + href: "/components" + }, { text: componentName | unslugify } diff --git a/packages/govuk-frontend-review/src/views/components.njk b/packages/govuk-frontend-review/src/views/components.njk index 48f263aad9..ea1c6f906c 100644 --- a/packages/govuk-frontend-review/src/views/components.njk +++ b/packages/govuk-frontend-review/src/views/components.njk @@ -1,6 +1,6 @@ {% extends "layouts/full-width-landmarks.njk" %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} +{% from "govuk/components/breadcrumbs/macro.njk" import govukBreadcrumbs %} {% from "macros/showExamples.njk" import showExamples %} {% block pageTitle %}All components - GOV.UK Frontend{% endblock %} @@ -10,8 +10,16 @@ {% endset %} {% block beforeContent %} - {{ govukBackLink({ - href: "/" + {{ govukBreadcrumbs({ + items: [ + { + text: "GOV.UK Frontend", + href: "/" + }, + { + text: "All components" + } + ] }) }} {% endblock %} diff --git a/packages/govuk-frontend-review/src/views/errors/404.njk b/packages/govuk-frontend-review/src/views/errors/404.njk index c7f3a2c7a8..677678bfa1 100644 --- a/packages/govuk-frontend-review/src/views/errors/404.njk +++ b/packages/govuk-frontend-review/src/views/errors/404.njk @@ -1,7 +1,23 @@ {% extends "layouts/layout.njk" %} +{% from "govuk/components/breadcrumbs/macro.njk" import govukBreadcrumbs %} + {% block pageTitle %}Page not found - GOV.UK Frontend{% endblock %} +{% block beforeContent %} + {{ govukBreadcrumbs({ + items: [ + { + text: "GOV.UK Frontend", + href: "/" + }, + { + text: "Page not found" + } + ] + }) }} +{% endblock %} + {% block content %} <div class="govuk-grid-row"> <div class="govuk-grid-column-three-quarters govuk-grid-column-two-thirds-from-desktop"> diff --git a/packages/govuk-frontend-review/src/views/errors/500.njk b/packages/govuk-frontend-review/src/views/errors/500.njk index f872676087..6bfcd5192d 100644 --- a/packages/govuk-frontend-review/src/views/errors/500.njk +++ b/packages/govuk-frontend-review/src/views/errors/500.njk @@ -1,7 +1,23 @@ {% extends "layouts/layout.njk" %} +{% from "govuk/components/breadcrumbs/macro.njk" import govukBreadcrumbs %} + {% block pageTitle %}Sorry, there is a problem with the service - GOV.UK Frontend{% endblock %} +{% block beforeContent %} + {{ govukBreadcrumbs({ + items: [ + { + text: "GOV.UK Frontend", + href: "/" + }, + { + text: "Server error" + } + ] + }) }} +{% endblock %} + {% block content %} <div class="govuk-grid-row"> <div class="govuk-grid-column-three-quarters govuk-grid-column-two-thirds-from-desktop"> diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/index.njk b/packages/govuk-frontend-review/src/views/full-page-examples/index.njk index b059179e15..86c261ce68 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/index.njk +++ b/packages/govuk-frontend-review/src/views/full-page-examples/index.njk @@ -1,11 +1,19 @@ {% extends "layouts/layout.njk" %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} +{% from "govuk/components/breadcrumbs/macro.njk" import govukBreadcrumbs %} {% block pageTitle %}Full page examples - GOV.UK Frontend{% endblock %} {% block beforeContent %} - {{ govukBackLink({ - href: "/" + {{ govukBreadcrumbs({ + items: [ + { + text: "GOV.UK Frontend", + href: "/" + }, + { + text: "Full page examples" + } + ] }) }} {% endblock %} From d5eac385c957acc361f338ab53fb63ab837cd802 Mon Sep 17 00:00:00 2001 From: Colin Rotherham <work@colinr.com> Date: Tue, 14 Nov 2023 17:17:37 +0000 Subject: [PATCH 4/4] Add JSDoc declarations --- packages/govuk-frontend-review/src/app.mjs | 197 ++++++++++++------ .../src/routes/full-page-examples.mjs | 7 +- 2 files changed, 137 insertions(+), 67 deletions(-) diff --git a/packages/govuk-frontend-review/src/app.mjs b/packages/govuk-frontend-review/src/app.mjs index 00f63215c8..a4cf3afea1 100644 --- a/packages/govuk-frontend-review/src/app.mjs +++ b/packages/govuk-frontend-review/src/app.mjs @@ -87,52 +87,74 @@ export default async () => { * * Finds all component fixtures and default example */ - app.param('componentName', (req, res, next, componentName) => { - const exampleName = 'default' - - // Find all fixtures for component - const componentFixtures = componentsFixtures.find( - ({ component }) => component === componentName - ) - - // Find default fixture for component - const componentFixture = componentFixtures?.fixtures.find( - ({ name }) => name === exampleName - ) - - // Add response locals - res.locals.componentName = componentName - res.locals.componentFixtures = componentFixtures - res.locals.componentFixture = componentFixture - res.locals.exampleName = 'default' - - next() - }) + app.param( + 'componentName', + + /** + * @param {import('express').Request} req + * @param {import('express').Response<{}, Partial<PreviewLocals>>} res + * @param {import('express').NextFunction} next + * @param {string} componentName + */ + (req, res, next, componentName) => { + const exampleName = 'default' + + // Find all fixtures for component + const componentFixtures = componentsFixtures.find( + ({ component }) => component === componentName + ) + + // Find default fixture for component + const componentFixture = componentFixtures?.fixtures.find( + ({ name }) => name === exampleName + ) + + // Add response locals + res.locals.componentName = componentName + res.locals.componentFixtures = componentFixtures + res.locals.componentFixture = componentFixture + res.locals.exampleName = 'default' + + next() + } + ) /** * Handle parameter :exampleName * * Finds component fixture for example and updates locals */ - app.param('exampleName', (req, res, next, exampleName) => { - const { componentFixtures } = res.locals - - // Replace default fixture with named example - const componentFixture = componentFixtures?.fixtures.find( - ({ name }) => nunjucks.filters.slugify(name) === exampleName - ) - - // Update response locals - res.locals.componentFixture = componentFixture - res.locals.exampleName = exampleName - - next() - }) + app.param( + 'exampleName', + + /** + * @param {import('express').Request} req + * @param {import('express').Response<{}, Partial<PreviewLocals>>} res + * @param {import('express').NextFunction} next + * @param {string} exampleName + */ + (req, res, next, exampleName) => { + const { componentFixtures } = res.locals + + // Replace default fixture with named example + const componentFixture = componentFixtures?.fixtures.find( + ({ name }) => nunjucks.filters.slugify(name) === exampleName + ) + + // Update response locals + res.locals.componentFixture = componentFixture + res.locals.exampleName = exampleName + + next() + } + ) // Define routes - // Index page - render the component list template - app.get('/', async function (req, res) { + /** + * Review app home page + */ + app.get('/', (req, res) => { res.render('index', { componentNames, componentNamesWithJavaScript, @@ -141,30 +163,53 @@ export default async () => { }) }) - // All components redirect + /** + * All components redirect + */ app.get('/components/all', function (req, res) { res.redirect('./') }) - // Component examples - app.get('/components/:componentName?', (req, res, next) => { - const { componentName } = res.locals + /** + * Component examples + */ + app.get( + '/components/:componentName?', + + /** + * @param {import('express').Request} req + * @param {import('express').Response<{}, Partial<PreviewLocals>>} res + * @param {import('express').NextFunction} next + * @returns {void} + */ + (req, res, next) => { + const { componentName } = res.locals + + // Unknown component, continue to page not found + if (componentName && !componentNames.includes(componentName)) { + return next() + } - // Unknown component, continue to page not found - if (componentName && !componentNames.includes(componentName)) { - return next() + res.render(componentName ? 'component' : 'components', { + componentsFixtures, + componentName + }) } + ) - res.render(componentName ? 'component' : 'components', { - componentsFixtures, - componentName - }) - }) - - // Component example preview + /** + * Component example preview + */ app.get( '/components/:componentName/:exampleName?/preview', - function (req, res, next) { + + /** + * @param {import('express').Request} req + * @param {import('express').Response<{}, Partial<PreviewLocals>>} res + * @param {import('express').NextFunction} next + * @returns {void} + */ + (req, res, next) => { const { componentName, componentFixtures: fixtures, @@ -203,22 +248,34 @@ export default async () => { } ) - // Example view - app.get('/examples/:exampleName', function (req, res, next) { - const { exampleName } = res.locals - - // Unknown example, continue to page not found - if (!exampleNames.includes(exampleName)) { - return next() - } + /** + * Example view + */ + app.get( + '/examples/:exampleName', + + /** + * @param {import('express').Request} req + * @param {import('express').Response<{}, Partial<PreviewLocals>>} res + * @param {import('express').NextFunction} next + * @returns {void} + */ + (req, res, next) => { + const { exampleName } = res.locals + + // Unknown example, continue to page not found + if (!exampleNames.includes(exampleName)) { + return next() + } - res.render(`examples/${exampleName}/index`, { - exampleName, + res.render(`examples/${exampleName}/index`, { + exampleName, - // Render with random number for unique non-visited links - randomPageHash: (Math.random() * 1000000).toFixed() - }) - }) + // Render with random number for unique non-visited links + randomPageHash: (Math.random() * 1000000).toFixed() + }) + } + ) // Full page example views routes.fullPageExamples(app) @@ -243,6 +300,14 @@ export default async () => { return app } +/** + * @typedef {object} PreviewLocals + * @property {import('@govuk-frontend/lib/components').ComponentFixtures} componentFixtures - All Component fixtures + * @property {import('@govuk-frontend/lib/components').ComponentFixture} [componentFixture] - Single component fixture + * @property {string} componentName - Component name + * @property {string} [exampleName] - Example name + */ + /** * @typedef {object} FeatureFlags * @property {boolean} isDeployedToHeroku - Review app using `HEROKU_APP` diff --git a/packages/govuk-frontend-review/src/routes/full-page-examples.mjs b/packages/govuk-frontend-review/src/routes/full-page-examples.mjs index 088c227e0b..641401dadb 100644 --- a/packages/govuk-frontend-review/src/routes/full-page-examples.mjs +++ b/packages/govuk-frontend-review/src/routes/full-page-examples.mjs @@ -23,13 +23,18 @@ export default (app) => { routes.whatIsYourPostcode(app) routes.whatWasTheLastCountryYouVisited(app) + /** + * Full page examples index + */ app.get('/full-page-examples', async (req, res) => { res.render('full-page-examples/index', { fullPageExamples }) }) - // Display full page examples index by default if not handled already + /** + * Full page example + */ app.get('/full-page-examples/:exampleName', (req, res, next) => { const { exampleName } = req.params