diff --git a/.env.example b/.env.example index cccdc55..508ec88 100644 --- a/.env.example +++ b/.env.example @@ -12,6 +12,7 @@ COMPONENT_API_TIMEOUT_DEADLINE=5000 COMMON_COMPONENTS_ENABLED=true DPS_HOME_PAGE_URL=https://digital-dev.prison.service.justice.gov.uk PRISONER_PROFILE_URL=https://prisoner-dev.digital.prison.service.justice.gov.uk +LEGACY_KEY_WORKERS_UI_URL=https://dev.manage-key-workers.service.justice.gov.uk # Credentials for allowing user access AUTH_CODE_CLIENT_ID=hmpps-typescript-template diff --git a/assets/js/card.js b/assets/js/card.js new file mode 100644 index 0000000..5d2e497 --- /dev/null +++ b/assets/js/card.js @@ -0,0 +1,11 @@ +function Card(container) { + this.container = container + + if (this.container.querySelector('a') !== null) { + this.container.addEventListener('click', () => { + this.container.querySelector('a').click() + }) + } +} + +export default Card diff --git a/assets/js/index.js b/assets/js/index.js index 9243e49..4a3323f 100644 --- a/assets/js/index.js +++ b/assets/js/index.js @@ -1,5 +1,12 @@ import * as govukFrontend from 'govuk-frontend' import * as mojFrontend from '@ministryofjustice/frontend' +import Card from './card' +import { nodeListForEach } from './utils' govukFrontend.initAll() mojFrontend.initAll() + +var $cards = document.querySelectorAll('.card--clickable') +nodeListForEach($cards, function ($card) { + new Card($card) +}) diff --git a/assets/js/utils.js b/assets/js/utils.js new file mode 100644 index 0000000..599c11f --- /dev/null +++ b/assets/js/utils.js @@ -0,0 +1,10 @@ +function nodeListForEach(nodes, callback) { + if (window.NodeList.prototype.forEach) { + return nodes.forEach(callback) + } + for (var i = 0; i < nodes.length; i++) { + callback.call(window, nodes[i], i, nodes) + } +} + +export { nodeListForEach } diff --git a/assets/scss/application.scss b/assets/scss/application.scss index 8575b91..7fc351c 100644 --- a/assets/scss/application.scss +++ b/assets/scss/application.scss @@ -10,5 +10,6 @@ $govuk-page-width: $moj-page-width; @import 'node_modules/@ministryofjustice/hmpps-connect-dps-components/dist/assets/footer'; @import 'node_modules/@ministryofjustice/hmpps-connect-dps-components/dist/assets/header-bar'; +@import './components/card'; @import './components/header-bar'; -@import './local'; \ No newline at end of file +@import './local'; diff --git a/assets/scss/components/_card.scss b/assets/scss/components/_card.scss new file mode 100644 index 0000000..01a3934 --- /dev/null +++ b/assets/scss/components/_card.scss @@ -0,0 +1,98 @@ +/* ========================================================================== + COMPONENTS / #CARD + ========================================================================== */ + +$card-border-width: 1px; +$card-border-bottom-width: govuk-spacing(1); +$card-border-hover-color: $govuk-border-colour; +$card-border-color: lighten($card-border-hover-color, 15%); + +.card { + margin-bottom: govuk-spacing(7); + background: $govuk-body-background-colour; + border: $card-border-width solid $card-border-color; + position: relative; + width: 100%; + padding: govuk-spacing(5); + + &__heading { + margin-top: 0; + margin-bottom: govuk-spacing(3); + } + + &__description { + margin-bottom: 0; + } + + /* Clickable card + ========================================================================== */ + &--clickable { + border-bottom-width: $card-border-bottom-width; + + &:hover, + &:active { + cursor: pointer; + + .card__heading a, + .card__link { + color: $govuk-link-hover-colour; + text-decoration: none; + + &:focus { + @include govuk-focused-text; + } + } + } + + &:hover { + border-color: $card-border-hover-color; + } + + &:active { + border-color: $card-border-hover-color; + bottom: -$card-border-width; + } + } +} + +/* Card group +========================================================================== */ + +/** + * A card group allows you to have a row of cards. + * Flexbox is used to make each card in a row the same height. + */ + +.card-group { + display: flex; + flex-wrap: wrap; + margin-bottom: govuk-spacing(3); + padding: 0; + + @include govuk-media-query($until: desktop) { + margin-bottom: govuk-spacing(6); + } + + &__item { + display: flex; + list-style-type: none; + margin-bottom: 0; + + @include govuk-media-query($until: desktop) { + flex: 0 0 100%; + } + .card { + margin-bottom: govuk-spacing(5); + } + + @include govuk-media-query($until: desktop) { + .card { + margin-bottom: govuk-spacing(3); + } + + &:last-child .card { + margin-bottom: 0; + } + } + } +} \ No newline at end of file diff --git a/assets/scss/local.scss b/assets/scss/local.scss index 35cba2b..76a3a05 100644 --- a/assets/scss/local.scss +++ b/assets/scss/local.scss @@ -1,3 +1,22 @@ .govuk-main-wrapper { min-height: 600px; } + +.homepage-content-container { + width: 300%; + margin-left: -100%; + padding: 30px 0; + background: #f0f4f5; + height: auto; + display: flex; + justify-content: center; +} + +.homepage-content { + max-width: 100vw; +} + +.dropshadow { + border: 1px solid govuk-colour('dark-grey'); + box-shadow: 0 5px govuk-colour('mid-grey'); +} diff --git a/cypress.config.ts b/cypress.config.ts index 43f97b6..aa2a0dc 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -28,8 +28,12 @@ export default defineConfig({ }) }, baseUrl: 'http://localhost:3007', - excludeSpecPattern: '**/!(*.cy).ts', - specPattern: 'integration_tests/e2e/**/*.cy.{js,jsx,ts,tsx}', + excludeSpecPattern: ['dist', '**/!(*.cy).ts'], + specPattern: '**/*.cy.{js,jsx,ts,tsx}', supportFile: 'integration_tests/support/index.ts', + experimentalRunAllSpecs: true, + retries: { + runMode: 2, + }, }, }) diff --git a/esbuild/esbuild.config.js b/esbuild/esbuild.config.js index cbcabaf..4ac56d1 100644 --- a/esbuild/esbuild.config.js +++ b/esbuild/esbuild.config.js @@ -25,6 +25,14 @@ const buildConfig = { from: path.join(cwd, 'server/views/**/*'), to: path.join(cwd, 'dist/server/views'), }, + { + from: path.join(cwd, 'server/routes/journeys/**/*'), + to: path.join(cwd, 'dist/server/routes/journeys'), + }, + { + from: path.join(cwd, 'server/routes/**/*'), + to: path.join(cwd, 'dist/server/routes'), + }, ], }, @@ -60,14 +68,14 @@ const main = () => { if (args.includes('--dev-server')) { let serverProcess = null - chokidar.watch(['dist']).on('all', () => { + chokidar.watch(['dist'], { ignored: ['**/*.cy.ts'] }).on('all', () => { if (serverProcess) serverProcess.kill() serverProcess = spawn('node', ['--env-file=.env', 'dist/server.js'], { stdio: 'inherit' }) }) } if (args.includes('--dev-test-server')) { let serverProcess = null - chokidar.watch(['dist']).on('all', () => { + chokidar.watch(['dist'], { ignored: ['**/*.cy.ts'] }).on('all', () => { if (serverProcess) serverProcess.kill() serverProcess = spawn('node', ['--env-file=feature.env', 'dist/server.js'], { stdio: 'inherit' }) }) @@ -82,7 +90,7 @@ const main = () => { // App chokidar - .watch(['server/**/*'], { ...chokidarOptions, ignored: ['**/*.test.ts'] }) + .watch(['server/**/*'], { ...chokidarOptions, ignored: ['**/*.test.ts', '**/*.cy.ts'] }) .on('all', () => buildApp(buildConfig).catch(e => process.stderr.write(`${e}\n`))) } } diff --git a/eslint.config.mjs b/eslint.config.mjs index c082f42..7b226c0 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,10 +1,13 @@ import hmppsConfig from '@ministryofjustice/eslint-config-hmpps' export default [ - ...hmppsConfig(), + ...hmppsConfig({ + extraIgnorePaths: ['assets'], + }), { rules: { 'dot-notation': 'off', + 'import/prefer-default-export': 0, }, }, ] diff --git a/feature.env b/feature.env index dfebdef..2258d9e 100644 --- a/feature.env +++ b/feature.env @@ -13,3 +13,4 @@ KEYWORKER_API_URL=http://localhost:9091/keyworker-api PRISON_API_URL=http://localhost:9091/prison-api COMPONENT_API_URL=http://localhost:9091/components COMMON_COMPONENTS_ENABLED=true +LEGACY_KEY_WORKERS_UI_URL=https://legacy.key-workers.url diff --git a/helm_deploy/values-dev.yaml b/helm_deploy/values-dev.yaml index 2ee37f4..83b9caf 100644 --- a/helm_deploy/values-dev.yaml +++ b/helm_deploy/values-dev.yaml @@ -14,6 +14,7 @@ generic-service: PRISONER_PROFILE_URL: "https://prisoner-dev.digital.prison.service.justice.gov.uk" PRISON_API_URL: "https://prison-api-dev.prison.service.justice.gov.uk" KEYWORKER_API_URL: "https://keyworker-api-dev.prison.service.justice.gov.uk" + LEGACY_KEY_WORKERS_UI_URL: "https://dev.manage-key-workers.service.justice.gov.uk" ENVIRONMENT_NAME: DEV AUDIT_ENABLED: "false" SENTRY_ENVIRONMENT: DEV diff --git a/helm_deploy/values-preprod.yaml b/helm_deploy/values-preprod.yaml index b26f29f..350f94d 100644 --- a/helm_deploy/values-preprod.yaml +++ b/helm_deploy/values-preprod.yaml @@ -14,6 +14,7 @@ generic-service: PRISONER_PROFILE_URL: "https://prisoner-preprod.digital.prison.service.justice.gov.uk" PRISON_API_URL: "https://prison-api-preprod.prison.service.justice.gov.uk" KEYWORKER_API_URL: "https://keyworker-api-preprod.prison.service.justice.gov.uk" + LEGACY_KEY_WORKERS_UI_URL: "https://preprod.manage-key-workers.service.justice.gov.uk" ENVIRONMENT_NAME: PRE-PRODUCTION AUDIT_ENABLED: "false" SENTRY_ENVIRONMENT: PRE-PRODUCTION diff --git a/helm_deploy/values-prod.yaml b/helm_deploy/values-prod.yaml index dda3153..fd7061c 100644 --- a/helm_deploy/values-prod.yaml +++ b/helm_deploy/values-prod.yaml @@ -14,6 +14,7 @@ generic-service: PRISONER_PROFILE_URL: "https://prisoner.digital.prison.service.justice.gov.uk" KEYWORKER_API_URL: "https://keyworker-api.prison.service.justice.gov.uk" PRISON_API_URL: "https://prison-api.prison.service.justice.gov.uk" + LEGACY_KEY_WORKERS_UI_URL: "https://manage-key-workers.service.justice.gov.uk" AUDIT_ENABLED: "false" SENTRY_ENVIRONMENT: PRODUCTION diff --git a/server/errorHandler.cy.ts b/integration_tests/e2e/errorHandler.cy.ts similarity index 53% rename from server/errorHandler.cy.ts rename to integration_tests/e2e/errorHandler.cy.ts index 9b58a28..8b1e6c8 100644 --- a/server/errorHandler.cy.ts +++ b/integration_tests/e2e/errorHandler.cy.ts @@ -1,25 +1,21 @@ -import { v4 as uuidV4 } from 'uuid' - context('test errorHandler', () => { - const uuid = uuidV4() - beforeEach(() => { cy.task('reset') cy.task('stubSignIn') cy.task('stubComponents') }) - it('should go to the custom error page when an API 500s', () => { - cy.task('stubGetPrisoner500') - cy.task('stubGetPrisonerImage') + // TODO: add test for API error handling + it.skip('should go to the custom error page when an API 500s', () => { + cy.task('stubAPI500Error') cy.signIn() - cy.visit(`${uuid}/prisoners/A1111AA/referral/start`, { failOnStatusCode: false }) + cy.visit(`/path-that-requires-API-call`, { failOnStatusCode: false }) cy.findByText(/sorry, there is a problem with the service/i).should('be.visible') }) it('should say page not found when 404', () => { cy.signIn() - cy.visit(`${uuid}/foobar`, { failOnStatusCode: false }) + cy.visit(`/foobar`, { failOnStatusCode: false }) cy.findByRole('heading', { name: /Page not found/i }).should('be.visible') }) }) diff --git a/integration_tests/pages/index.ts b/integration_tests/pages/index.ts index 4de3d1c..5b7bea6 100644 --- a/integration_tests/pages/index.ts +++ b/integration_tests/pages/index.ts @@ -2,10 +2,8 @@ import Page, { PageElement } from './page' export default class IndexPage extends Page { constructor() { - super('This site is under construction...') + super('Key workers') } headerUserName = (): PageElement => cy.get('[data-qa=header-user-name]') - - headerPhaseBanner = (): PageElement => cy.get('[data-qa=header-phase-banner]') } diff --git a/integration_tests/support/index.ts b/integration_tests/support/index.ts index 43c03b7..82ca3f9 100644 --- a/integration_tests/support/index.ts +++ b/integration_tests/support/index.ts @@ -1 +1,2 @@ import './commands' +import '@testing-library/cypress/add-commands' diff --git a/integration_tests/tsconfig.json b/integration_tests/tsconfig.json new file mode 100644 index 0000000..1c02618 --- /dev/null +++ b/integration_tests/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es5", + "noEmit": true, + "lib": ["es5", "dom", "es2015.promise"], + "types": ["cypress", "express", "express-session", "@testing-library/cypress"], + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["**/*.ts"] +} diff --git a/package.json b/package.json index 444aaeb..594f345 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "start-feature:dev": "concurrently -k -p \"[{name}]\" -n \"ESBuild,Node\" -c \"yellow.bold,cyan.bold\" \"node esbuild/esbuild.config.js --build --watch \" \"node esbuild/esbuild.config.js --dev-test-server\"", "lint": "eslint . --cache --max-warnings 0", "lint-fix": "eslint . --cache --max-warnings 0 --fix", - "typecheck": "tsc", + "typecheck": "tsc && tsc -p integration_tests && tsc -p server/routes", "test": "jest", "test:ci": "jest --runInBand", "security_audit": "npx audit-ci --config audit-ci.json", diff --git a/server/@types/express/index.d.ts b/server/@types/express/index.d.ts index 6797ad4..354479d 100644 --- a/server/@types/express/index.d.ts +++ b/server/@types/express/index.d.ts @@ -35,6 +35,7 @@ export declare global { csrfToken: ReturnType user: HmppsUser digitalPrisonServicesUrl: string + legacyKeyWorkersUiUrl: string breadcrumbs: Breadcrumbs prisoner?: PrisonerSummary buildNumber?: string diff --git a/server/app.ts b/server/app.ts index f47248c..849a46b 100755 --- a/server/app.ts +++ b/server/app.ts @@ -25,6 +25,7 @@ import routes from './routes' import type { Services } from './services' import checkPopulateUserCaseloads from './middleware/checkPopulateUserCaseloads' import populateClientToken from './middleware/populateSystemClientToken' +import breadcrumbs from './middleware/breadcrumbs' export default function createApp(services: Services): express.Application { const app = express() @@ -62,7 +63,7 @@ export default function createApp(services: Services): express.Application { res.notFound = () => res.status(404).render('pages/not-found') next() }) - + app.use(breadcrumbs()) app.use(checkPopulateUserCaseloads(services.prisonApiService)) app.use(routes(services)) diff --git a/server/config.ts b/server/config.ts index fd8b91b..db51a34 100755 --- a/server/config.ts +++ b/server/config.ts @@ -120,6 +120,7 @@ export default { serviceUrls: { digitalPrison: get('DPS_HOME_PAGE_URL', 'http://localhost:3001', requiredInProduction), prisonerProfile: get('PRISONER_PROFILE_URL', 'http://localhost:3001', requiredInProduction), + legacyKeyWorkersUI: get('LEGACY_KEY_WORKERS_UI_URL', 'http://localhost:3001', requiredInProduction), }, sqs: { audit: auditConfig(), diff --git a/server/jest.config.js b/server/jest.config.js index 16e9115..fcd6676 100644 --- a/server/jest.config.js +++ b/server/jest.config.js @@ -17,7 +17,7 @@ module.exports = { '.*.cy.ts', ], testMatch: ['/(server|job)/**/?(*.)(test).{ts,js,jsx,mjs}'], - testPathIgnorePatterns: ['/server/routes/journeys', '/server/routes/csip-records', 'node_modules'], + testPathIgnorePatterns: ['/server/routes/journeys', 'node_modules'], testEnvironment: 'node', rootDir: '../', moduleFileExtensions: ['web.js', 'js', 'json', 'node', 'ts'], diff --git a/server/middleware/breadcrumbs.ts b/server/middleware/breadcrumbs.ts new file mode 100644 index 0000000..119c66d --- /dev/null +++ b/server/middleware/breadcrumbs.ts @@ -0,0 +1,39 @@ +import type { Request, Response, NextFunction, RequestHandler } from 'express' + +export type Breadcrumb = { href: string } & ({ text: string } | { html: string }) + +export class Breadcrumbs { + breadcrumbs: Breadcrumb[] + + constructor(res: Response) { + this.breadcrumbs = [ + { + text: 'Digital Prison Services', + href: res.locals.digitalPrisonServicesUrl, + }, + { + text: 'Key workers', + href: '/', + }, + ] + } + + popLastItem(): Breadcrumb | undefined { + return this.breadcrumbs.pop() + } + + addItems(...items: Breadcrumb[]): void { + this.breadcrumbs.push(...items) + } + + get items(): readonly Breadcrumb[] { + return [...this.breadcrumbs] + } +} + +export default function breadcrumbs(): RequestHandler { + return (_req: Request, res: Response, next: NextFunction): void => { + res.locals.breadcrumbs = new Breadcrumbs(res) + next() + } +} diff --git a/server/routes/controller.ts b/server/routes/controller.ts new file mode 100644 index 0000000..bc99c2b --- /dev/null +++ b/server/routes/controller.ts @@ -0,0 +1,11 @@ +import { Request, Response } from 'express' + +export class HomePageController { + GET = async (_req: Request, res: Response) => { + res.locals.breadcrumbs.popLastItem() + + return res.render('view', { + showBreadcrumbs: true, + }) + } +} diff --git a/server/routes/index.ts b/server/routes/index.ts index d5e2718..1372218 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -2,17 +2,14 @@ import { type RequestHandler, Router } from 'express' import asyncMiddleware from '../middleware/asyncMiddleware' import type { Services } from '../services' -import { Page } from '../services/auditService' +import { HomePageController } from './controller' -export default function routes({ auditService }: Services): Router { +export default function routes(_services: Services): Router { const router = Router() + const controller = new HomePageController() const get = (path: string | string[], handler: RequestHandler) => router.get(path, asyncMiddleware(handler)) - get('/', async (req, res) => { - await auditService.logPageView(Page.EXAMPLE_PAGE, { who: res.locals.user.username, correlationId: req.id }) - - res.render('pages/index') - }) + get('/', controller.GET) return router } diff --git a/server/routes/test.cy.ts b/server/routes/test.cy.ts new file mode 100644 index 0000000..14ab8ee --- /dev/null +++ b/server/routes/test.cy.ts @@ -0,0 +1,58 @@ +context('test / homepage', () => { + beforeEach(() => { + cy.task('reset') + }) + + it('shows all tiles when user has all required roles', () => { + cy.task('stubSignIn', { + roles: [], + }) + navigateToTestPage() + + cy.findByRole('heading', { name: /^Key workers$/i }).should('be.visible') + + cy.findByRole('link', { name: /View all without a key worker$/i }) + .should('be.visible') + .and('have.attr', 'href') + .and('to.equal', 'https://legacy.key-workers.url/manage-key-workers/allocate-key-worker') + + cy.findByRole('link', { name: /View by residential location$/i }) + .should('be.visible') + .and('have.attr', 'href') + .and('to.equal', 'https://legacy.key-workers.url/manage-key-workers/view-residential-location') + + cy.findByRole('link', { name: /Search for a prisoner$/i }) + .should('be.visible') + .and('have.attr', 'href') + .and('to.equal', 'https://legacy.key-workers.url/manage-key-workers/search-for-prisoner') + + cy.findByRole('link', { name: /View key workers in your establishment$/i }) + .should('be.visible') + .and('have.attr', 'href') + .and('to.equal', 'https://legacy.key-workers.url/key-worker-search') + + cy.findByRole('link', { name: /Key worker statistics$/i }) + .should('be.visible') + .and('have.attr', 'href') + .and('to.equal', 'https://legacy.key-workers.url/key-worker-statistics') + + cy.findByRole('link', { name: /Manage your establishment’s key worker settings$/i }) + .should('be.visible') + .and('have.attr', 'href') + .and('to.equal', 'https://legacy.key-workers.url/manage-key-worker-settings') + }) + + it.skip('shows unauthorised message if user does not have any of the required roles', () => { + cy.task('stubSignIn', { + roles: [], + }) + navigateToTestPage() + cy.findByText('You are not authorised to use this application.').should('be.visible') + cy.findByRole('heading', { name: /^Key workers$/i }).should('not.exist') + }) + + const navigateToTestPage = () => { + cy.signIn({ failOnStatusCode: false }) + cy.visit('/', { failOnStatusCode: false }) + } +}) diff --git a/server/routes/tsconfig.json b/server/routes/tsconfig.json new file mode 100644 index 0000000..6b64da4 --- /dev/null +++ b/server/routes/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.json", + "include": ["**/*.cy.ts"], + "exclude": [], + "compilerOptions": { + "types": [ + "cypress", + "express", + "express-session", + "@testing-library/cypress", + "../../integration_tests/index.d.ts", + "../@types/environment.d.ts" + ] + } +} diff --git a/server/routes/view.njk b/server/routes/view.njk new file mode 100644 index 0000000..ed4d36c --- /dev/null +++ b/server/routes/view.njk @@ -0,0 +1,76 @@ +{% extends "partials/layout.njk" %} + +{% set pageTitle = "Key workers" %} +{% set mainClasses = "app-container govuk-body" %} + +{% block content %} +

Key workers

+ +
+
+
+
+
+
+ +
+
+

+ View all without a key worker +

+

You can allocate key workers to these prisoners. You can also automatically allocate a key worker to these prisoners if your establishment allows it.

+
+
+ +
+
+

+ View by residential location +

+

View all prisoners in a residential location and allocate or change key workers. You can also see high complexity prisoners

+
+
+ +
+
+

+ Search for a prisoner +

+

You can allocate or change a key worker after searching for a prisoner. You will need the prisoner’s name or prison number.

+
+
+ +
+
+

+ View key workers in your establishment +

+

You can manage a key worker’s availability, reassign their prisoners and check their individual statistics.

+
+
+ +
+
+

+ Key worker statistics +

+

View the statistics for your establishment’s key workers.

+
+
+ +
+
+

+ Manage your establishment’s key worker settings +

+

Allow auto-allocation, edit key worker capacity and session frequency.

+
+
+ +
+
+
+
+
+
+{% endblock %} diff --git a/server/utils/nunjucksSetup.ts b/server/utils/nunjucksSetup.ts index 2568756..38d9098 100644 --- a/server/utils/nunjucksSetup.ts +++ b/server/utils/nunjucksSetup.ts @@ -25,9 +25,18 @@ export default function nunjucksSetup(app: express.Express): void { } } + app.use((_req, res, next) => { + res.locals.digitalPrisonServicesUrl = config.serviceUrls.digitalPrison + res.locals.legacyKeyWorkersUiUrl = config.serviceUrls.legacyKeyWorkersUI + res.locals['prisonerProfileUrl'] = config.serviceUrls.prisonerProfile + return next() + }) + const njkEnv = nunjucks.configure( [ path.join(__dirname, '../../server/views'), + path.join(__dirname, '../../server/routes/journeys'), + path.join(__dirname, '../../server/routes'), 'node_modules/govuk-frontend/dist/', 'node_modules/@ministryofjustice/frontend/', 'node_modules/@ministryofjustice/hmpps-connect-dps-components/dist/assets/', diff --git a/server/views/partials/layout.njk b/server/views/partials/layout.njk index 37abef3..4cec884 100644 --- a/server/views/partials/layout.njk +++ b/server/views/partials/layout.njk @@ -1,4 +1,6 @@ {% extends "govuk/template.njk" %} +{% from "govuk/components/breadcrumbs/macro.njk" import govukBreadcrumbs %} +{% from "govuk/components/back-link/macro.njk" import govukBackLink %} {% block head %} @@ -19,6 +21,32 @@ {% endblock %} {% block beforeContent %} + {% if backUrl %} + {{ govukBackLink({ + text: "Back", + href: backUrl + }) }} + {% endif %} + + {% if showBreadcrumbs and breadcrumbs and (not backUrl) %} +
+
+ {{ govukBreadcrumbs({ + items: breadcrumbs.items + }) }} +
+
+ {% endif %} + + {% block pageHeader %}{% endblock %} +{% endblock %} + +{% block content %} +
+
+ {% block innerContent %}{% endblock %} +
+
{% endblock %} {% block bodyStart %} diff --git a/tsconfig.json b/tsconfig.json index 91af6fb..0ad30ac 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,8 +33,10 @@ "coverage", "codecov_reports", "esbuild", - "server/@types/csip/index.d.ts", - "test_results" + "server/@types/keyWorker/index.d.ts", + "test_results", + "integration_tests", + "**/*.cy.ts" ], "include": ["**/*.js", "**/*.ts"] }