diff --git a/app/scripts/components/common/cookie-consent/cookieConsent.spec.js b/app/scripts/components/common/cookie-consent/cookieConsent.spec.js index e852949e8..7c12ad9f5 100644 --- a/app/scripts/components/common/cookie-consent/cookieConsent.spec.js +++ b/app/scripts/components/common/cookie-consent/cookieConsent.spec.js @@ -4,14 +4,19 @@ import '@testing-library/jest-dom'; import { render, screen, fireEvent } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; // For testing import { createMemoryHistory } from 'history'; -import * as utils from './utils'; +import * as utils from './utils'; import CookieConsent from './index'; +const lodash = require('lodash'); describe('Cookie consent form should render with correct content.', () => { + const setDisplayCookieConsent = jest.fn(); + const setGoogleTagManager = jest.fn(); const cookieData = { title: 'Cookie Consent', - copy: '

We use cookies to enhance your browsing experience and to help us understand how our website is used. These cookies allow us to collect data on site usage and improve our services based on your interactions. To learn more about it, see our Privacy Policy

We use cookies to enhance your browsing experience and to help us understand how our website is used. These cookies allow us to collect data on site usage and improve our services based on your interactions. To learn more about it, see our [Privacy Policy](https://www.nasa.gov/privacy/#cookies)' + copy: '

We use cookies to enhance your browsing experience and to help us understand how our website is used. These cookies allow us to collect data on site usage and improve our services based on your interactions. To learn more about it, see our Privacy Policy

We use cookies to enhance your browsing experience and to help us understand how our website is used. These cookies allow us to collect data on site usage and improve our services based on your interactions. To learn more about it, see our [Privacy Policy](https://www.nasa.gov/privacy/#cookies)', + setDisplayCookieConsent, + setGoogleTagManager }; const onFormInteraction = jest.fn(); @@ -24,28 +29,14 @@ describe('Cookie consent form should render with correct content.', () => { pathname: 'localhost:3000/example/path' }) })); + + lodash.debounce = jest.fn((fn) => fn); + afterEach(() => { jest.clearAllMocks(); - }); - it('Check that session item is non existant prior to cookie consent render. Then confirm that cookie consent creates session item.', () => { - expect(sessionStorage.getItem(utils.SESSION_KEY)).toBeNull(); - render( - - - - ); - expect(sessionStorage.getItem(utils.SESSION_KEY)).toBe(`true`); - }); - it('Check that getcookie is only called once on render and session item is true', () => { - const spy = jest.spyOn(utils, 'getCookie'); - render( - - - - ); - expect(spy).toHaveBeenCalledTimes(1); - expect(sessionStorage.getItem(utils.SESSION_KEY)).toBe(`true`); + // Clear cookies after each test + document.cookie = `${utils.COOKIE_CONSENT_KEY}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; }); it('Renders correct content', () => { @@ -91,8 +82,8 @@ describe('Cookie consent form should render with correct content.', () => { ); - const button = screen.getByRole('button', { name: 'Accept Cookies' }); - fireEvent.click(button); + const acceptButton = screen.getByRole('button', { name: 'Accept Cookies' }); + fireEvent.click(acceptButton); const resultCookie = document.cookie; expect(resultCookie).toBe( diff --git a/app/scripts/components/common/cookie-consent/index.tsx b/app/scripts/components/common/cookie-consent/index.tsx index bf8854615..1b0606491 100644 --- a/app/scripts/components/common/cookie-consent/index.tsx +++ b/app/scripts/components/common/cookie-consent/index.tsx @@ -1,7 +1,8 @@ import React, { useState, useEffect } from 'react'; +import { debounce } from 'lodash'; import { Icon } from '@trussworks/react-uswds'; -import { COOKIE_CONSENT_KEY, SESSION_KEY, getCookie } from './utils'; +import { setCookie, getCookie } from './utils'; import { USWDSAlert, USWDSButton, @@ -14,7 +15,7 @@ interface CookieConsentProps { title?: string | undefined; copy?: string | undefined; pathname: string; - sessionStart: string | undefined; + setDisplayCookieConsentForm: (boolean) => void; setGoogleTagManager: () => void; } @@ -25,126 +26,128 @@ function addAttribute(copy) { export const CookieConsent = ({ title, copy, - sessionStart, pathname, + setDisplayCookieConsentForm, setGoogleTagManager }: CookieConsentProps) => { - const [cookieConsentResponded, SetCookieConsentResponded] = + const [cookieConsentResponded, setCookieConsentResponded] = useState(false); - const [cookieConsentAnswer, SetCookieConsentAnswer] = + // Debounce the setDisplayCookieConsentForm function + const debouncedSetCookieConsentResponded = debounce( + setCookieConsentResponded, + 500 + ); + + const [cookieConsentAnswer, setCookieConsentAnswer] = useState(false); const [closeConsent, setCloseConsent] = useState(false); - //Setting expiration date for cookie to expire and re-ask user for consent. - const setCookieExpiration = () => { - const today = new Date(); - today.setMonth(today.getMonth() + 3); - return today.toUTCString(); - }; - - const setCookie = (cookieValue, closeConsent) => { - document.cookie = `${COOKIE_CONSENT_KEY}=${JSON.stringify( - cookieValue - )}; path=/; expires=${closeConsent ? '0' : setCookieExpiration()}`; - }; - - const setSessionData = () => { - if (typeof window !== 'undefined') { - const checkForSessionDate = window.sessionStorage.getItem(SESSION_KEY); - if (!checkForSessionDate) { - window.sessionStorage.setItem(SESSION_KEY, 'true'); + useEffect(() => { + const cookieContents = getCookie(); + if (cookieContents) { + if (!cookieContents.responded) { + setCloseConsent(false); + return; } + cookieContents.answer && setGoogleTagManager(); + setCookieConsentResponded(cookieContents.responded); + setCookieConsentAnswer(cookieContents.answer); + setDisplayCookieConsentForm(false); + } else { + setCloseConsent(false); } - }; + // Only run on the first render + }, [setGoogleTagManager, setDisplayCookieConsentForm]); useEffect(() => { - if (sessionStart !== 'true' && !cookieConsentResponded) { - setSessionData(); - getCookie( - SetCookieConsentResponded, - SetCookieConsentAnswer, - setGoogleTagManager - ); - } - if (!cookieConsentResponded && closeConsent) { - setCloseConsent(false); - } - // to Rerender on route change + if (!cookieConsentResponded) setCloseConsent(false); + // To render the component when user hasn't answered yet // eslint-disable-next-line react-hooks/exhaustive-deps }, [pathname]); useEffect(() => { + // When not responded, do nothing. + if (!cookieConsentResponded) return; + // When answer is accept cookie, + // 1. set up google manager + cookieConsentAnswer && setGoogleTagManager(); + // 2. update the cookie value const cookieValue = { responded: cookieConsentResponded, answer: cookieConsentAnswer }; setCookie(cookieValue, closeConsent); - - // Ignoring setcookie for now since it will make infinite rendering - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [cookieConsentResponded, cookieConsentAnswer, closeConsent]); + // 3. Tell the layout that we don't have to render this consent form + // from the next render of layout. + setTimeout(() => { + setDisplayCookieConsentForm(false); + }, 500); + }, [ + cookieConsentResponded, + cookieConsentAnswer, + closeConsent, + setDisplayCookieConsentForm, + setGoogleTagManager + ]); return (
- { - // Adding debounce to conditional for animation out - setTimeout(() => { - !cookieConsentResponded; - }, 500) && ( - ); }; diff --git a/app/scripts/components/common/cookie-consent/utils.ts b/app/scripts/components/common/cookie-consent/utils.ts index 910ea1368..74faf765e 100644 --- a/app/scripts/components/common/cookie-consent/utils.ts +++ b/app/scripts/components/common/cookie-consent/utils.ts @@ -10,16 +10,23 @@ export const readCookie = (name: string): string => { ); }; -export const getCookie = ( - SetCookieConsentResponded, - SetCookieConsentAnswer, - setGoogleTagManager -) => { +export const getCookie = () => { const cookie = readCookie(COOKIE_CONSENT_KEY); if (cookie) { const cookieContents = JSON.parse(cookie); - if (cookieContents.answer) setGoogleTagManager(); - SetCookieConsentResponded(cookieContents.responded); - SetCookieConsentAnswer(cookieContents.answer); + return cookieContents; } }; + +//Setting expiration date for cookie to expire and re-ask user for consent. +export const setCookieExpiration = () => { + const today = new Date(); + today.setMonth(today.getMonth() + 3); + return today.toUTCString(); +}; + +export const setCookie = (cookieValue, closeConsent) => { + document.cookie = `${COOKIE_CONSENT_KEY}=${JSON.stringify( + cookieValue + )}; path=/; expires=${closeConsent ? '0' : setCookieExpiration()}`; +}; diff --git a/app/scripts/components/common/layout-root/index.tsx b/app/scripts/components/common/layout-root/index.tsx index 9feb9c69f..6fe9add22 100644 --- a/app/scripts/components/common/layout-root/index.tsx +++ b/app/scripts/components/common/layout-root/index.tsx @@ -13,9 +13,6 @@ import { reveal } from '@devseed-ui/animation'; import { getBannerFromVedaConfig, getCookieConsentFromVedaConfig } from 'veda'; import MetaTags from '../meta-tags'; import PageFooter from '../page-footer'; -// import Banner from '../banner'; -// import { CookieConsent } from '../cookie-consent'; -import { SESSION_KEY } from '../cookie-consent/utils'; const Banner = React.lazy(() => import('../banner')); const CookieConsent = React.lazy(() => import('../cookie-consent')); @@ -54,13 +51,13 @@ function LayoutRoot(props: { children?: ReactNode }) { const cookieConsentContent = getCookieConsentFromVedaConfig(); const bannerContent = getBannerFromVedaConfig(); const { children } = props; - const [sessionStart, setSesstionStart] = useState(); - const sessionItem = window.sessionStorage.getItem(SESSION_KEY); + const [displayCookieConsentForm, setDisplayCookieConsentForm] = + useState(true); const { pathname } = useLocation(); useEffect(() => { + // When there is no cookie consent form set up !cookieConsentContent && setGoogleTagManager(); - sessionItem && setSesstionStart(sessionItem); }, []); const { title, thumbnail, description, hideFooter } = @@ -92,10 +89,10 @@ function LayoutRoot(props: { children?: ReactNode }) { {children} - {cookieConsentContent && ( + {cookieConsentContent && displayCookieConsentForm && (