Adapt cookie banner for GDPR (#1814)
* Update cookie banner action and reducer * Add cookie banner new assets * Update cookie banner * Update cookie banner migration * Disable cookie banner and intercom in desktop app * Use google analytics hook in Open Safe component Co-authored-by: Daniel Sanchez <daniel.sanchez@gnosis.pm>
This commit is contained in:
parent
7caec9c29c
commit
aba414ab79
|
@ -44,7 +44,7 @@ matrix:
|
|||
if: (branch = master AND NOT type = pull_request) OR tag IS present
|
||||
cache:
|
||||
npm: false
|
||||
yarn: false
|
||||
yarn: true
|
||||
before_script:
|
||||
- if [[ -n "$TRAVIS_TAG" ]]; then export REACT_APP_ENV='production'; fi;
|
||||
- if [ $TRAVIS_PULL_REQUEST != "false" ]; then export PUBLIC_URL="/${REACT_APP_NETWORK}/app"; fi;
|
||||
|
@ -54,7 +54,6 @@ before_install:
|
|||
- sudo apt-get -y install python3-pip python3-dev libusb-1.0-0-dev libudev-dev
|
||||
- pip install awscli --upgrade --user
|
||||
script:
|
||||
- yarn lint:check
|
||||
- yarn prettier:check
|
||||
- yarn test:coverage
|
||||
- yarn build
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M0 0H16V16H0z" transform="translate(-260 -550) translate(260 550)"/>
|
||||
<path fill="#F02525" d="M8 5.996c.553 0 1 .447 1 1v.998c0 .553-.447 1-1 1-.552 0-1-.447-1-1v-.998c0-.553.448-1 1-1M8 9.895c.607 0 1.101.492 1.101 1.1 0 .607-.494 1.1-1.1 1.1-.608 0-1.1-.493-1.1-1.1 0-.608.492-1.1 1.1-1.1" transform="translate(-260 -550) translate(260 550)"/>
|
||||
<path fill="#F02525" d="M8 0c-.383 0-.766.193-.975.581L.133 13.373c-.396.734.138 1.624.974 1.624h13.786c.836 0 1.37-.89.974-1.624L8.974.581C8.766.193 8.384 0 8 0m0 3l5.386 9.997H2.613L8 3" transform="translate(-260 -550) translate(260 550)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 843 B |
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
|
@ -1,10 +1,8 @@
|
|||
import Checkbox from '@material-ui/core/Checkbox'
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import cn from 'classnames'
|
||||
import React, { useEffect, useState, useCallback } from 'react'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
import Button from 'src/components/layout/Button'
|
||||
import Link from 'src/components/layout/Link'
|
||||
import { COOKIES_KEY } from 'src/logic/cookies/model/cookie'
|
||||
|
@ -13,7 +11,9 @@ import { cookieBannerOpen } from 'src/logic/cookies/store/selectors'
|
|||
import { loadFromCookie, saveCookie } from 'src/logic/cookies/utils'
|
||||
import { mainFontFamily, md, primary, screenSm } from 'src/theme/variables'
|
||||
import { loadGoogleAnalytics } from 'src/utils/googleAnalytics'
|
||||
import { loadIntercom } from 'src/utils/intercom'
|
||||
import { closeIntercom, isIntercomLoaded, loadIntercom } from 'src/utils/intercom'
|
||||
import AlertRedIcon from './assets/alert-red.svg'
|
||||
import IntercomIcon from './assets/intercom.png'
|
||||
|
||||
const isDesktop = process.env.REACT_APP_BUILD_FOR_DESKTOP
|
||||
|
||||
|
@ -27,14 +27,13 @@ const useStyles = makeStyles({
|
|||
justifyContent: 'center',
|
||||
left: '0',
|
||||
minHeight: '200px',
|
||||
padding: '27px 15px',
|
||||
padding: '30px 15px 45px',
|
||||
position: 'fixed',
|
||||
width: '100%',
|
||||
zIndex: '15',
|
||||
zIndex: '999',
|
||||
},
|
||||
content: {
|
||||
maxWidth: '100%',
|
||||
width: '830px',
|
||||
},
|
||||
text: {
|
||||
color: primary,
|
||||
|
@ -42,19 +41,21 @@ const useStyles = makeStyles({
|
|||
fontSize: md,
|
||||
fontWeight: 'normal',
|
||||
lineHeight: '1.38',
|
||||
margin: '0 0 25px',
|
||||
margin: '0 auto 35px',
|
||||
textAlign: 'center',
|
||||
maxWidth: '810px',
|
||||
},
|
||||
form: {
|
||||
columnGap: '10px',
|
||||
columnGap: '20px',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr',
|
||||
paddingBottom: '30px',
|
||||
rowGap: '10px',
|
||||
|
||||
paddingBottom: '50px',
|
||||
rowGap: '15px',
|
||||
margin: '0 auto',
|
||||
[`@media (min-width: ${screenSm}px)`]: {
|
||||
gridTemplateColumns: '1fr 1fr 1fr',
|
||||
gridTemplateColumns: '1fr 1fr 1fr 1fr 1fr',
|
||||
paddingBottom: '0',
|
||||
rowGap: '5px',
|
||||
},
|
||||
},
|
||||
formItem: {
|
||||
|
@ -68,89 +69,121 @@ const useStyles = makeStyles({
|
|||
textDecoration: 'none',
|
||||
},
|
||||
},
|
||||
acceptPreferences: {
|
||||
bottom: '-20px',
|
||||
intercomAlert: {
|
||||
fontWeight: 'bold',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
padding: '0 0 13px 0',
|
||||
svg: {
|
||||
marginRight: '5px',
|
||||
},
|
||||
},
|
||||
intercomImage: {
|
||||
position: 'fixed',
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
right: '20px',
|
||||
textDecoration: 'underline',
|
||||
|
||||
[`@media (min-width: ${screenSm}px)`]: {
|
||||
bottom: '-10px',
|
||||
},
|
||||
|
||||
'&:hover': {
|
||||
textDecoration: 'none',
|
||||
},
|
||||
height: '80px',
|
||||
width: '80px',
|
||||
bottom: '8px',
|
||||
right: '10px',
|
||||
zIndex: '1000',
|
||||
boxShadow: '1px 2px 10px 0 var(rgba(40, 54, 61, 0.18))',
|
||||
},
|
||||
} as any)
|
||||
|
||||
const CookiesBanner = () => {
|
||||
interface CookiesBannerFormProps {
|
||||
alertMessage: boolean
|
||||
}
|
||||
|
||||
const CookiesBanner = (): ReactElement => {
|
||||
const classes = useStyles()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const [showAnalytics, setShowAnalytics] = useState(false)
|
||||
const [showIntercom, setShowIntercom] = useState(false)
|
||||
const [localNecessary, setLocalNecessary] = useState(true)
|
||||
const [localAnalytics, setLocalAnalytics] = useState(false)
|
||||
const showBanner = useSelector(cookieBannerOpen)
|
||||
const [localIntercom, setLocalIntercom] = useState(false)
|
||||
|
||||
const acceptCookiesHandler = useCallback(async () => {
|
||||
const newState = {
|
||||
acceptedNecessary: true,
|
||||
acceptedAnalytics: !isDesktop,
|
||||
}
|
||||
await saveCookie(COOKIES_KEY, newState, 365)
|
||||
dispatch(openCookieBanner(false))
|
||||
setShowAnalytics(!isDesktop)
|
||||
}, [dispatch])
|
||||
const showBanner = useSelector(cookieBannerOpen)
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchCookiesFromStorage() {
|
||||
const cookiesState = await loadFromCookie(COOKIES_KEY)
|
||||
if (cookiesState) {
|
||||
const { acceptedAnalytics, acceptedNecessary } = cookiesState
|
||||
if (!cookiesState) {
|
||||
dispatch(openCookieBanner(true))
|
||||
} else {
|
||||
const { acceptedIntercom, acceptedAnalytics, acceptedNecessary } = cookiesState
|
||||
if (acceptedIntercom === undefined) {
|
||||
const newState = {
|
||||
acceptedNecessary,
|
||||
acceptedAnalytics,
|
||||
acceptedIntercom: acceptedAnalytics,
|
||||
}
|
||||
const expDays = acceptedAnalytics ? 365 : 7
|
||||
await saveCookie(COOKIES_KEY, newState, expDays)
|
||||
setLocalIntercom(newState.acceptedIntercom)
|
||||
setShowIntercom(newState.acceptedIntercom)
|
||||
} else {
|
||||
setLocalIntercom(acceptedIntercom)
|
||||
setShowIntercom(acceptedIntercom)
|
||||
}
|
||||
setLocalAnalytics(acceptedAnalytics)
|
||||
setLocalNecessary(acceptedNecessary)
|
||||
const openBanner = acceptedNecessary === false || showBanner
|
||||
dispatch(openCookieBanner(openBanner))
|
||||
setShowAnalytics(acceptedAnalytics)
|
||||
} else {
|
||||
dispatch(openCookieBanner(true))
|
||||
}
|
||||
}
|
||||
fetchCookiesFromStorage()
|
||||
}, [dispatch, showBanner])
|
||||
}, [showAnalytics, showIntercom])
|
||||
|
||||
useEffect(() => {
|
||||
if (isDesktop && showBanner) acceptCookiesHandler()
|
||||
}, [acceptCookiesHandler, showBanner])
|
||||
const acceptCookiesHandler = async () => {
|
||||
const newState = {
|
||||
acceptedNecessary: true,
|
||||
acceptedAnalytics: !isDesktop,
|
||||
acceptedIntercom: true,
|
||||
}
|
||||
await saveCookie(COOKIES_KEY, newState, 365)
|
||||
setShowAnalytics(!isDesktop)
|
||||
setShowIntercom(true)
|
||||
dispatch(openCookieBanner(false))
|
||||
}
|
||||
|
||||
const closeCookiesBannerHandler = async () => {
|
||||
const newState = {
|
||||
acceptedNecessary: true,
|
||||
acceptedAnalytics: localAnalytics,
|
||||
acceptedIntercom: localIntercom,
|
||||
}
|
||||
const expDays = localAnalytics ? 365 : 7
|
||||
await saveCookie(COOKIES_KEY, newState, expDays)
|
||||
setShowAnalytics(localAnalytics)
|
||||
setShowIntercom(localIntercom)
|
||||
if (!localIntercom && isIntercomLoaded()) {
|
||||
closeIntercom()
|
||||
}
|
||||
dispatch(openCookieBanner(false))
|
||||
}
|
||||
|
||||
const cookieBannerContent = (
|
||||
if (showAnalytics && !isDesktop) {
|
||||
loadGoogleAnalytics()
|
||||
}
|
||||
|
||||
if (showIntercom) {
|
||||
loadIntercom()
|
||||
}
|
||||
|
||||
const CookiesBannerForm = (props: CookiesBannerFormProps) => {
|
||||
const { alertMessage } = props
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<span
|
||||
className={cn(classes.acceptPreferences, classes.text)}
|
||||
onClick={closeCookiesBannerHandler}
|
||||
onKeyDown={closeCookiesBannerHandler}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
data-testid="accept-preferences"
|
||||
>
|
||||
Accept preferences >
|
||||
</span>
|
||||
<div className={classes.content}>
|
||||
{alertMessage && (
|
||||
<div className={classes.intercomAlert}>
|
||||
<img src={AlertRedIcon} />
|
||||
You attempted to open the customer support chat. Please accept the customer support cookie.
|
||||
</div>
|
||||
)}
|
||||
<p className={classes.text}>
|
||||
We use cookies to give you the best experience and to help improve our website. Please read our{' '}
|
||||
We use cookies to provide you with the best experience and to help improve our website and application.
|
||||
Please read our{' '}
|
||||
<Link className={classes.link} to="https://gnosis-safe.io/cookie">
|
||||
Cookie Policy
|
||||
</Link>{' '}
|
||||
|
@ -169,6 +202,15 @@ const CookiesBanner = () => {
|
|||
value={localNecessary}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.formItem}>
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={localIntercom} />}
|
||||
label="Customer support"
|
||||
name="Customer support"
|
||||
onChange={() => setLocalIntercom((prev) => !prev)}
|
||||
value={localIntercom}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.formItem}>
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={localAnalytics} />}
|
||||
|
@ -183,24 +225,43 @@ const CookiesBanner = () => {
|
|||
color="primary"
|
||||
component={Link}
|
||||
minWidth={180}
|
||||
onClick={() => acceptCookiesHandler()}
|
||||
onClick={() => closeCookiesBannerHandler()}
|
||||
variant="outlined"
|
||||
>
|
||||
Accept All
|
||||
Accept selection
|
||||
</Button>
|
||||
</div>
|
||||
<div className={classes.formItem}>
|
||||
<Button
|
||||
color="primary"
|
||||
component={Link}
|
||||
minWidth={180}
|
||||
onClick={() => acceptCookiesHandler()}
|
||||
variant="contained"
|
||||
>
|
||||
Accept all
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
if (showAnalytics) {
|
||||
loadIntercom()
|
||||
loadGoogleAnalytics()
|
||||
}
|
||||
if (isDesktop) loadIntercom()
|
||||
|
||||
return showBanner && !isDesktop ? cookieBannerContent : null
|
||||
return (
|
||||
<>
|
||||
{!isDesktop && !showIntercom && (
|
||||
<img
|
||||
className={classes.intercomImage}
|
||||
src={IntercomIcon}
|
||||
onClick={() => dispatch(openCookieBanner(true, true))}
|
||||
/>
|
||||
)}
|
||||
{!isDesktop && showBanner?.cookieBannerOpen && (
|
||||
<CookiesBannerForm alertMessage={showBanner?.intercomAlertDisplayed} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CookiesBanner
|
||||
|
|
|
@ -2,6 +2,10 @@ import { createAction } from 'redux-actions'
|
|||
|
||||
export const OPEN_COOKIE_BANNER = 'OPEN_COOKIE_BANNER'
|
||||
|
||||
export const openCookieBanner = createAction(OPEN_COOKIE_BANNER, (cookieBannerOpen) => ({
|
||||
export const openCookieBanner = createAction(
|
||||
OPEN_COOKIE_BANNER,
|
||||
(cookieBannerOpen, intercomAlertDisplayed = false) => ({
|
||||
cookieBannerOpen,
|
||||
}))
|
||||
intercomAlertDisplayed,
|
||||
}),
|
||||
)
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
import { Map } from 'immutable'
|
||||
import { handleActions } from 'redux-actions'
|
||||
|
||||
import { OPEN_COOKIE_BANNER } from 'src/logic/cookies/store/actions/openCookieBanner'
|
||||
|
||||
export const COOKIES_REDUCER_ID = 'cookies'
|
||||
|
||||
export default handleActions(
|
||||
{
|
||||
[OPEN_COOKIE_BANNER]: (state, action) => {
|
||||
const { cookieBannerOpen } = action.payload
|
||||
|
||||
return state.set('cookieBannerOpen', cookieBannerOpen)
|
||||
},
|
||||
[OPEN_COOKIE_BANNER]: (state, action) => state.set('cookieBannerOpen', action.payload),
|
||||
},
|
||||
Map(),
|
||||
)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import ReactGA from 'react-ga'
|
||||
import { Dispatch } from 'redux'
|
||||
|
||||
import addProvider from './addProvider'
|
||||
|
||||
|
@ -8,7 +9,6 @@ import enqueueSnackbar from 'src/logic/notifications/store/actions/enqueueSnackb
|
|||
import { getProviderInfo, getWeb3 } from 'src/logic/wallets/getWeb3'
|
||||
import { makeProvider } from 'src/logic/wallets/store/model/provider'
|
||||
import { updateStoredTransactionsStatus } from 'src/logic/safe/store/actions/transactions/utils/transactionHelpers'
|
||||
import { Dispatch } from 'redux'
|
||||
|
||||
export const processProviderResponse = (dispatch, provider) => {
|
||||
const walletRecord = makeProvider(provider)
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { Loader } from '@gnosis.pm/safe-react-components'
|
||||
import queryString from 'query-string'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { PromiEvent, TransactionReceipt } from 'web3-core'
|
||||
|
||||
import { SafeDeployment } from 'src/routes/opening'
|
||||
import { InitialValuesForm, Layout } from 'src/routes/open/components/Layout'
|
||||
import Page from 'src/components/layout/Page'
|
||||
|
@ -23,8 +25,7 @@ import { loadFromStorage, removeFromStorage, saveToStorage } from 'src/utils/sto
|
|||
import { userAccountSelector } from 'src/logic/wallets/store/selectors'
|
||||
import { SafeRecordProps } from 'src/logic/safe/store/models/safe'
|
||||
import { addOrUpdateSafe } from 'src/logic/safe/store/actions/addOrUpdateSafe'
|
||||
import { PromiEvent, TransactionReceipt } from 'web3-core'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { useAnalytics } from 'src/utils/googleAnalytics'
|
||||
|
||||
const SAFE_PENDING_CREATION_STORAGE_KEY = 'SAFE_PENDING_CREATION_STORAGE_KEY'
|
||||
|
||||
|
@ -121,6 +122,7 @@ const Open = (): React.ReactElement => {
|
|||
const userAccount = useSelector(userAccountSelector)
|
||||
const dispatch = useDispatch()
|
||||
const location = useLocation()
|
||||
const { trackEvent } = useAnalytics()
|
||||
|
||||
useEffect(() => {
|
||||
// #122: Allow to migrate an old Multisig by passing the parameters to the URL.
|
||||
|
@ -179,7 +181,7 @@ const Open = (): React.ReactElement => {
|
|||
|
||||
await dispatch(addOrUpdateSafe(safeProps))
|
||||
|
||||
ReactGA.event({
|
||||
trackEvent({
|
||||
category: 'User',
|
||||
action: 'Created a safe',
|
||||
})
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import { INTERCOM_ID } from 'src/utils/constants'
|
||||
|
||||
let intercomLoaded = false
|
||||
|
||||
export const isIntercomLoaded = () => intercomLoaded
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
export const loadIntercom = () => {
|
||||
export const loadIntercom = (): void => {
|
||||
const APP_ID = INTERCOM_ID
|
||||
if (!APP_ID) {
|
||||
console.error('[Intercom] - In order to use Intercom you need to add an appID')
|
||||
return null
|
||||
return
|
||||
}
|
||||
const d = document
|
||||
const s = d.createElement('script')
|
||||
|
@ -20,5 +24,12 @@ export const loadIntercom = () => {
|
|||
app_id: APP_ID,
|
||||
consent: true,
|
||||
})
|
||||
intercomLoaded = true
|
||||
}
|
||||
}
|
||||
|
||||
export const closeIntercom = (): void => {
|
||||
if (!isIntercomLoaded()) return
|
||||
intercomLoaded = false
|
||||
;(window as any).Intercom('shutdown')
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue