From 781873cb7d62d86c4235d1c644da560943873dac Mon Sep 17 00:00:00 2001 From: Kris Charbonneau Date: Fri, 5 Jul 2024 10:48:05 -0400 Subject: [PATCH 1/4] Revert 'Revert 'Merge branch 'staging' into main. This reverts commit 73b1aa4619e48fe80345e72c851271590c8e65ca.' --- .env.example | 1 + .github/workflows/default-tests.yml | 3 +- .gitignore | 3 + .vscode/launch.json | 7 + Dockerfile | 16 +- __tests__/components/Breadcrumb.test.js | 4 + __tests__/components/ContextualAlert.test.js | 12 +- __tests__/components/ExitBetaModal.test.js | 54 - __tests__/components/Header.test.js | 1 + __tests__/components/IdleTimeout.test.js | 27 + __tests__/components/InfoMessage.test.js | 40 + __tests__/components/PhaseBanner.test.js | 93 -- .../sessionModals/CountDown.test.js | 41 - __tests__/pages/404.test.js | 19 - __tests__/pages/500.test.js | 19 - __tests__/pages/my-dashboard.test.js | 44 +- components/BenefitTasks.tsx | 26 +- components/Breadcrumb.tsx | 6 +- components/Card.tsx | 96 +- components/Collapse.tsx | 4 +- components/ContextualAlert.tsx | 71 +- components/ErrorPage.tsx | 7 +- components/ExitBeta.tsx | 82 -- components/Header.tsx | 10 +- components/Heading.tsx | 3 +- components/IdleTimeout.tsx | 141 ++ components/InfoMessage.tsx | 75 ++ components/Layout.js | 183 +-- components/Menu.tsx | 12 +- components/MostReqTasks.tsx | 156 ++- components/MultiModal.js | 137 -- components/PhaseBanner.tsx | 120 -- components/ViewMoreLessButton.tsx | 4 +- components/contact/ContactProvinceRow.tsx | 7 +- components/contact/ContactSection.tsx | 5 +- components/contact/ContactSectionRow.tsx | 15 +- components/sessionModals/CountDown.js | 69 - cypress/e2e/Login.cy.js | 27 +- cypress/e2e/Logout.cy.js | 2 +- cypress/e2e/dashboard.cy.js | 293 +++-- graphql/mappers/beta-banner-opt-out.ts | 157 --- graphql/mappers/beta-popup-exit.ts | 142 -- .../mappers/beta-popup-page-not-available.ts | 140 -- graphql/mappers/my-dashboard.ts | 81 +- .../privacy-notice-terms-conditions.ts | 4 +- .../templates/deployment.yaml | 13 +- helm/charts/secure-client-hub/values.yaml | 6 + .../dev/secure-client-hub.yaml.gotmpl | 36 +- .../prod/secure-client-hub.yaml.gotmpl | 30 +- lib/auth.js | 11 + locales/en.js | 22 +- locales/fr.js | 22 + middleware.ts | 11 +- package-lock.json | 1162 +++++++++-------- package.json | 30 +- pages/404.tsx | 14 - pages/500.tsx | 14 - pages/_app.js | 29 +- pages/api/auth/[...nextauth].ts | 105 +- pages/api/refresh-msca.js | 14 +- pages/auth/login.js | 29 +- pages/contact-us/[id].tsx | 28 +- pages/contact-us/index.tsx | 64 +- pages/decision-reviews.js | 56 +- pages/my-dashboard.js | 82 +- pages/privacy-notice-terms-conditions.js | 84 +- pages/profile.js | 55 +- pages/security-settings.js | 60 +- public/robots.txt | 31 + 69 files changed, 2077 insertions(+), 2390 deletions(-) delete mode 100644 __tests__/components/ExitBetaModal.test.js create mode 100644 __tests__/components/IdleTimeout.test.js create mode 100644 __tests__/components/InfoMessage.test.js delete mode 100644 __tests__/components/PhaseBanner.test.js delete mode 100644 __tests__/components/sessionModals/CountDown.test.js delete mode 100644 components/ExitBeta.tsx create mode 100644 components/IdleTimeout.tsx create mode 100644 components/InfoMessage.tsx delete mode 100644 components/MultiModal.js delete mode 100644 components/PhaseBanner.tsx delete mode 100644 components/sessionModals/CountDown.js delete mode 100644 graphql/mappers/beta-banner-opt-out.ts delete mode 100644 graphql/mappers/beta-popup-exit.ts delete mode 100644 graphql/mappers/beta-popup-page-not-available.ts create mode 100644 public/robots.txt diff --git a/.env.example b/.env.example index e01951849..66709accc 100644 --- a/.env.example +++ b/.env.example @@ -14,6 +14,7 @@ AUTH_ECAS_USERINFO="ECAS userinfo endpoint" AUTH_PRIVATE={ "kty": "RSA", "n": "example private key" } AUTH_DISABLED="set to 'true' to disable authentiation" SWAP_USER_TESTING_LINKS = "Set true to use UT links" +MSCA_NG_CERT_LOCATION="./env.crt" # OpenTelemetry/Dynatrace settings # Note: to disable metrics and/or tracing exporting, leave the respective endpoint undefined diff --git a/.github/workflows/default-tests.yml b/.github/workflows/default-tests.yml index 470c58ebd..0bb894247 100644 --- a/.github/workflows/default-tests.yml +++ b/.github/workflows/default-tests.yml @@ -48,7 +48,8 @@ jobs: run: npm run test:coverage -- -u env: CI: true - + AUTH_DISABLED: true + - name: Store Results uses: actions/upload-artifact@v3 with: diff --git a/.gitignore b/.gitignore index b1bd3a98e..8c9c5bf5f 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,9 @@ yarn-error.log* .env .env.local +#cert +env.crt + # typescript *.tsbuildinfo next-env.d.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 1689524c2..1c9edadae 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,13 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "Debug MSCA-D frontend", + "command": "npm run dev", + "request": "launch", + "type": "node-terminal", + "cwd": "${workspaceFolder}" + }, { "type": "pwa-chrome", "request": "launch", diff --git a/Dockerfile b/Dockerfile index 808be4d3c..445366c4f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20-alpine3.18 AS base +FROM node:20-alpine3.20 AS base WORKDIR /base COPY package*.json ./ RUN npm ci && npm cache clean --force @@ -7,6 +7,8 @@ COPY . . FROM base AS build # Build envs +ARG HOSTALIAS_CERT +ENV HOSTALIAS_CERT=$HOSTALIAS_CERT ARG LOGGING_LEVEL=info ENV LOGGING_LEVEL=$LOGGING_LEVEL ARG AEM_GRAPHQL_ENDPOINT=https://www.canada.ca/graphql/execute.json/decd-endc/ @@ -23,15 +25,19 @@ ENV MSCA_ECAS_RASC_BASE_URL=$MSCA_ECAS_RASC_BASE_URL ENV NODE_ENV=production WORKDIR /build COPY --from=base /base ./ -RUN npm run build -FROM node:20-alpine3.18 AS production +RUN mkdir -p /usr/local/share/ca-certificates/ && echo ${HOSTALIAS_CERT} | sed 's/\\n/\n/g' | xargs > /usr/local/share/ca-certificates/env.crt && chmod 644 /usr/local/share/ca-certificates/env.crt && npm run build + +FROM node:20-alpine3.20 AS production ENV NODE_ENV=production ARG user=nodeuser ARG group=nodegroup ARG home=/srv/app +ARG MSCA_NG_CERT_LOCATION=/usr/local/share/ca-certificates/env.crt +ENV MSCA_NG_CERT_LOCATION=$MSCA_NG_CERT_LOCATION + RUN addgroup \ -S ${group} \ --gid 1001 && \ @@ -45,6 +51,10 @@ RUN addgroup \ WORKDIR ${home} +COPY --from=build --chown=${user}:${group} /usr/local/share/ca-certificates/env.crt ${MSCA_NG_CERT_LOCATION} + +RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* && update-ca-certificates + USER ${user} COPY --from=build --chown=${user}:${group} /build/next.config.js ./ diff --git a/__tests__/components/Breadcrumb.test.js b/__tests__/components/Breadcrumb.test.js index 9a84959c0..b3e53b322 100644 --- a/__tests__/components/Breadcrumb.test.js +++ b/__tests__/components/Breadcrumb.test.js @@ -21,6 +21,7 @@ describe('BreadCrumb', () => { , ) expect(primary).toBeTruthy() @@ -36,6 +37,7 @@ describe('BreadCrumb', () => { { text: 'Link2', link: '/' }, { text: 'Link3', link: '/' }, ]} + refPageAA="dashboard" />, ) expect(withItems).toBeTruthy() @@ -52,6 +54,7 @@ describe('BreadCrumb', () => { { text: 'Max length of breadcrumb 28', link: '/' }, { text: 'Link3', link: '/' }, ]} + refPageAA="dashboard" />, ) expect(withItemsWithLongText).toBeTruthy() @@ -62,6 +65,7 @@ describe('BreadCrumb', () => { , ) const results = await axe(container) diff --git a/__tests__/components/ContextualAlert.test.js b/__tests__/components/ContextualAlert.test.js index 1b1e590e4..b7a0c06f3 100644 --- a/__tests__/components/ContextualAlert.test.js +++ b/__tests__/components/ContextualAlert.test.js @@ -13,9 +13,9 @@ describe('ContextualAlert', () => { id="alert_icon_id" alert_icon_id="icon-id" alert_icon_alt_text="alt" - type="info" - message_heading="Information" - message_body="You may wish to print this page..." + type="information" + alertHeading="Information" + alertBody="You may wish to print this page..." />, ) it('renders this Contectual Alert component', () => { @@ -36,9 +36,9 @@ describe('ContextualAlert', () => { id="alert_icon_id" alert_icon_id="icon-id" alert_icon_alt_text="alt" - type="info" - message_heading="Information" - message_body="You may wish to print this page..." + type="information" + alertHeading="Information" + alertBody="You may wish to print this page..." />, ) const results = await axe(container) diff --git a/__tests__/components/ExitBetaModal.test.js b/__tests__/components/ExitBetaModal.test.js deleted file mode 100644 index baa2861ee..000000000 --- a/__tests__/components/ExitBetaModal.test.js +++ /dev/null @@ -1,54 +0,0 @@ -import { render, screen } from '@testing-library/react' -import '@testing-library/jest-dom' -import { axe, toHaveNoViolations } from 'jest-axe' -import ExitBeta from '../../components/ExitBeta' - -expect.extend(toHaveNoViolations) - -describe('Exit Beta Modal', () => { - it('renders Exit Beta Modal', () => { - render( - {}} - closeModalAria={'close'} - continueLink="/" - popupId={'Test Id'} - popupTitle={'Test Title'} - popupDescription={'Test Description'} - popupPrimaryBtn={{ id: 'Test Primary Id', text: 'Test Primary Text' }} - popupSecondaryBtn={{ - id: 'Test Secondary Id', - text: 'Test Secondary Text', - }} - />, - ) - const title = screen.getByText('Test Title') - const description = screen.getByText('Test Description') - const primaryBtnText = screen.getByText('Test Primary Text') - const secondaryBtnText = screen.getByText('Test Secondary Text') - expect(title).toBeInTheDocument() - expect(description).toBeInTheDocument() - expect(primaryBtnText).toBeInTheDocument() - expect(secondaryBtnText).toBeInTheDocument() - }) - it('has no a11y viollations', async () => { - const { container } = render( - {}} - closeModalAria={'close'} - continueLink="/" - popupId={'Test Id'} - popupTitle={'Test Title'} - popupDescription={'Test Description'} - popupPrimaryBtn={{ id: 'Test Primary Id', text: 'Test Primary Text' }} - popupSecondaryBtn={{ - id: 'Test Secondary Id', - text: 'Test Secondary Text', - }} - />, - ) - const results = await axe(container) - expect(results).toHaveNoViolations() - }) - it('placeholder', () => {}) -}) diff --git a/__tests__/components/Header.test.js b/__tests__/components/Header.test.js index d372709b9..aad12e543 100644 --- a/__tests__/components/Header.test.js +++ b/__tests__/components/Header.test.js @@ -62,6 +62,7 @@ describe('Header', () => { { text: 'Max length of breadcrumb 28', link: '/' }, { text: 'Link3', link: '/' }, ], + refPageAA: 'dashboard', } test('renders Header component with default props', () => { render(
) diff --git a/__tests__/components/IdleTimeout.test.js b/__tests__/components/IdleTimeout.test.js new file mode 100644 index 000000000..9c02b24e2 --- /dev/null +++ b/__tests__/components/IdleTimeout.test.js @@ -0,0 +1,27 @@ +import '@testing-library/jest-dom' +import { render, screen, waitFor } from '@testing-library/react' +import IdleTimeout from '../../components/IdleTimeout' + +jest.mock('next/router', () => ({ + useRouter: () => ({}), +})) + +describe('IdleTimeout', () => { + it('renders with modal opened after 1 second timeout', async () => { + render( + , + ) + await waitFor( + () => { + const modal = screen.getByTestId('modal') + expect(modal).toBeInTheDocument() + }, + { timeout: 3000 }, + ) + }) +}) diff --git a/__tests__/components/InfoMessage.test.js b/__tests__/components/InfoMessage.test.js new file mode 100644 index 000000000..06a11cc7b --- /dev/null +++ b/__tests__/components/InfoMessage.test.js @@ -0,0 +1,40 @@ +import { render, screen } from '@testing-library/react' +import '@testing-library/jest-dom' +import { axe, toHaveNoViolations } from 'jest-axe' +import InfoMessage from '../../components/InfoMessage' + +expect.extend(toHaveNoViolations) + +describe('InfoMessage', () => { + it('renders InfoMessage', () => { + render( + , + ) + const label = screen.getAllByTestId('label') + const messageText = screen.getByText('messageText') + const messageLinkText = screen.getByText('messageLinkText') + + expect.arrayContaining(label) + expect(messageText).toBeInTheDocument() + expect(messageLinkText).toBeInTheDocument() + }) + + it('has no a11y viollations', async () => { + const { container } = render( + , + ) + const results = await axe(container) + expect(results).toHaveNoViolations() + }) +}) diff --git a/__tests__/components/PhaseBanner.test.js b/__tests__/components/PhaseBanner.test.js deleted file mode 100644 index e2db33a24..000000000 --- a/__tests__/components/PhaseBanner.test.js +++ /dev/null @@ -1,93 +0,0 @@ -import { render, screen } from '@testing-library/react' -import '@testing-library/jest-dom' -import { axe, toHaveNoViolations } from 'jest-axe' -import PhaseBanner from '../../components/PhaseBanner' - -expect.extend(toHaveNoViolations) - -describe('PhaseBanner', () => { - const popupContent = { - scId: 'beta-popup-exit', - scHeadingEn: 'Exiting beta version', - scHeadingFr: 'Vous quittez la version bêta', - scContentEn: - 'Thank you for trying the beta version. You are now returning to My Service Canada Account home page.', - scContentFr: - "Merci d'avoir essayé la version bêta. Nous vous redirigeons vers la page d’accueil de Mon dossier Service Canada.", - scFragments: [ - { - scId: 'stay-on-beta-version', - // scLinkTextAssistiveEn: "Stay on beta version", - // scLinkTextAssistiveFr: "Rester sur la version bêta", - scLinkTextEn: 'Stay on beta version', - scLinkTextFr: 'Rester sur la version bêta', - }, - { - scId: 'exit-beta-version', - // scLinkTextAssistiveEn: 'Continue to page', - // scLinkTextAssistiveFr: 'Continuer vers la page', - scLinkTextEn: 'Exit Beta version', - scLinkTextFr: 'Quitter la version beta', - }, - ], - } - it('renders PhaseBanner', () => { - render( - , - ) - const bannerBoldText = screen.getByText('bannerBoldText') - const bannerSummaryTitle = screen.getByText('bannerSummaryTitle') - const bannerText = screen.getByText('bannerText') - const bannerLink = screen.getByText('bannerLink') - const bannerSummaryContent = screen.getByText('bannerSummaryContent') - const bannerButtonText = screen.getByText('bannerButtonText') - expect(bannerBoldText).toBeInTheDocument() - expect(bannerSummaryTitle).toBeInTheDocument() - expect(bannerText).toBeInTheDocument() - expect(bannerLink).toBeInTheDocument() - expect(bannerSummaryContent).toBeInTheDocument() - expect(bannerButtonText).toBeInTheDocument() - }) - - it('has no a11y viollations', async () => { - const { container } = render( - , - ) - const results = await axe(container) - expect(results).toHaveNoViolations() - }) -}) diff --git a/__tests__/components/sessionModals/CountDown.test.js b/__tests__/components/sessionModals/CountDown.test.js deleted file mode 100644 index 1c969c5f1..000000000 --- a/__tests__/components/sessionModals/CountDown.test.js +++ /dev/null @@ -1,41 +0,0 @@ -import { render } from '@testing-library/react' -import '@testing-library/jest-dom' -import { axe, toHaveNoViolations } from 'jest-axe' -import CountDown from '../../../components/sessionModals/CountDown' - -expect.extend(toHaveNoViolations) - -const fallbackContent = { - en: { - bannerHeading: '', - signOutLinkText: '', - staySignedInLinktext: '', - bannerContent: '', - bannerMinutesAnd: 'minutes and', - bannerSeconds: 'seconds', - }, - fr: { - bannerHeading: '', - signOutLinkText: '', - staySignedInLinktext: '', - bannerContent: '', - bannerMinutesAnd: 'minutes et', - bannerSeconds: 'secondes', - }, -} - -describe('CountDownModal', () => { - it('renders countDown', () => { - const primary = render( - console.log('Close Modal')} - onSignOut={() => console.log('Sign Out Clicked')} - onStay={() => console.log('Stay Signed In Clicked')} - id="CountDown" - deadline="January, 31, 2023" - {...fallbackContent.en} - />, - ) - expect(primary).toBeTruthy() - }) -}) diff --git a/__tests__/pages/404.test.js b/__tests__/pages/404.test.js index 69627fece..7c0f7aeba 100644 --- a/__tests__/pages/404.test.js +++ b/__tests__/pages/404.test.js @@ -5,25 +5,6 @@ import { render, screen } from '@testing-library/react' import '@testing-library/jest-dom' import Custom404 from '../../pages/404' -jest.mock('../../graphql/mappers/beta-banner-opt-out', () => ({ - getBetaBannerContent: () => { - return new Promise(function (resolve, reject) { - resolve({ - en: {}, - fr: {}, - }) - }) - }, -})) - -jest.mock('../../graphql/mappers/beta-popup-exit', () => ({ - getBetaPopupExitContent: () => { - return new Promise(function (resolve, reject) { - resolve({ en: {}, fr: {} }) - }) - }, -})) - describe('custom error', () => { it('renders custom statusCode 404 without crashing', () => { render( diff --git a/__tests__/pages/500.test.js b/__tests__/pages/500.test.js index 0cf1a270a..b37a1f6dc 100644 --- a/__tests__/pages/500.test.js +++ b/__tests__/pages/500.test.js @@ -5,25 +5,6 @@ import { render, screen } from '@testing-library/react' import '@testing-library/jest-dom' import Custom500 from '../../pages/500' -jest.mock('../../graphql/mappers/beta-banner-opt-out', () => ({ - getBetaBannerContent: () => { - return new Promise(function (resolve, reject) { - resolve({ - en: {}, - fr: {}, - }) - }) - }, -})) - -jest.mock('../../graphql/mappers/beta-popup-exit', () => ({ - getBetaPopupExitContent: () => { - return new Promise(function (resolve, reject) { - resolve({ en: {}, fr: {} }) - }) - }, -})) - describe('custom error', () => { it('renders custom statusCode 500 without crashing', () => { render( diff --git a/__tests__/pages/my-dashboard.test.js b/__tests__/pages/my-dashboard.test.js index abe17d131..7cc7cebe8 100644 --- a/__tests__/pages/my-dashboard.test.js +++ b/__tests__/pages/my-dashboard.test.js @@ -27,11 +27,37 @@ jest.mock('../../components/Card', () => { return MockCard }) +jest.mock('../../components/ContextualAlert', () => { + const MockAlert = () => + return MockAlert +}) describe('My Dashboard page', () => { const content = { heading: 'heading', paragraph: 'paragraph', - cards: [{ id: 'test', title: 'title', lists: [] }], + cards: [ + { + id: 'test', + title: 'title', + cardAlerts: [ + { + id: 'test', + type: 'information', + alertHeading: 'heading', + alertBody: 'body', + }, + ], + lists: [], + }, + ], + pageAlerts: [ + { + id: 'test', + type: 'information', + alertHeading: 'heading', + alertBody: 'body', + }, + ], } const popupContent = {} @@ -74,7 +100,19 @@ describe('My Dashboard page', () => { popupContentNA={popupContent} />, ) - const testCard = screen.getByTestId('mock-card') - expect(testCard).toBeInTheDocument() + const testCard = screen.getAllByTestId('mock-card') + expect(testCard[0]).toBeInTheDocument() + }) + + it('should contain an alert', () => { + render( + , + ) + const testAlert = screen.getByTestId('mock-alert') + expect(testAlert).toBeInTheDocument() }) }) diff --git a/components/BenefitTasks.tsx b/components/BenefitTasks.tsx index a88aebed5..40ff999c4 100644 --- a/components/BenefitTasks.tsx +++ b/components/BenefitTasks.tsx @@ -48,7 +48,7 @@ const BenefitTasks = ({ return (
-

+

{taskList.title}

    - + {task.title} {newTabTaskExceptions.includes(task.link) ? ( @@ -90,15 +95,6 @@ const BenefitTasks = ({ > ) : null} - - {newTabTaskExceptions.includes(task.link) ? ( - - {locale === 'fr' - ? "S'ouvre dans un nouvel onglet" - : 'Opens in a new tab'} - - ) : null} - diff --git a/components/Breadcrumb.tsx b/components/Breadcrumb.tsx index 3ef9d98c5..bf0cc9ed4 100644 --- a/components/Breadcrumb.tsx +++ b/components/Breadcrumb.tsx @@ -10,11 +10,12 @@ export interface BreadcrumbItem { export interface BreadcrumbProps { id?: string items?: BreadcrumbItem[] + refPageAA: string } -const Breadcrumb = ({ id, items }: BreadcrumbProps) => { +const Breadcrumb = ({ id, items, refPageAA }: BreadcrumbProps) => { return ( -
diff --git a/components/Heading.tsx b/components/Heading.tsx index fc702021d..fc03e9b74 100644 --- a/components/Heading.tsx +++ b/components/Heading.tsx @@ -18,12 +18,13 @@ const Heading = ({ return ( <>

{title}

+
{fromLink && fromText && (

From: diff --git a/components/IdleTimeout.tsx b/components/IdleTimeout.tsx new file mode 100644 index 000000000..200808dcb --- /dev/null +++ b/components/IdleTimeout.tsx @@ -0,0 +1,141 @@ +import { useCallback, useEffect, useId, useState } from 'react' +import { useRouter } from 'next/router' +import { IIdleTimerProps, useIdleTimer } from 'react-idle-timer' +import Modal from 'react-modal' +import { FocusOn } from 'react-focus-on' +import Button from './Button' +import en from '../locales/en' +import fr from '../locales/fr' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { icon } from '../lib/loadIcons' + +export interface IdleTimeoutProps + extends Pick, + Pick { + locale: string + refPageAA?: string +} + +const IdleTimeout = ({ + promptBeforeIdle, + timeout, + locale, + refPageAA, +}: IdleTimeoutProps) => { + const router = useRouter() + const [modalOpen, setModalOpen] = useState(false) + const [timeRemaining, setTimeRemaining] = useState({ + seconds: '0', + minutes: 0, + }) + const t = locale === 'en' ? en : fr + const id = useId() + + const handleOnIdle = () => { + router.push('/auth/logout') + } + + const handleOnIdleContinueSession = () => { + setModalOpen(false) + reset() + } + + const { reset, getRemainingTime } = useIdleTimer({ + onIdle: handleOnIdle, + onPrompt: () => setModalOpen(true), + promptBeforeIdle: promptBeforeIdle ?? 5 * 60 * 1000, //5 minutes + timeout: timeout ?? 15 * 60 * 1000, //15 minutes + }) + + const tick = useCallback(() => { + const minutes = Math.floor(getRemainingTime() / 60000) + const seconds = Math.floor((getRemainingTime() / 1000) % 60).toFixed(0) + setTimeRemaining({ seconds, minutes }) + }, [getRemainingTime]) + + useEffect(() => { + setInterval(tick, 1000) + }, [tick]) + + return ( + + +

+
+
+ {t.bannerHeading} +
+ +
+ +
+
+ +
+
+

{t.bannerContent.notActive}

+

+ {t.bannerContent.signOut} {timeRemaining.minutes} + {t.bannerMinutesAnd} {timeRemaining.seconds} {t.bannerSeconds}. +

+
+
+ +
+
+
+ ) + + + ) +} + +export default IdleTimeout diff --git a/components/InfoMessage.tsx b/components/InfoMessage.tsx new file mode 100644 index 000000000..505d4d709 --- /dev/null +++ b/components/InfoMessage.tsx @@ -0,0 +1,75 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { icon as loadIcon } from '../lib/loadIcons' + +interface InfoMessageProps { + label: string + messageText: string + messageLinkText: string + messageLinkHref: string + locale: string + icon?: string + refPageAA: string +} + +const InfoMessage = ({ + locale, + label, + messageText, + messageLinkText, + messageLinkHref, + icon, + refPageAA, +}: InfoMessageProps) => { + return ( +
+
+ + {label} + +
+ + {label} + + + {messageText} + + {messageLinkText} + + +
+
+
+ ) +} + +export default InfoMessage diff --git a/components/Layout.js b/components/Layout.js index 1cf618ea9..b1ca62d9e 100644 --- a/components/Layout.js +++ b/components/Layout.js @@ -1,19 +1,17 @@ import PropTypes from 'prop-types' -import { useState, useCallback, useMemo, useEffect, cloneElement } from 'react' +import { useState, useCallback, useMemo, useEffect } from 'react' import Header from './Header' import Footer from './Footer' import MetaData from './MetaData' -import PhaseBanner from './PhaseBanner' import en from '../locales/en' import fr from '../locales/fr' -import MultiModal from './MultiModal' import { lato, notoSans } from '../utils/fonts' -import throttle from 'lodash.throttle' import { useRouter } from 'next/router' +import throttle from 'lodash.throttle' +import IdleTimeout from './IdleTimeout' import { signOut } from 'next-auth/react' export default function Layout(props) { - const display = props.display ?? {} const t = props.locale === 'en' ? en : fr const [response, setResponse] = useState() const router = useRouter() @@ -21,31 +19,6 @@ export default function Layout(props) { const contactLink = props.locale === 'en' ? '/en/contact-us' : '/fr/contactez-nous' - const [openModalWithLink, setOpenModalWithLink] = useState({ - activeLink: '/', - context: null, - }) - - const openModal = (link, context) => { - setOpenModalWithLink(() => { - return { - isOpen: true, - activeLink: link, - context, - } - }) - } - - const closeModal = () => { - setOpenModalWithLink(() => { - return { - isOpen: false, - activeLink: '/', - context: null, - } - }) - } - const validationResponse = useCallback( async () => setResponse(await fetch('/api/refresh-msca')), [], @@ -65,7 +38,7 @@ export default function Layout(props) { useEffect(() => { window.addEventListener('visibilitychange', throttledVisiblityChangeEvent) window.addEventListener('click', throttledOnClickEvent) - //If validateSession call indicates an invalid MSCA session, redirect to logout + //If validateSession call indicates an invalid MSCA session, end next-auth session and redirect to login if (response?.status === 401) { signOut() router.push(`/${props.locale}/auth/login`) @@ -95,27 +68,6 @@ export default function Layout(props) { } `} - {props.display.hideBanner ? ( - '' - ) : ( - - )}
- {cloneElement(props.children, { openModal, closeModal })} + {props.children}
- - {process.env.ENVIRONMENT === 'production' ? ( -