Track GA for safe actions (#1302)

* Track GA for safe actions

* Add tracking for safeListSidebar

* review changes

* review changes v2
This commit is contained in:
nicolas 2020-08-31 11:33:55 -03:00 committed by GitHub
parent e9f7acff63
commit f7d4cfe112
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 265 additions and 191 deletions

View File

@ -1,7 +1,7 @@
import React, { useEffect, useMemo, useState } from 'react'
import Drawer from '@material-ui/core/Drawer' import Drawer from '@material-ui/core/Drawer'
import SearchIcon from '@material-ui/icons/Search' import SearchIcon from '@material-ui/icons/Search'
import SearchBar from 'material-ui-search-bar' import SearchBar from 'material-ui-search-bar'
import * as React from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import SafeList from './SafeList' import SafeList from './SafeList'
@ -16,12 +16,11 @@ import Link from 'src/components/layout/Link'
import Row from 'src/components/layout/Row' import Row from 'src/components/layout/Row'
import { WELCOME_ADDRESS } from 'src/routes/routes' import { WELCOME_ADDRESS } from 'src/routes/routes'
import setDefaultSafe from 'src/logic/safe/store/actions/setDefaultSafe' import setDefaultSafe from 'src/logic/safe/store/actions/setDefaultSafe'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
import { defaultSafeSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' import { defaultSafeSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
import { AppReduxState } from 'src/store' import { AppReduxState } from 'src/store'
const { useEffect, useMemo, useState } = React
export const SafeListSidebarContext = React.createContext({ export const SafeListSidebarContext = React.createContext({
isOpen: false, isOpen: false,
toggleSidebar: () => {}, toggleSidebar: () => {},
@ -39,12 +38,7 @@ const SafeListSidebar = ({ children, currentSafe, defaultSafe, safes, setDefault
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const [filter, setFilter] = useState('') const [filter, setFilter] = useState('')
const classes = useSidebarStyles() const classes = useSidebarStyles()
const { trackEvent } = useAnalytics()
useEffect(() => {
setTimeout(() => {
setFilter('')
}, 300)
}, [isOpen])
const searchClasses = { const searchClasses = {
input: classes.searchInput, input: classes.searchInput,
@ -54,6 +48,9 @@ const SafeListSidebar = ({ children, currentSafe, defaultSafe, safes, setDefault
} }
const toggleSidebar = () => { const toggleSidebar = () => {
if (!isOpen) {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Safe List Sidebar' })
}
setIsOpen((prevIsOpen) => !prevIsOpen) setIsOpen((prevIsOpen) => !prevIsOpen)
} }
@ -73,6 +70,12 @@ const SafeListSidebar = ({ children, currentSafe, defaultSafe, safes, setDefault
const filteredSafes = useMemo(() => filterBy(filter, safes), [safes, filter]) const filteredSafes = useMemo(() => filterBy(filter, safes), [safes, filter])
useEffect(() => {
setTimeout(() => {
setFilter('')
}, 300)
}, [isOpen])
return ( return (
<SafeListSidebarContext.Provider value={{ isOpen, toggleSidebar }}> <SafeListSidebarContext.Provider value={{ isOpen, toggleSidebar }}>
<Drawer <Drawer

View File

@ -41,6 +41,7 @@ import RemoveOwnerIcon from 'src/routes/safe/components/Settings/assets/icons/bi
import RemoveOwnerIconDisabled from 'src/routes/safe/components/Settings/assets/icons/disabled-bin.svg' import RemoveOwnerIconDisabled from 'src/routes/safe/components/Settings/assets/icons/disabled-bin.svg'
import { addressBookQueryParamsSelector, safesListSelector } from 'src/logic/safe/store/selectors' import { addressBookQueryParamsSelector, safesListSelector } from 'src/logic/safe/store/selectors'
import { checksumAddress } from 'src/utils/checksumAddress' import { checksumAddress } from 'src/utils/checksumAddress'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
const AddressBookTable = ({ classes }) => { const AddressBookTable = ({ classes }) => {
const columns = generateColumns() const columns = generateColumns()
@ -53,6 +54,11 @@ const AddressBookTable = ({ classes }) => {
const [editCreateEntryModalOpen, setEditCreateEntryModalOpen] = useState(false) const [editCreateEntryModalOpen, setEditCreateEntryModalOpen] = useState(false)
const [deleteEntryModalOpen, setDeleteEntryModalOpen] = useState(false) const [deleteEntryModalOpen, setDeleteEntryModalOpen] = useState(false)
const [sendFundsModalOpen, setSendFundsModalOpen] = useState(false) const [sendFundsModalOpen, setSendFundsModalOpen] = useState(false)
const { trackEvent } = useAnalytics()
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'AddressBook' })
}, [trackEvent])
useEffect(() => { useEffect(() => {
if (entryAddressToEditOrCreateNew) { if (entryAddressToEditOrCreateNew) {

View File

@ -19,6 +19,7 @@ import {
import { isSameURL } from 'src/utils/url' import { isSameURL } from 'src/utils/url'
import { useIframeMessageHandler } from './hooks/useIframeMessageHandler' import { useIframeMessageHandler } from './hooks/useIframeMessageHandler'
import ConfirmTransactionModal from './components/ConfirmTransactionModal' import ConfirmTransactionModal from './components/ConfirmTransactionModal'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
const centerCSS = css` const centerCSS = css`
display: flex; display: flex;
@ -64,6 +65,7 @@ const Apps = (): React.ReactElement => {
) )
const iframeRef = useRef<HTMLIFrameElement>() const iframeRef = useRef<HTMLIFrameElement>()
const { trackEvent } = useAnalytics()
const granted = useSelector(grantedSelector) const granted = useSelector(grantedSelector)
const safeAddress = useSelector(safeParamAddressFromStateSelector) const safeAddress = useSelector(safeParamAddressFromStateSelector)
const safeName = useSelector(safeNameSelector) const safeName = useSelector(safeNameSelector)
@ -111,6 +113,7 @@ const Apps = (): React.ReactElement => {
[selectedAppId], [selectedAppId],
) )
// Auto Select app first App
useEffect(() => { useEffect(() => {
const selectFirstEnabledApp = () => { const selectFirstEnabledApp = () => {
const firstEnabledApp = appList.find((a) => !a.disabled) const firstEnabledApp = appList.find((a) => !a.disabled)
@ -124,7 +127,14 @@ const Apps = (): React.ReactElement => {
if (initialSelect || currentAppWasDisabled) { if (initialSelect || currentAppWasDisabled) {
selectFirstEnabledApp() selectFirstEnabledApp()
} }
}, [appList, selectedApp, selectedAppId]) }, [appList, selectedApp, selectedAppId, trackEvent])
// track GA
useEffect(() => {
if (selectedApp) {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Apps', label: selectedApp.name })
}
}, [selectedApp, trackEvent])
const handleIframeLoad = useCallback(() => { const handleIframeLoad = useCallback(() => {
const iframe = iframeRef.current const iframe = iframeRef.current

View File

@ -1,9 +1,9 @@
import React, { useEffect } from 'react'
import TableCell from '@material-ui/core/TableCell' import TableCell from '@material-ui/core/TableCell'
import TableContainer from '@material-ui/core/TableContainer' import TableContainer from '@material-ui/core/TableContainer'
import TableRow from '@material-ui/core/TableRow' import TableRow from '@material-ui/core/TableRow'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { List } from 'immutable' import { List } from 'immutable'
import React from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { styles } from './styles' import { styles } from './styles'
@ -29,6 +29,7 @@ import {
} from 'src/routes/safe/components/Balances/dataFetcher' } from 'src/routes/safe/components/Balances/dataFetcher'
import { extendedSafeTokensSelector, grantedSelector } from 'src/routes/safe/container/selector' import { extendedSafeTokensSelector, grantedSelector } from 'src/routes/safe/container/selector'
import { Skeleton } from '@material-ui/lab' import { Skeleton } from '@material-ui/lab'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
const useStyles = makeStyles(styles as any) const useStyles = makeStyles(styles as any)
@ -61,6 +62,11 @@ const Coins = (props: Props): React.ReactElement => {
const currencyValues = useSelector(safeFiatBalancesListSelector) const currencyValues = useSelector(safeFiatBalancesListSelector)
const granted = useSelector(grantedSelector) const granted = useSelector(grantedSelector)
const [filteredData, setFilteredData] = React.useState<List<BalanceData>>(List()) const [filteredData, setFilteredData] = React.useState<List<BalanceData>>(List())
const { trackEvent } = useAnalytics()
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Coins' })
}, [trackEvent])
React.useMemo(() => { React.useMemo(() => {
setFilteredData(getBalanceData(activeTokens, selectedCurrency, currencyValues, currencyRate)) setFilteredData(getBalanceData(activeTokens, selectedCurrency, currencyValues, currencyRate))

View File

@ -1,6 +1,6 @@
import React, { useEffect } from 'react'
import Card from '@material-ui/core/Card' import Card from '@material-ui/core/Card'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import React from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import Item from './components/Item' import Item from './components/Item'
@ -10,6 +10,7 @@ import { activeNftAssetsListSelector, nftTokensSelector } from 'src/logic/collec
import SendModal from 'src/routes/safe/components/Balances/SendModal' import SendModal from 'src/routes/safe/components/Balances/SendModal'
import { safeSelector } from 'src/logic/safe/store/selectors' import { safeSelector } from 'src/logic/safe/store/selectors'
import { fontColor, lg, screenSm, screenXs } from 'src/theme/variables' import { fontColor, lg, screenSm, screenXs } from 'src/theme/variables'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
const useStyles = makeStyles({ const useStyles = makeStyles({
cardInner: { cardInner: {
@ -74,13 +75,18 @@ const useStyles = makeStyles({
}, },
} as any) } as any)
const Collectibles = () => { const Collectibles = (): React.ReactElement => {
const classes = useStyles() const classes = useStyles()
const [selectedToken, setSelectedToken] = React.useState({}) const [selectedToken, setSelectedToken] = React.useState({})
const [sendNFTsModalOpen, setSendNFTsModalOpen] = React.useState(false) const [sendNFTsModalOpen, setSendNFTsModalOpen] = React.useState(false)
const { address, ethBalance, name } = useSelector(safeSelector) const { address, ethBalance, name } = useSelector(safeSelector)
const nftTokens = useSelector(nftTokensSelector) const nftTokens = useSelector(nftTokensSelector)
const activeAssetsList = useSelector(activeNftAssetsListSelector) const activeAssetsList = useSelector(activeNftAssetsListSelector)
const { trackEvent } = useAnalytics()
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Collectibles' })
}, [trackEvent])
const handleItemSend = (nftToken) => { const handleItemSend = (nftToken) => {
setSelectedToken(nftToken) setSelectedToken(nftToken)

View File

@ -1,6 +1,6 @@
import { Loader, Text, theme, Title } from '@gnosis.pm/safe-react-components' import { Loader, Text, theme, Title } from '@gnosis.pm/safe-react-components'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import React from 'react' import React, { useEffect } from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import styled from 'styled-components' import styled from 'styled-components'
@ -10,6 +10,7 @@ import ModulesTable from './ModulesTable'
import Block from 'src/components/layout/Block' import Block from 'src/components/layout/Block'
import { safeModulesSelector, safeNonceSelector } from 'src/logic/safe/store/selectors' import { safeModulesSelector, safeNonceSelector } from 'src/logic/safe/store/selectors'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
@ -39,10 +40,14 @@ const LoadingModules = (): React.ReactElement => {
const Advanced = (): React.ReactElement => { const Advanced = (): React.ReactElement => {
const classes = useStyles() const classes = useStyles()
const nonce = useSelector(safeNonceSelector) const nonce = useSelector(safeNonceSelector)
const modules = useSelector(safeModulesSelector) const modules = useSelector(safeModulesSelector)
const moduleData = getModuleData(modules) ?? null const moduleData = getModuleData(modules) ?? null
const { trackEvent } = useAnalytics()
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Settings', label: 'Advanced' })
}, [trackEvent])
return ( return (
<> <>

View File

@ -33,7 +33,7 @@ export const SAVE_OWNER_CHANGES_BTN_TEST_ID = 'save-owner-changes-btn'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
type OwnProps = { type OwnProps = {
isOpen: true isOpen: boolean
onClose: () => void onClose: () => void
ownerAddress: string ownerAddress: string
selectedOwnerName: string selectedOwnerName: string

View File

@ -1,9 +1,10 @@
import React, { useState, useEffect } from 'react'
import TableCell from '@material-ui/core/TableCell' import TableCell from '@material-ui/core/TableCell'
import TableContainer from '@material-ui/core/TableContainer' import TableContainer from '@material-ui/core/TableContainer'
import TableRow from '@material-ui/core/TableRow' import TableRow from '@material-ui/core/TableRow'
import { withStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import cn from 'classnames' import cn from 'classnames'
import React from 'react' import { List } from 'immutable'
import RemoveOwnerIcon from '../assets/icons/bin.svg' import RemoveOwnerIcon from '../assets/icons/bin.svg'
@ -28,6 +29,9 @@ import Img from 'src/components/layout/Img'
import Paragraph from 'src/components/layout/Paragraph/index' import Paragraph from 'src/components/layout/Paragraph/index'
import Row from 'src/components/layout/Row' import Row from 'src/components/layout/Row'
import { getOwnersWithNameFromAddressBook } from 'src/logic/addressBook/utils' import { getOwnersWithNameFromAddressBook } from 'src/logic/addressBook/utils'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
import { AddressBookEntryProps } from 'src/logic/addressBook/model/addressBook'
import { SafeOwner } from 'src/logic/safe/store/models/safe'
export const RENAME_OWNER_BTN_TEST_ID = 'rename-owner-btn' export const RENAME_OWNER_BTN_TEST_ID = 'rename-owner-btn'
export const REMOVE_OWNER_BTN_TEST_ID = 'remove-owner-btn' export const REMOVE_OWNER_BTN_TEST_ID = 'remove-owner-btn'
@ -35,166 +39,167 @@ export const ADD_OWNER_BTN_TEST_ID = 'add-owner-btn'
export const REPLACE_OWNER_BTN_TEST_ID = 'replace-owner-btn' export const REPLACE_OWNER_BTN_TEST_ID = 'replace-owner-btn'
export const OWNERS_ROW_TEST_ID = 'owners-row' export const OWNERS_ROW_TEST_ID = 'owners-row'
class ManageOwners extends React.Component<any, any> { const useStyles = makeStyles(styles)
constructor(props) {
super(props)
this.state = { type Props = {
selectedOwnerAddress: undefined, addressBook: unknown
selectedOwnerName: undefined, granted: boolean
showAddOwner: false, owners: List<SafeOwner>
showRemoveOwner: false,
showReplaceOwner: false,
showEditOwner: false,
}
}
onShow = (action, row?: any) => () => {
this.setState({
[`show${action}`]: true,
selectedOwnerAddress: row && row.address,
selectedOwnerName: row && row.name,
})
}
onHide = (action) => () => {
this.setState({
[`show${action}`]: false,
selectedOwnerAddress: undefined,
selectedOwnerName: undefined,
})
}
render() {
const { addressBook, classes, granted, owners } = this.props
const {
selectedOwnerAddress,
selectedOwnerName,
showAddOwner,
showEditOwner,
showRemoveOwner,
showReplaceOwner,
} = this.state
const columns = generateColumns()
const autoColumns = columns.filter((c) => !c.custom)
const ownersAdbk = getOwnersWithNameFromAddressBook(addressBook, owners)
const ownerData = getOwnerData(ownersAdbk)
return (
<>
<Block className={classes.formContainer}>
<Heading className={classes.title} tag="h2">
Manage Safe Owners
</Heading>
<Paragraph className={classes.annotation}>
Add, remove and replace owners or rename existing owners. Owner names are only stored locally and never
shared with Gnosis or any third parties.
</Paragraph>
<TableContainer>
<Table
columns={columns}
data={ownerData}
defaultFixed
defaultOrderBy={OWNERS_TABLE_NAME_ID}
disablePagination
label="Owners"
noBorder
size={ownerData.size}
>
{(sortedData) =>
sortedData.map((row, index) => (
<TableRow
className={cn(classes.hide, index >= 3 && index === sortedData.size - 1 && classes.noBorderBottom)}
data-testid={OWNERS_ROW_TEST_ID}
key={index}
tabIndex={-1}
>
{autoColumns.map((column: any) => (
<TableCell align={column.align} component="td" key={column.id} style={cellWidth(column.width)}>
{column.id === OWNERS_TABLE_ADDRESS_ID ? (
<OwnerAddressTableCell address={row[column.id]} showLinks />
) : (
row[column.id]
)}
</TableCell>
))}
<TableCell component="td">
<Row align="end" className={classes.actions}>
<Img
alt="Edit owner"
className={classes.editOwnerIcon}
onClick={this.onShow('EditOwner', row)}
src={RenameOwnerIcon}
testId={RENAME_OWNER_BTN_TEST_ID}
/>
{granted && (
<>
<Img
alt="Replace owner"
className={classes.replaceOwnerIcon}
onClick={this.onShow('ReplaceOwner', row)}
src={ReplaceOwnerIcon}
testId={REPLACE_OWNER_BTN_TEST_ID}
/>
{ownerData.size > 1 && (
<Img
alt="Remove owner"
className={classes.removeOwnerIcon}
onClick={this.onShow('RemoveOwner', row)}
src={RemoveOwnerIcon}
testId={REMOVE_OWNER_BTN_TEST_ID}
/>
)}
</>
)}
</Row>
</TableCell>
</TableRow>
))
}
</Table>
</TableContainer>
</Block>
{granted && (
<>
<Hairline />
<Row align="end" className={classes.controlsRow} grow>
<Col end="xs">
<Button
color="primary"
onClick={this.onShow('AddOwner')}
size="small"
testId={ADD_OWNER_BTN_TEST_ID}
variant="contained"
>
Add new owner
</Button>
</Col>
</Row>
</>
)}
<AddOwnerModal isOpen={showAddOwner} onClose={this.onHide('AddOwner')} />
<RemoveOwnerModal
isOpen={showRemoveOwner}
onClose={this.onHide('RemoveOwner')}
ownerAddress={selectedOwnerAddress}
ownerName={selectedOwnerName}
/>
<ReplaceOwnerModal
isOpen={showReplaceOwner}
onClose={this.onHide('ReplaceOwner')}
ownerAddress={selectedOwnerAddress}
ownerName={selectedOwnerName}
/>
<EditOwnerModal
isOpen={showEditOwner}
onClose={this.onHide('EditOwner')}
ownerAddress={selectedOwnerAddress}
selectedOwnerName={selectedOwnerName}
/>
</>
)
}
} }
export default withStyles(styles as any)(ManageOwners) const ManageOwners = ({ addressBook, granted, owners }: Props): React.ReactElement => {
const { trackEvent } = useAnalytics()
const classes = useStyles()
const [selectedOwnerAddress, setSelectedOwnerAddress] = useState()
const [selectedOwnerName, setSelectedOwnerName] = useState()
const [modalsStatus, setModalStatus] = useState({
showAddOwner: false,
showRemoveOwner: false,
showReplaceOwner: false,
showEditOwner: false,
})
const onShow = (action, row?: any) => () => {
setModalStatus((prevState) => ({
...prevState,
[`show${action}`]: !prevState[`show${action}`],
}))
setSelectedOwnerAddress(row && row.address)
setSelectedOwnerName(row && row.name)
}
const onHide = (action) => () => {
setModalStatus((prevState) => ({
...prevState,
[`show${action}`]: !Boolean(prevState[`show${action}`]),
}))
setSelectedOwnerAddress(undefined)
setSelectedOwnerName(undefined)
}
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Settings', label: 'Owners' })
}, [trackEvent])
const columns = generateColumns()
const autoColumns = columns.filter((c) => !c.custom)
const ownersAdbk = getOwnersWithNameFromAddressBook(addressBook as AddressBookEntryProps, owners)
const ownerData = getOwnerData(ownersAdbk)
return (
<>
<Block className={classes.formContainer}>
<Heading className={classes.title} tag="h2">
Manage Safe Owners
</Heading>
<Paragraph className={classes.annotation}>
Add, remove and replace owners or rename existing owners. Owner names are only stored locally and never shared
with Gnosis or any third parties.
</Paragraph>
<TableContainer>
<Table
columns={columns}
data={ownerData}
defaultFixed
defaultOrderBy={OWNERS_TABLE_NAME_ID}
disablePagination
label="Owners"
noBorder
size={ownerData.size}
>
{(sortedData) =>
sortedData.map((row, index) => (
<TableRow
className={cn(classes.hide, index >= 3 && index === sortedData.size - 1 && classes.noBorderBottom)}
data-testid={OWNERS_ROW_TEST_ID}
key={index}
>
{autoColumns.map((column: any) => (
<TableCell align={column.align} component="td" key={column.id} style={cellWidth(column.width)}>
{column.id === OWNERS_TABLE_ADDRESS_ID ? (
<OwnerAddressTableCell address={row[column.id]} showLinks />
) : (
row[column.id]
)}
</TableCell>
))}
<TableCell component="td">
<Row align="end" className={classes.actions}>
<Img
alt="Edit owner"
className={classes.editOwnerIcon}
onClick={onShow('EditOwner', row)}
src={RenameOwnerIcon}
testId={RENAME_OWNER_BTN_TEST_ID}
/>
{granted && (
<>
<Img
alt="Replace owner"
className={classes.replaceOwnerIcon}
onClick={onShow('ReplaceOwner', row)}
src={ReplaceOwnerIcon}
testId={REPLACE_OWNER_BTN_TEST_ID}
/>
{ownerData.size > 1 && (
<Img
alt="Remove owner"
className={classes.removeOwnerIcon}
onClick={onShow('RemoveOwner', row)}
src={RemoveOwnerIcon}
testId={REMOVE_OWNER_BTN_TEST_ID}
/>
)}
</>
)}
</Row>
</TableCell>
</TableRow>
))
}
</Table>
</TableContainer>
</Block>
{granted && (
<>
<Hairline />
<Row align="end" className={classes.controlsRow} grow>
<Col end="xs">
<Button
color="primary"
onClick={onShow('AddOwner')}
size="small"
testId={ADD_OWNER_BTN_TEST_ID}
variant="contained"
>
Add new owner
</Button>
</Col>
</Row>
</>
)}
<AddOwnerModal isOpen={modalsStatus.showAddOwner} onClose={onHide('AddOwner')} />
<RemoveOwnerModal
isOpen={modalsStatus.showRemoveOwner}
onClose={onHide('RemoveOwner')}
ownerAddress={selectedOwnerAddress}
ownerName={selectedOwnerName}
/>
<ReplaceOwnerModal
isOpen={modalsStatus.showReplaceOwner}
onClose={onHide('ReplaceOwner')}
ownerAddress={selectedOwnerAddress}
ownerName={selectedOwnerName}
/>
<EditOwnerModal
isOpen={modalsStatus.showEditOwner}
onClose={onHide('EditOwner')}
ownerAddress={selectedOwnerAddress}
selectedOwnerName={selectedOwnerName}
/>
</>
)
}
export default ManageOwners

View File

@ -1,6 +1,7 @@
import { lg, sm } from 'src/theme/variables' import { lg, sm } from 'src/theme/variables'
import { createStyles } from '@material-ui/core'
export const styles = () => ({ export const styles = createStyles({
formContainer: { formContainer: {
minHeight: '420px', minHeight: '420px',
}, },

View File

@ -1,5 +1,5 @@
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import React from 'react' import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { styles } from './style' import { styles } from './style'
@ -29,6 +29,7 @@ import {
safeNeedsUpdateSelector, safeNeedsUpdateSelector,
safeParamAddressFromStateSelector, safeParamAddressFromStateSelector,
} from 'src/logic/safe/store/selectors' } from 'src/logic/safe/store/selectors'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
export const SAFE_NAME_INPUT_TEST_ID = 'safe-name-input' export const SAFE_NAME_INPUT_TEST_ID = 'safe-name-input'
export const SAFE_NAME_SUBMIT_BTN_TEST_ID = 'change-safe-name-btn' export const SAFE_NAME_SUBMIT_BTN_TEST_ID = 'change-safe-name-btn'
@ -44,6 +45,7 @@ const SafeDetails = (): React.ReactElement => {
const safeName = useSelector(safeNameSelector) const safeName = useSelector(safeNameSelector)
const safeNeedsUpdate = useSelector(safeNeedsUpdateSelector) const safeNeedsUpdate = useSelector(safeNeedsUpdateSelector)
const safeCurrentVersion = useSelector(safeCurrentVersionSelector) const safeCurrentVersion = useSelector(safeCurrentVersionSelector)
const { trackEvent } = useAnalytics()
const [isModalOpen, setModalOpen] = React.useState(false) const [isModalOpen, setModalOpen] = React.useState(false)
const safeAddress = useSelector(safeParamAddressFromStateSelector) const safeAddress = useSelector(safeParamAddressFromStateSelector)
@ -63,6 +65,10 @@ const SafeDetails = (): React.ReactElement => {
setModalOpen(true) setModalOpen(true)
} }
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Settings', label: 'Details' })
}, [trackEvent])
return ( return (
<> <>
<GnoForm onSubmit={handleSubmit}> <GnoForm onSubmit={handleSubmit}>

View File

@ -1,6 +1,6 @@
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import { withSnackbar } from 'notistack' import { withSnackbar } from 'notistack'
import React, { useState } from 'react' import React, { useState, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import ChangeThreshold from './ChangeThreshold' import ChangeThreshold from './ChangeThreshold'
@ -22,6 +22,7 @@ import {
safeParamAddressFromStateSelector, safeParamAddressFromStateSelector,
safeThresholdSelector, safeThresholdSelector,
} from 'src/logic/safe/store/selectors' } from 'src/logic/safe/store/selectors'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
const ThresholdSettings = ({ classes, closeSnackbar, enqueueSnackbar }) => { const ThresholdSettings = ({ classes, closeSnackbar, enqueueSnackbar }) => {
const [isModalOpen, setModalOpen] = useState(false) const [isModalOpen, setModalOpen] = useState(false)
@ -52,6 +53,12 @@ const ThresholdSettings = ({ classes, closeSnackbar, enqueueSnackbar }) => {
) )
} }
const { trackEvent } = useAnalytics()
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Settings', label: 'Owners' })
}, [trackEvent])
return ( return (
<> <>
<Block className={classes.container}> <Block className={classes.container}>

View File

@ -7,7 +7,7 @@ import { withStyles } from '@material-ui/core/styles'
import ExpandLess from '@material-ui/icons/ExpandLess' import ExpandLess from '@material-ui/icons/ExpandLess'
import ExpandMore from '@material-ui/icons/ExpandMore' import ExpandMore from '@material-ui/icons/ExpandMore'
import cn from 'classnames' import cn from 'classnames'
import React, { useState } from 'react' import React, { useState, useEffect } from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import ExpandedTxComponent from './ExpandedTx' import ExpandedTxComponent from './ExpandedTx'
@ -21,6 +21,7 @@ import Block from 'src/components/layout/Block'
import Row from 'src/components/layout/Row' import Row from 'src/components/layout/Row'
import { safeCancellationTransactionsSelector } from 'src/logic/safe/store/selectors' import { safeCancellationTransactionsSelector } from 'src/logic/safe/store/selectors'
import { extendedTransactionsSelector } from 'src/logic/safe/store/selectors/transactions' import { extendedTransactionsSelector } from 'src/logic/safe/store/selectors/transactions'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
export const TRANSACTION_ROW_TEST_ID = 'transaction-row' export const TRANSACTION_ROW_TEST_ID = 'transaction-row'
@ -28,6 +29,11 @@ const TxsTable = ({ classes }) => {
const [expandedTx, setExpandedTx] = useState(null) const [expandedTx, setExpandedTx] = useState(null)
const cancellationTransactions = useSelector(safeCancellationTransactionsSelector) const cancellationTransactions = useSelector(safeCancellationTransactionsSelector)
const transactions = useSelector(extendedTransactionsSelector) const transactions = useSelector(extendedTransactionsSelector)
const { trackEvent } = useAnalytics()
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Transactions' })
}, [trackEvent])
const handleTxExpand = (safeTxHash) => { const handleTxExpand = (safeTxHash) => {
setExpandedTx((prevTx) => (prevTx === safeTxHash ? null : safeTxHash)) setExpandedTx((prevTx) => (prevTx === safeTxHash ? null : safeTxHash))

View File

@ -1,12 +1,14 @@
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import GoogleAnalytics from 'react-ga' import GoogleAnalytics, { EventArgs } from 'react-ga'
import { getGoogleAnalyticsTrackingID } from 'src/config' import { getGoogleAnalyticsTrackingID } from 'src/config'
import { COOKIES_KEY } from 'src/logic/cookies/model/cookie' import { COOKIES_KEY } from 'src/logic/cookies/model/cookie'
import { loadFromCookie } from 'src/logic/cookies/utils' import { loadFromCookie } from 'src/logic/cookies/utils'
export const SAFE_NAVIGATION_EVENT = 'Safe Navigation'
let analyticsLoaded = false let analyticsLoaded = false
export const loadGoogleAnalytics = () => { export const loadGoogleAnalytics = (): void => {
if (analyticsLoaded) { if (analyticsLoaded) {
return return
} }
@ -22,7 +24,12 @@ export const loadGoogleAnalytics = () => {
} }
} }
export const useAnalytics = () => { type UseAnalyticsResponse = {
trackPage: (path: string) => void
trackEvent: (event: EventArgs) => void
}
export const useAnalytics = (): UseAnalyticsResponse => {
const [analyticsAllowed, setAnalyticsAllowed] = useState(false) const [analyticsAllowed, setAnalyticsAllowed] = useState(false)
useEffect(() => { useEffect(() => {
@ -37,18 +44,24 @@ export const useAnalytics = () => {
}, []) }, [])
const trackPage = useCallback( const trackPage = useCallback(
(page, options = {}) => { (page) => {
if (!analyticsAllowed || !analyticsLoaded) { if (!analyticsAllowed || !analyticsLoaded) {
return return
} }
GoogleAnalytics.set({
page,
...options,
})
GoogleAnalytics.pageview(page) GoogleAnalytics.pageview(page)
}, },
[analyticsAllowed], [analyticsAllowed],
) )
return { trackPage } const trackEvent = useCallback(
(event: EventArgs) => {
if (!analyticsAllowed || !analyticsLoaded) {
return
}
GoogleAnalytics.event(event)
},
[analyticsAllowed],
)
return { trackPage, trackEvent }
} }