diff --git a/.travis.yml b/.travis.yml index 249b42bb..8ee6afc9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/src/components/CookiesBanner/assets/alert-red.svg b/src/components/CookiesBanner/assets/alert-red.svg new file mode 100644 index 00000000..6a20c41c --- /dev/null +++ b/src/components/CookiesBanner/assets/alert-red.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/components/CookiesBanner/assets/intercom.png b/src/components/CookiesBanner/assets/intercom.png new file mode 100644 index 00000000..74eb6efa Binary files /dev/null and b/src/components/CookiesBanner/assets/intercom.png differ diff --git a/src/components/CookiesBanner/index.tsx b/src/components/CookiesBanner/index.tsx index 6850456b..bd5e2551 100644 --- a/src/components/CookiesBanner/index.tsx +++ b/src/components/CookiesBanner/index.tsx @@ -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,139 +69,199 @@ 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 = ( -
- - Accept preferences > - -
-

- We use cookies to give you the best experience and to help improve our website. Please read our{' '} - - Cookie Policy - {' '} - for more information. By clicking "Accept all", you agree to the storing of cookies on your device - to enhance site navigation, analyze site usage and provide customer support. -

-
-
- } - disabled - label="Necessary" - name="Necessary" - onChange={() => setLocalNecessary((prev) => !prev)} - value={localNecessary} - /> -
-
- } - label="Analytics" - name="Analytics" - onChange={() => setLocalAnalytics((prev) => !prev)} - value={localAnalytics} - /> -
-
- + if (showAnalytics && !isDesktop) { + loadGoogleAnalytics() + } + + if (showIntercom) { + loadIntercom() + } + + const CookiesBannerForm = (props: CookiesBannerFormProps) => { + const { alertMessage } = props + return ( +
+
+ {alertMessage && ( +
+ + You attempted to open the customer support chat. Please accept the customer support cookie. +
+ )} +

+ We use cookies to provide you with the best experience and to help improve our website and application. + Please read our{' '} + + Cookie Policy + {' '} + for more information. By clicking "Accept all", you agree to the storing of cookies on your device + to enhance site navigation, analyze site usage and provide customer support. +

+
+
+ } + disabled + label="Necessary" + name="Necessary" + onChange={() => setLocalNecessary((prev) => !prev)} + value={localNecessary} + /> +
+
+ } + label="Customer support" + name="Customer support" + onChange={() => setLocalIntercom((prev) => !prev)} + value={localIntercom} + /> +
+
+ } + label="Analytics" + name="Analytics" + onChange={() => setLocalAnalytics((prev) => !prev)} + value={localAnalytics} + /> +
+
+ +
+
+ +
-
- ) - - if (showAnalytics) { - loadIntercom() - loadGoogleAnalytics() + ) } - if (isDesktop) loadIntercom() - return showBanner && !isDesktop ? cookieBannerContent : null + return ( + <> + {!isDesktop && !showIntercom && ( + dispatch(openCookieBanner(true, true))} + /> + )} + {!isDesktop && showBanner?.cookieBannerOpen && ( + + )} + + ) } export default CookiesBanner diff --git a/src/logic/cookies/store/actions/openCookieBanner.ts b/src/logic/cookies/store/actions/openCookieBanner.ts index 64eaa774..2f0a6231 100644 --- a/src/logic/cookies/store/actions/openCookieBanner.ts +++ b/src/logic/cookies/store/actions/openCookieBanner.ts @@ -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) => ({ - cookieBannerOpen, -})) +export const openCookieBanner = createAction( + OPEN_COOKIE_BANNER, + (cookieBannerOpen, intercomAlertDisplayed = false) => ({ + cookieBannerOpen, + intercomAlertDisplayed, + }), +) diff --git a/src/logic/cookies/store/reducer/cookies.ts b/src/logic/cookies/store/reducer/cookies.ts index 11da0cc9..43b1bca3 100644 --- a/src/logic/cookies/store/reducer/cookies.ts +++ b/src/logic/cookies/store/reducer/cookies.ts @@ -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(), ) diff --git a/src/logic/wallets/store/actions/fetchProvider.ts b/src/logic/wallets/store/actions/fetchProvider.ts index 79ce4479..def1045f 100644 --- a/src/logic/wallets/store/actions/fetchProvider.ts +++ b/src/logic/wallets/store/actions/fetchProvider.ts @@ -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) diff --git a/src/routes/open/container/Open.tsx b/src/routes/open/container/Open.tsx index 8b5df977..9744f4f3 100644 --- a/src/routes/open/container/Open.tsx +++ b/src/routes/open/container/Open.tsx @@ -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', }) diff --git a/src/utils/intercom.ts b/src/utils/intercom.ts index 68035e7d..605ac63d 100644 --- a/src/utils/intercom.ts +++ b/src/utils/intercom.ts @@ -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') +}