Skip to content
This repository has been archived by the owner on Nov 10, 2023. It is now read-only.

Commit

Permalink
feat: Cookies and privacy policy (#3517)
Browse files Browse the repository at this point in the history
* feat: link Beamer with the analytics cookies. Display different CookieBanner warning messages.

* refactor: extract to an object cookie warning messages

* refactor: clean up cookies reducer

Co-authored-by: Aaron Cook <iamacook@users.noreply.github.com>

* fix: update Beamer cookie alert copy

* fix: handle Beamer with update cookies

Co-authored-by: Aaron Cook <iamacook@users.noreply.github.com>
  • Loading branch information
DiogoSoaress and iamacook committed Feb 17, 2022
1 parent 60449bd commit db20b66
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 60 deletions.
13 changes: 9 additions & 4 deletions src/components/AppLayout/Sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { wrapInSuspense } from 'src/utils/wrapInSuspense'
import ListIcon from 'src/components/List/ListIcon'
import { openCookieBanner } from 'src/logic/cookies/store/actions/openCookieBanner'
import { loadFromCookie } from 'src/logic/cookies/utils'
import { COOKIES_KEY, BannerCookiesType } from 'src/logic/cookies/model/cookie'
import { COOKIES_KEY, BannerCookiesType, COOKIE_IDS } from 'src/logic/cookies/model/cookie'

const StyledDivider = styled(Divider)`
margin: 16px -8px 0;
Expand Down Expand Up @@ -88,8 +88,13 @@ const Sidebar = ({
dispatch(openCookieBanner({ cookieBannerOpen: true }))
return
}
if (!cookiesState.acceptedIntercom) {
dispatch(openCookieBanner({ cookieBannerOpen: true, intercomAlertDisplayed: true }))
if (!cookiesState.acceptedSupportAndUpdates) {
dispatch(
openCookieBanner({
cookieBannerOpen: true,
key: COOKIE_IDS.BEAMER,
}),
)
}
}

Expand Down Expand Up @@ -124,7 +129,7 @@ const Sidebar = ({
<HelpList>
<StyledListItem id="whats-new-button" button onClick={handleClick}>
<ListIcon type="gift" />
<StyledListItemText>Whats new</StyledListItemText>
<StyledListItemText>What&apos;s new</StyledListItemText>
</StyledListItem>

<HelpCenterLink href="https://help.gnosis-safe.io/en/" target="_blank" title="Help Center of Gnosis Safe">
Expand Down
83 changes: 44 additions & 39 deletions src/components/CookiesBanner/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { ReactElement, useEffect, useRef, 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, BannerCookiesType } from 'src/logic/cookies/model/cookie'
import { openCookieBanner } from 'src/logic/cookies/store/actions/openCookieBanner'
import { cookieBannerOpen } from 'src/logic/cookies/store/selectors'
import { COOKIES_KEY, BannerCookiesType, COOKIE_IDS, COOKIE_ALERTS } from 'src/logic/cookies/model/cookie'
import { closeCookieBanner, openCookieBanner } from 'src/logic/cookies/store/actions/openCookieBanner'
import { cookieBannerState } 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, removeCookies } from 'src/utils/googleAnalytics'
Expand Down Expand Up @@ -93,30 +93,29 @@ const useStyles = makeStyles({
},
} as any)

interface CookiesBannerFormProps {
alertMessage: boolean
}

const CookiesBanner = (): ReactElement => {
const classes = useStyles()
const dispatch = useRef(useDispatch())
const dispatch = useDispatch()
const intercomLoaded = isIntercomLoaded()

const [showAnalytics, setShowAnalytics] = useState(false)
const [showIntercom, setShowIntercom] = useState(false)
const [localNecessary, setLocalNecessary] = useState(true)
const [localAnalytics, setLocalAnalytics] = useState(false)
const [localIntercom, setLocalIntercom] = useState(false)
const [localSupportAndUpdates, setLocalSupportAndUpdates] = useState(false)
const { getAppUrl } = useSafeAppUrl()
const beamerScriptRef = useRef<HTMLScriptElement>()

const showBanner = useSelector(cookieBannerOpen)
const { key, cookieBannerOpen } = useSelector(cookieBannerState)
const newAppUrl = getAppUrl()
const isSafeAppView = newAppUrl !== null

useEffect(() => {
if (showIntercom && !isSafeAppView) {
loadIntercom()

// For use in non-webapps (Mobile and Desktop)
// https://www.getbeamer.com/help/how-to-install-beamer-using-our-api
loadBeamer(beamerScriptRef)
}
}, [showIntercom, isSafeAppView])
Expand All @@ -131,24 +130,24 @@ const CookiesBanner = (): ReactElement => {
async function fetchCookiesFromStorage() {
const cookiesState = await loadFromCookie<BannerCookiesType>(COOKIES_KEY)
if (!cookiesState) {
dispatch.current(openCookieBanner({ cookieBannerOpen: true }))
dispatch(openCookieBanner({ cookieBannerOpen: true }))
} else {
const { acceptedIntercom, acceptedAnalytics, acceptedNecessary } = cookiesState
if (acceptedIntercom === undefined) {
const { acceptedSupportAndUpdates, acceptedAnalytics, acceptedNecessary } = cookiesState
if (acceptedSupportAndUpdates === undefined) {
const newState = {
acceptedNecessary,
acceptedAnalytics,
acceptedIntercom: acceptedAnalytics,
acceptedSupportAndUpdates: acceptedAnalytics,
}
const cookieConfig: CookieAttributes = {
expires: acceptedAnalytics ? 365 : 7,
}
await saveCookie<BannerCookiesType>(COOKIES_KEY, newState, cookieConfig)
setLocalIntercom(newState.acceptedIntercom)
setShowIntercom(newState.acceptedIntercom)
setLocalSupportAndUpdates(newState.acceptedSupportAndUpdates)
setShowIntercom(newState.acceptedSupportAndUpdates)
} else {
setLocalIntercom(acceptedIntercom)
setShowIntercom(acceptedIntercom)
setLocalSupportAndUpdates(acceptedSupportAndUpdates)
setShowIntercom(acceptedSupportAndUpdates)
}
setLocalAnalytics(acceptedAnalytics)
setLocalNecessary(acceptedNecessary)
Expand All @@ -159,56 +158,57 @@ const CookiesBanner = (): ReactElement => {
}
}
fetchCookiesFromStorage()
}, [showAnalytics, showIntercom])
}, [dispatch, showAnalytics, showIntercom])

const acceptCookiesHandler = async () => {
const newState = {
acceptedNecessary: true,
acceptedAnalytics: !isDesktop,
acceptedIntercom: true,
acceptedSupportAndUpdates: true,
}
const cookieConfig: CookieAttributes = {
expires: 365,
}
await saveCookie<BannerCookiesType>(COOKIES_KEY, newState, cookieConfig)
setShowAnalytics(!isDesktop)
setShowIntercom(true)
dispatch.current(openCookieBanner({ cookieBannerOpen: false }))
dispatch(closeCookieBanner())
}

const closeCookiesBannerHandler = async () => {
const newState = {
acceptedNecessary: true,
acceptedAnalytics: localAnalytics,
acceptedIntercom: localIntercom,
acceptedSupportAndUpdates: localSupportAndUpdates,
}
const cookieConfig: CookieAttributes = {
expires: localAnalytics ? 365 : 7,
}
await saveCookie<BannerCookiesType>(COOKIES_KEY, newState, cookieConfig)
setShowAnalytics(localAnalytics)
setShowIntercom(localIntercom)
setShowIntercom(localSupportAndUpdates)

if (!localAnalytics) {
removeCookies()
}

if (!localIntercom && isIntercomLoaded()) {
closeIntercom()
if (!localSupportAndUpdates) {
closeBeamer(beamerScriptRef)
if (isIntercomLoaded()) {
closeIntercom()
}
}
dispatch.current(openCookieBanner({ cookieBannerOpen: false }))
dispatch(closeCookieBanner())
}

const CookiesBannerForm = (props: CookiesBannerFormProps) => {
const { alertMessage } = props
const CookiesBannerForm = () => {
return (
<div data-testid="cookies-banner-form" className={classes.container}>
<div className={classes.content}>
{alertMessage && (
{key && (
<div className={classes.intercomAlert}>
<img src={AlertRedIcon} />
You attempted to open the customer support chat. Please accept the customer support cookie.
{COOKIE_ALERTS[key]}
</div>
)}
<p className={classes.text}>
Expand All @@ -234,11 +234,11 @@ const CookiesBanner = (): ReactElement => {
</div>
<div className={classes.formItem}>
<FormControlLabel
control={<Checkbox checked={localIntercom} />}
label="Customer support"
name="Customer support"
onChange={() => setLocalIntercom((prev) => !prev)}
value={localIntercom}
control={<Checkbox checked={localSupportAndUpdates} />}
label="Community support & updates"
name="Community support & updates"
onChange={() => setLocalSupportAndUpdates((prev) => !prev)}
value={localSupportAndUpdates}
/>
</div>
<div className={classes.formItem}>
Expand Down Expand Up @@ -284,12 +284,17 @@ const CookiesBanner = (): ReactElement => {
<img
className={classes.intercomImage}
src={IntercomIcon}
onClick={() => dispatch.current(openCookieBanner({ cookieBannerOpen: true, intercomAlertDisplayed: true }))}
onClick={() =>
dispatch(
openCookieBanner({
cookieBannerOpen: true,
key: COOKIE_IDS.INTERCOM,
}),
)
}
/>
)}
{!isDesktop && showBanner?.cookieBannerOpen && (
<CookiesBannerForm alertMessage={showBanner?.intercomAlertDisplayed} />
)}
{!isDesktop && cookieBannerOpen && <CookiesBannerForm />}
</>
)
}
Expand Down
17 changes: 14 additions & 3 deletions src/logic/cookies/model/cookie.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
export const COOKIES_KEY = 'COOKIES'
export const COOKIES_KEY_INTERCOM = `${COOKIES_KEY}_INTERCOM`

export enum COOKIE_IDS {
INTERCOM = 'INTERCOM',
BEAMER = 'BEAMER',
}

export const COOKIE_ALERTS: Record<COOKIE_IDS, string> = {
INTERCOM: 'You attempted to open the customer support chat. Please accept the community support & updates cookies',
BEAMER: "You attempted to open the What's New section. Please accept the community support & updates cookies.",
}

export type BannerCookiesType = {
acceptedNecessary: boolean
acceptedIntercom: boolean
acceptedSupportAndUpdates: boolean
acceptedAnalytics: boolean
}
export type IntercomCookieType = {
userId: string
}
export const COOKIES_KEY = 'COOKIES'
export const COOKIES_KEY_INTERCOM = `${COOKIES_KEY}_INTERCOM`
8 changes: 6 additions & 2 deletions src/logic/cookies/store/actions/openCookieBanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { createAction } from 'redux-actions'

import { OpenCookieBannerPayload } from 'src/logic/cookies/store/reducer/cookies'

export const OPEN_COOKIE_BANNER = 'OPEN_COOKIE_BANNER'
export enum COOKIE_ACTIONS {
OPEN_BANNER = 'cookies/openCookieBanner',
CLOSE_BANNER = 'cookies/closeCookieBanner',
}

export const openCookieBanner = createAction<OpenCookieBannerPayload>(OPEN_COOKIE_BANNER)
export const openCookieBanner = createAction<OpenCookieBannerPayload>(COOKIE_ACTIONS.OPEN_BANNER)
export const closeCookieBanner = createAction(COOKIE_ACTIONS.CLOSE_BANNER)
27 changes: 19 additions & 8 deletions src/logic/cookies/store/reducer/cookies.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import { Map } from 'immutable'
import { handleActions } from 'redux-actions'
import { OPEN_COOKIE_BANNER } from 'src/logic/cookies/store/actions/openCookieBanner'

import { COOKIE_IDS } from 'src/logic/cookies/model/cookie'
import { COOKIE_ACTIONS } from 'src/logic/cookies/store/actions/openCookieBanner'

export const COOKIES_REDUCER_ID = 'cookies'

type CookieState = Map<string, any>
export type CookieState = {
cookieBannerOpen: boolean
key?: COOKIE_IDS
}

const initialState: CookieState = {
cookieBannerOpen: false,
key: undefined,
}

export type OpenCookieBannerPayload = { cookieBannerOpen: boolean; intercomAlertDisplayed?: boolean }
export type OpenCookieBannerPayload = CookieState

const cookiesReducer = handleActions<CookieState, OpenCookieBannerPayload>(
{
[OPEN_COOKIE_BANNER]: (state, action) => {
const { intercomAlertDisplayed = false, cookieBannerOpen } = action.payload
return state.set('cookieBannerOpen', { intercomAlertDisplayed, cookieBannerOpen })
[COOKIE_ACTIONS.OPEN_BANNER]: (_, action) => {
return action.payload
},
[COOKIE_ACTIONS.CLOSE_BANNER]: () => {
return initialState
},
},
Map(),
initialState,
)

export default cookiesReducer
5 changes: 3 additions & 2 deletions src/logic/cookies/store/selectors/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { COOKIES_REDUCER_ID } from 'src/logic/cookies/store/reducer/cookies'
import { CookieState, COOKIES_REDUCER_ID } from 'src/logic/cookies/store/reducer/cookies'
import { AppReduxState } from 'src/store'

export const cookieBannerOpen = (state) => state[COOKIES_REDUCER_ID].get('cookieBannerOpen')
export const cookieBannerState = (state: AppReduxState): CookieState => state[COOKIES_REDUCER_ID]
4 changes: 2 additions & 2 deletions src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
nftAssetReducer,
nftTokensReducer,
} from 'src/logic/collectibles/store/reducer/collectibles'
import cookiesReducer, { COOKIES_REDUCER_ID } from 'src/logic/cookies/store/reducer/cookies'
import cookiesReducer, { CookieState, COOKIES_REDUCER_ID } from 'src/logic/cookies/store/reducer/cookies'
import currentSessionReducer, {
CurrentSessionState,
CURRENT_SESSION_REDUCER_ID,
Expand Down Expand Up @@ -112,7 +112,7 @@ export type AppReduxState = CombinedState<{
[PENDING_TRANSACTIONS_ID]: PendingTransactionsState
[NOTIFICATIONS_REDUCER_ID]: Map<string, Notification>
[CURRENCY_REDUCER_ID]: CurrencyValuesState
[COOKIES_REDUCER_ID]: Map<string, any>
[COOKIES_REDUCER_ID]: CookieState
[ADDRESS_BOOK_REDUCER_ID]: AddressBookState
[CURRENT_SESSION_REDUCER_ID]: CurrentSessionState
[CONFIG_REDUCER_ID]: ConfigState
Expand Down

0 comments on commit db20b66

Please sign in to comment.