Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating cookie consent component #1127

Merged
merged 40 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8c62b28
creating cookie consent component POC
snmln Aug 26, 2024
35f324f
cleaning up padding
snmln Aug 27, 2024
b57b33c
adjusting max width for poc
snmln Aug 27, 2024
0e73df9
creating modal exit animation
snmln Aug 29, 2024
6a49da2
removing cookieconsent component
snmln Aug 29, 2024
eb03d75
adding necessary uswds scss pacakagse
snmln Aug 30, 2024
e74f01a
creating core functionality
snmln Aug 30, 2024
6d6695e
creating mobile styling
snmln Aug 30, 2024
4714652
Merge branch 'main' into Cookie-consent-form-POC
snmln Sep 3, 2024
a1057a2
creating cookie consent component POC
snmln Aug 26, 2024
e150e29
implementing dynamic link positioning
snmln Sep 3, 2024
82ce544
cleaning up layoutroot
snmln Sep 3, 2024
16374be
reverting cookie consent format.
snmln Sep 3, 2024
1a6ad88
creating unit test
snmln Sep 4, 2024
322ab1c
correcting Chrome bug
snmln Sep 4, 2024
b77d334
cleaning out comments
snmln Sep 4, 2024
d4bd88d
registering cookieconsent content with parcel
snmln Sep 4, 2024
746f697
adding ENV flag for cookie usage
snmln Sep 4, 2024
019ee97
adding null check
snmln Sep 5, 2024
33fb687
adding CookieConsentFromVedaConfig
snmln Sep 5, 2024
1adb24a
addressing test and lint errors
snmln Sep 5, 2024
e3b6966
Merge branch 'main' into Cookie-consent-form-POC
snmln Sep 11, 2024
b3b238f
Implementing review feedback
snmln Sep 13, 2024
50029a2
adding components to USWDS and creating roll up file.
snmln Sep 16, 2024
99170d7
implementing review feedback
snmln Sep 17, 2024
b7267e2
addressing lint and ts-check issues
snmln Sep 17, 2024
a030126
implementing util classes
snmln Sep 18, 2024
11ee198
removing uneccessary comments
snmln Sep 18, 2024
9cc346e
removing redundant util file
snmln Sep 18, 2024
01b7443
remving .env check
snmln Sep 18, 2024
84d3d62
Parse cookie consent form as html
hanbyul-here Sep 20, 2024
51d6270
Parse cookie consent form as html (#1163)
hanbyul-here Sep 20, 2024
80f63a9
implementing Parcel resolver and cleaning up test
snmln Sep 23, 2024
4ff71e5
Remove custom styles, other small touch-ups, hide the cookie consent
hanbyul-here Sep 23, 2024
ce5415e
Touch up for animation, separate constants
hanbyul-here Sep 23, 2024
5b981fc
Merge branch 'Cookie-consent-form-POC' into cookie-consent-touch-up
snmln Sep 23, 2024
107ce35
correcting lint errors
snmln Sep 23, 2024
9b14371
Remove custom styles, other small touch-ups, hide the cookie consent …
snmln Sep 23, 2024
9356b87
Fix Cookey Key
hanbyul-here Sep 25, 2024
3bbd537
Fix test
hanbyul-here Sep 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ API_STAC_ENDPOINT='https://staging.openveda.cloud/api/stac'
GOOGLE_FORM = 'https://docs.google.com/forms/d/e/1FAIpQLSfGcd3FDsM3kQIOVKjzdPn4f88hX8RZ4Qef7qBsTtDqxjTSkg/viewform?embedded=true'

FEATURE_NEW_EXPLORATION = 'TRUE'
SHOW_CONFIGURABLE_COLOR_MAP = 'FALSE'
COOKIE_CONSENT_FORM = 'TRUE'
SHOW_CONFIGURABLE_COLOR_MAP = 'FALSE'

60 changes: 60 additions & 0 deletions app/scripts/components/common/cookie-consent/cookieConsent.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';
snmln marked this conversation as resolved.
Show resolved Hide resolved
import '@testing-library/jest-dom';

import { render, screen, fireEvent } from '@testing-library/react';
import { CookieConsent } from './index';

describe('Cookie consent form should render with correct content.', () => {
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](https://www.nasa.gov/privacy/#cookies)'
};
const onFormInteraction = jest.fn();
beforeEach(() => {
render(
<CookieConsent {...cookieData} onFormInteraction={onFormInteraction} />
);
});
it('Renders correct content', () => {
expect(
screen.getByRole('link', { name: 'Privacy Policy' })
).toHaveAttribute('href', 'https://www.nasa.gov/privacy/#cookies');
expect(
screen.getByRole('button', { name: 'Decline Cookies' })
).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'Accept Cookies' })
).toBeInTheDocument();
expect(screen.getByText('Cookie Consent')).toBeInTheDocument();
expect(
screen.getByText(
'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'
)
).toBeInTheDocument();
});

it('Check correct cookie initialization', () => {
const resultCookie = document.cookie;

expect(resultCookie).toBe(
'CookieConsent={"responded":false,"answer":false}'
);
});

it('Check correct cookie content on Decline click', () => {
const button = screen.getByRole('button', { name: 'Decline Cookies' });
fireEvent.click(button);
const resultCookie = document.cookie;
expect(resultCookie).toBe(
'CookieConsent={"responded":true,"answer":false}'
);
});

it('Check correct cookie content on Accept click', () => {
const button = screen.getByRole('button', { name: 'Accept Cookies' });
fireEvent.click(button);
const resultCookie = document.cookie;

expect(resultCookie).toBe('CookieConsent={"responded":true,"answer":true}');
});
});
24 changes: 24 additions & 0 deletions app/scripts/components/common/cookie-consent/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.modal {
hanbyul-here marked this conversation as resolved.
Show resolved Hide resolved
z-index: 999;
position: fixed;
max-width: 725px;
bottom: 65px;
@media screen and (min-width: 768px) {
bottom: 0;
right: 0;
}
}
.hide-modal {
bottom: -500px;
opacity: 0;
transition: bottom 1.125s ease-in-out 0.125s, opacity 1s ease-in-out 0.125s;
}

.close {
padding: 0;
& svg {
@media screen and (min-width: 768px) {
right: -50px;
}
}
}
127 changes: 127 additions & 0 deletions app/scripts/components/common/cookie-consent/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import React, { useState, useEffect } from 'react';

import {
Alert,
Button,
ButtonGroup,
Link,
Icon
} from '@trussworks/react-uswds';
snmln marked this conversation as resolved.
Show resolved Hide resolved
import './index.scss';

interface CookieConsentProps {
title: string;
copy: string;
onFormInteraction: () => void;
}

export const CookieConsent = ({
title,
copy,
onFormInteraction
}: CookieConsentProps) => {
const [cookieConsentResponded, SetCookieConsentResponded] = useState(Boolean);
const [cookieConsentAnswer, SetCookieConsentAnswer] = useState(Boolean);
const [closeConsent, setCloseConsent] = useState(Boolean);
snmln marked this conversation as resolved.
Show resolved Hide resolved
//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 = `CookieConsent=${JSON.stringify(
cookieValue
)}; path=/; expires=${closeConsent ? '0' : setCookieExpiration()}`;
};

const renderContent = () => {
const bracketsParenthRegex = /\[(.*?)\)/;
const interiroBracketsRegex = /\]\(/;

const parts = copy.split(bracketsParenthRegex);

const updatedContent = parts.map((item, key) => {
if (interiroBracketsRegex.test(item)) {
const linkParts = item.split(interiroBracketsRegex);

const newItem = (
/* eslint-disable react/no-array-index-key */

<Link key={key} href={linkParts[1]} target='_blank'>
{linkParts[0]}
</Link>
);
/* eslint-enable react/no-array-index-key */

return newItem;
}
return item;
});
return updatedContent;
};
snmln marked this conversation as resolved.
Show resolved Hide resolved

useEffect(() => {
// if (readCookie('CookieConsent') ) {
// console.log('document.cookie[CookieConsent]', readCookie('CookieConsent'))
const cookieValue = {
responded: cookieConsentResponded,
answer: cookieConsentAnswer
};

Check failure on line 72 in app/scripts/components/common/cookie-consent/index.tsx

View workflow job for this annotation

GitHub Actions / lint

Trailing spaces not allowed
setCookie(cookieValue, closeConsent);
onFormInteraction();
// }
}, [cookieConsentResponded, cookieConsentAnswer]);

return (
<div
className={`modal margin-2 shadow-2 ${
cookieConsentResponded || closeConsent ? 'hide-modal' : ''
}`}
>
<Alert
type='info'
heading={title && title}
headingLevel='h1'
noIcon={true}
className='radius-lg'
>
<Button
type='button'
className='usa-modal__close close'
onClick={() => {
setCloseConsent(true);
}}
>
<Icon.Close />
</Button>

{copy && renderContent()}
<ButtonGroup className='padding-top-2'>
<Button
onClick={() => {
SetCookieConsentResponded(true);
SetCookieConsentAnswer(false);

Check failure on line 107 in app/scripts/components/common/cookie-consent/index.tsx

View workflow job for this annotation

GitHub Actions / lint

Trailing spaces not allowed
}}
outline={true}
type='button'
>
Decline Cookies
</Button>
<Button
onClick={() => {
SetCookieConsentResponded(true);
SetCookieConsentAnswer(true);
}}
type='button'
>
Accept Cookies
</Button>
</ButtonGroup>
</Alert>
</div>
);
};
11 changes: 11 additions & 0 deletions app/scripts/components/common/cookie-consent/util.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const readCookie = (name) => {
const nameEQ = name + '=';
const attribute = document.cookie.split(';');
for (let i = 0; i < attribute.length; i++) {
let c = attribute[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
};

Check failure on line 11 in app/scripts/components/common/cookie-consent/util.tsx

View workflow job for this annotation

GitHub Actions / lint

Trailing spaces not allowed
71 changes: 65 additions & 6 deletions app/scripts/components/common/layout-root/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import React, { ReactNode, useContext, useCallback } from 'react';
import React, { ReactNode, useContext, useCallback, useEffect } from 'react';
import { useDeepCompareEffect } from 'use-deep-compare';
import styled from 'styled-components';
import { Outlet } from 'react-router';
import { reveal } from '@devseed-ui/animation';
import { getCookieConsentFromVedaConfig } from 'veda';
import MetaTags from '../meta-tags';
import PageFooter from '../page-footer';
import Banner from '../banner';
import { CookieConsent } from '../cookie-consent';

import { LayoutRootContext } from './context';

import { useGoogleTagManager } from '$utils/use-google-tag-manager';
import { setGoogleTagManager } from '$utils/use-google-tag-manager';

import NavWrapper from '$components/common/nav-wrapper';
import Logo from '$components/common/page-header/logo';
import { mainNavItems, subNavItems} from '$components/common/page-header/default-config';
import {
mainNavItems,
subNavItems
} from '$components/common/page-header/default-config';

import { checkEnvFlag } from '$utils/utils';

const appTitle = process.env.APP_TITLE;
const appDescription = process.env.APP_DESCRIPTION;
Expand All @@ -35,17 +43,58 @@ const PageBody = styled.div`
`;

function LayoutRoot(props: { children?: ReactNode }) {
const useConsentForm = checkEnvFlag(process.env.COOKIE_CONSENT_FORM);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is still using env variable. And I think your intention is to depend on veda config instead of env var?

const readCookie = (name) => {
const nameEQ = name + '=';
hanbyul-here marked this conversation as resolved.
Show resolved Hide resolved
const attribute = document.cookie.split(';');
for (let i = 0; i < attribute.length; i++) {
let c = attribute[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
};
const { children } = props;
let cookieContents;

function getCookie() {
if (document.cookie != '') {
const cookie = readCookie('CookieConsent');
if (cookie != null) {
cookieContents = JSON.parse(cookie);

cookieContents.answer && setGoogleTagManager();

return cookieContents;
}
}
cookieContents = 'NO COOKIE';
}
hanbyul-here marked this conversation as resolved.
Show resolved Hide resolved

const cookieConsentContent = getCookieConsentFromVedaConfig();
const showForm = () => {
getCookie();

hanbyul-here marked this conversation as resolved.
Show resolved Hide resolved
if (cookieContents === 'NO COOKIE') {
return true;
} else {
return !cookieContents.responded;
}
hanbyul-here marked this conversation as resolved.
Show resolved Hide resolved
};

useGoogleTagManager();
useEffect(() => {
!useConsentForm && setGoogleTagManager();

}, []);

const { title, thumbnail, description, banner, hideFooter } =
useContext(LayoutRootContext);

const truncatedTitle =
title?.length > 32 ? `${title.slice(0, 32)}...` : title;

const fullTitle = truncatedTitle ? `${truncatedTitle} — ` : '';

return (
<Page>
<MetaTags
Expand All @@ -54,10 +103,20 @@ function LayoutRoot(props: { children?: ReactNode }) {
thumbnail={thumbnail}
/>
{banner && <Banner appTitle={title} {...banner} />}
<NavWrapper mainNavItems={mainNavItems} subNavItems={subNavItems} logo={<Logo />} />
<NavWrapper
mainNavItems={mainNavItems}
subNavItems={subNavItems}
logo={<Logo />}
/>
<PageBody id={PAGE_BODY_ID} tabIndex={-1}>
<Outlet />
{children}
{useConsentForm && showForm() && (
<CookieConsent
{...cookieConsentContent}
onFormInteraction={getCookie}
/>
)}
</PageBody>
<PageFooter isHidden={hideFooter} />
</Page>
Expand Down
13 changes: 7 additions & 6 deletions app/scripts/components/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
ContentOverride
} from '$components/common/page-overrides';


const homeContent = getOverride('homeContent');

const Connections = styled(Hug)`
Expand Down Expand Up @@ -112,10 +113,10 @@ const getCoverProps = () => {

return author
? {
...coverProps,
attributionAuthor: author.name,
attributionUrl: author.url
}
...coverProps,
attributionAuthor: author.name,
attributionUrl: author.url
}
: coverProps;
} else {
return {
Expand All @@ -138,9 +139,8 @@ function RootHome() {
<PageMainContent>
<LayoutProps
title='Welcome'
banner={renderBanner? {...banner}: null}
banner={renderBanner ? { ...banner } : null}
/>

<ComponentOverride with='homeHero'>
<PageHeroHome
title={homeContent?.data.title ?? `Welcome to the ${appTitle}`}
Expand Down Expand Up @@ -171,6 +171,7 @@ function RootHome() {
</ComponentOverride>

<ContentOverride with='homeContent'>

<Audience />

<FeaturedStories />
Expand Down
Loading
Loading