Skip to content

Commit

Permalink
Merge pull request #7008 from uktrade/feat/export-wins-review-cookie-…
Browse files Browse the repository at this point in the history
…page

Feat/export wins review cookie page
  • Loading branch information
paulgain authored Aug 9, 2024
2 parents b1268cb + 55e6d06 commit 3955d1d
Show file tree
Hide file tree
Showing 19 changed files with 581 additions and 33 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@
"http-server": "^14.1.1",
"image-minimizer-webpack-plugin": "^4.1.0",
"js-beautify": "^1.14.11",
"js-cookie": "^3.0.5",
"jsdom": "^20.0.3",
"jsdom-global": "^3.0.2",
"json-schema-faker": "^0.5.5",
Expand Down
1 change: 1 addition & 0 deletions src/apps/__export-wins-review/view.njk
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
padding: 0;
}
</style>
{% include "_includes/google-tag-manager-snippet.njk" %}
</head>
<body>
{% include "_includes/csp-nonce.njk" %}
Expand Down
28 changes: 28 additions & 0 deletions src/client/components/Task/RecentResult.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { TASK__START } from '../../actions'
import multiinstance from '../../utils/multiinstance'

export default multiinstance({
name: 'Task/RecentResult',
actionPattern: /.*/,
idProp: 'name',
reducer: (state, { type, id, onSuccessDispatch, result }) => {
switch (type) {
case TASK__START:
return {
...state,
[id]: {
...state?.[id],
successActionType: onSuccessDispatch,
},
}
case state?.[id]?.successActionType:
return {
...state,
[id]: { result },
}
default:
return state
}
},
component: ({ children, id, ...props }) => children(props[id]?.result),
})
11 changes: 10 additions & 1 deletion src/client/export-win-review.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import './webpack-csp-nonce'
import React from 'react'
import { createRoot } from 'react-dom/client'
import { Routes, Route } from 'react-router-dom'

import { createProvider } from './createProvider'
import WithoutOurSupport from './components/Resource/WithoutOurSupport'
Expand All @@ -11,6 +12,10 @@ import MarketingSource from './components/Resource/MarketingSource'

import Review from './modules/ExportWins/Review'
import { patchExportWinReview } from './modules/ExportWins/tasks'
import {
loadCookiePreference,
saveCookiePreference,
} from './modules/ExportWins/Review/CookiePage/tasks'

const Provider = createProvider({
...ExportWinReview.tasks,
Expand All @@ -19,12 +24,16 @@ const Provider = createProvider({
...Experience.tasks,
...MarketingSource.tasks,
TASK_PATCH_EXPORT_WIN_REVIEW: patchExportWinReview,
'load cookie preference': loadCookiePreference,
'save cookie preference': saveCookiePreference,
})

window.addEventListener('DOMContentLoaded', () => {
createRoot(document.getElementById('react-app')).render(
<Provider>
<Review />
<Routes>
<Route path="/exportwins/review*" element={<Review />} />
</Routes>
</Provider>
)
})
55 changes: 55 additions & 0 deletions src/client/modules/ExportWins/Review/CookiePage/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react'
import { H2 } from 'govuk-react'

import { FieldRadios, Form } from '../../../../components'
import Layout from '../Layout'

const CookiePage = () => (
<Layout title="Export Wins cookie policy">
<H2>How cookies are used in Export Wins</H2>
<p>Export wins puts small files (known as 'cookies') onto your computer.</p>
<p>
Cookies are used to measure how to use the website so it can be updated
and improved based on your needs.
</p>
<p>
Export Wins cookies never contain personally identifiable information.
</p>
<H2>Analytics cookies</H2>
<p>
We use Google Analytics software to collect information about how you use
Export Wins. We do this to help make sure the site is meeting the needs of
its users and to help us make improvements. Google Analytics stores
information about:
</p>
<ul>
<li>the pages you visit</li>
<li>how long you spend on each page</li>
<li>how you got to the site</li>
<li>what you click on while you're visiting the site</li>
</ul>
<p>We don't allow Google to use or share our analytics data.</p>
<Form
analyticsFormName="cookie-page-form"
id="cookieConsent"
submissionTaskName="save cookie preference"
initialValuesTaskName="load cookie preference"
submissionTaskResultToValues={(cookieConsent) => ({ cookieConsent })}
transformInitialValues={(cookieConsent) => ({ cookieConsent })}
transformPayload={({ cookieConsent }) => cookieConsent}
submitButtonLabel="Save cookie settings"
>
<FieldRadios
label="Do you want to accept analytics cookies?"
name="cookieConsent"
required="Choose one option"
options={[
{ label: 'Yes', value: 'granted' },
{ label: 'No', value: 'denied' },
]}
/>
</Form>
</Layout>
)

export default CookiePage
26 changes: 26 additions & 0 deletions src/client/modules/ExportWins/Review/CookiePage/saga.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { put, take } from 'redux-saga/effects'
import { eventChannel } from 'redux-saga'

const storageChannel = eventChannel((emit) => {
window.addEventListener('storage', emit)
return () => window.removeEventListener(emit)
})

/**
* This ensures that when the user sets their cookie preference
* in one browser tab, all the other tabs will pick up the change.
*/
// TODO: Once Redux state is persisted in session storage, this should not be needed
export function* cookiePreferenceChangeSaga() {
while (true) {
const { key, newValue } = yield take(storageChannel)
if (key === 'cookie-consent') {
yield put({
type: 'RESOURCE',
name: 'load cookie preference',
id: 'cookieConsent',
result: newValue,
})
}
}
}
33 changes: 33 additions & 0 deletions src/client/modules/ExportWins/Review/CookiePage/tasks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Cookies from 'js-cookie'

export const COOKIE_CONSENT_COOKIE_NAME = 'cookie-consent'
export const GRANTED = 'granted'
export const DENIED = 'denied'

export const loadCookiePreference = () =>
localStorage.getItem(COOKIE_CONSENT_COOKIE_NAME)

export const saveCookiePreference = (payload) => {
if (!window.gtag) {
throw Error(
'window.gtag not defined, you probably forgot to set the GOOGLE_TAG_MANAGER_KEY env var.'
)
}
if (![GRANTED, DENIED].includes(payload)) {
throw Error('Payload must be "granted" or "denied"')
}

localStorage.setItem(COOKIE_CONSENT_COOKIE_NAME, payload)

window.gtag('consent', 'update', {
analytics_storage: payload,
})

if (payload === DENIED) {
for (const cookieName in Cookies.get()) {
Cookies.remove(cookieName)
}
}

return payload
}
88 changes: 84 additions & 4 deletions src/client/modules/ExportWins/Review/Layout.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import React from 'react'
import styled from 'styled-components'
import { H1 } from 'govuk-react'
import { Button, H1, H2, Link } from 'govuk-react'
import { FONT_SIZE, FONT_WEIGHTS, SPACING } from '@govuk-react/constants'

import RecentTaskResult from '../../../components/Task/RecentResult'
import { StyledStatusMessage } from './ThankYou'
import { FormActions } from '../../../components'
import Footer from '../../../components/Footer'
import { BLACK, WHITE, LIGHT_GREY } from '../../../utils/colours'
import Task from '../../../components/Task'
import Resource from '../../../components/Resource/Resource'
import { BLACK, WHITE, LIGHT_GREY, GREEN } from '../../../utils/colours'

const Grid = styled.div({
minHeight: '100vh',
display: 'grid',
gridTemplateRows: 'auto auto 1fr minmax(min-content, 30px)',
gridTemplateRows: 'auto auto auto 1fr minmax(min-content, 30px)',
gridTemplateColumns: `1fr min(100vw, calc(960px + ${SPACING.SCALE_3} * 2)) 1fr`,
gridTemplateAreas: `
". cookie-banner ."
". main-bar ."
". header ."
". main ."
Expand Down Expand Up @@ -62,14 +68,87 @@ const Title = styled(H1)({
marginBottom: SPACING.SCALE_5,
})

const CookieBannerBackground = styled.div({
gridRow: 'cookie-banner',
gridColumn: '1 / -1',
background: '#f3f2f1',
})

const CookieBanner = styled.div({
gridArea: 'cookie-banner',
paddingTop: SPACING.SCALE_3,
})

const CookieConsentConfirmation = () => (
<RecentTaskResult name="save cookie preference" id="cookieConsent">
{(consent) =>
consent && (
<StyledStatusMessage colour={GREEN}>
You've {consent === 'granted' ? 'accepted' : 'rejected'} additional
cookies. You can change your cookie settings at any time.
</StyledStatusMessage>
)
}
</RecentTaskResult>
)

const Layout = ({ children, title, supertitle, headingContent }) => (
<Grid>
<CookieBannerBackground />
<Resource name="load cookie preference" id="cookieConsent">
{(persistedConsent) => (
<RecentTaskResult name="save cookie preference" id="cookieConsent">
{(updatedConsent) =>
!persistedConsent &&
!updatedConsent && (
<CookieBanner>
<H2>Cookies</H2>
<p>
We’d like to use analytics cookies so we can understand how
you use Export Wins and make improvements.
</p>
<p>
We also use essential cookies to remember if you’ve accepted
analytics cookies.
</p>
<FormActions>
<Task>
{(getTask) => {
const setPreference = (value) =>
getTask(
'save cookie preference',
'cookieConsent'
).start({
payload: value,
onSuccessDispatch: 'FORM__RESOLVED',
})
return (
<>
<Button onClick={() => setPreference('granted')}>
Accept analytics cookies
</Button>
<Button onClick={() => setPreference('denied')}>
Reject analytics cookies
</Button>
</>
)
}}
</Task>
<Link href="/exportwins/review/cookies">View cookies</Link>
</FormActions>
</CookieBanner>
)
}
</RecentTaskResult>
)}
</Resource>
<MainBarBackground />
<MainBar>Department for Business & Trade</MainBar>
<MainBar>Department for Business and Trade</MainBar>
<HeaderBackground />
<Header>
<p>{supertitle}</p>
<Title>{title}</Title>
<CookieConsentConfirmation />
{headingContent}
</Header>
<Main>{children}</Main>
Expand All @@ -78,6 +157,7 @@ const Layout = ({ children, title, supertitle, headingContent }) => (
'Privacy Policy':
'https://www.great.gov.uk/privacy-and-cookies/full-privacy-notice/',
'Accessibility Statement': '/exportwins/review/accesibility-statement',
Cookies: '/exportwins/review/cookies',
}}
/>
</Grid>
Expand Down
2 changes: 1 addition & 1 deletion src/client/modules/ExportWins/Review/ThankYou.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { StatusMessage } from '../../../components'

import Layout from './Layout'

const StyledStatusMessage = styled(StatusMessage)({
export const StyledStatusMessage = styled(StatusMessage)({
background: WHITE,
'& > *:first-child': {
marginTop: 0,
Expand Down
13 changes: 6 additions & 7 deletions src/client/modules/ExportWins/Review/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import styled from 'styled-components'
import HR from '../../../components/HR'

import ThankYou from './ThankYou'
import CookiePage from './CookiePage'

import Layout from './Layout'
import {
Expand Down Expand Up @@ -375,7 +376,7 @@ const Review = () => {
submissionTaskName="TASK_PATCH_EXPORT_WIN_REVIEW"
redirectMode="soft"
redirectTo={(_, { agree_with_win }) =>
`/exportwins/review-win/thankyou?agree=${agree_with_win}`
`../thankyou?agree=${agree_with_win}`
}
transformPayload={transformPayload(token)}
>
Expand All @@ -398,11 +399,9 @@ const Review = () => {

export default () => (
<Routes>
<Route
path="/exportwins/review/accesibility-statement"
element={<AccesibilityStatement />}
/>
<Route path="/exportwins/review/:token" element={<Review />} />
<Route path="/exportwins/review-win/thankyou" element={<ThankYou />} />
<Route path="/accesibility-statement" element={<AccesibilityStatement />} />
<Route path="/thankyou" element={<ThankYou />} />
<Route path="/cookies" element={<CookiePage />} />
<Route path="/:token" element={<Review />} />
</Routes>
)
3 changes: 3 additions & 0 deletions src/client/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ import companyActivityReducerNoAs from './modules/Companies/CompanyActivity/redu

import { ResendExportWin } from './modules/ExportWins/Form/ResendExportWin'

import RecentTaskResult from './components/Task/RecentResult'

export const reducers = {
tasks,
[FLASH_MESSAGE_ID]: flashMessageReducer,
Expand All @@ -211,6 +213,7 @@ export const reducers = {
...Form.reducerSpread,
...FieldAddAnother.reducerSpread,
...ResendExportWin.reducerSpread,
...RecentTaskResult.reducerSpread,
[DNB_CHECK_ID]: dnbCheckReducer,
[INVESTMENT_OPPORTUNITIES_LIST_ID]: investmentOpportunitiesListReducer,
[INVESTMENT_OPPORTUNITIES_DETAILS_ID]: investmentOpportunitiesDetailsReducer,
Expand Down
Loading

0 comments on commit 3955d1d

Please sign in to comment.