Skip to content

Commit

Permalink
Feat: analytics through safe-apps-sdk (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
schmanu authored Mar 28, 2024
1 parent 4bb4a6a commit bb980cd
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 50 deletions.
23 changes: 23 additions & 0 deletions src/analytics/lockEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const LOCK_EVENTS = {
LOCK_BUTTON: {
action: 'Lock tokens button',
},
UNLOCK_BUTTON: {
action: 'Unlock tokens button',
},
WITHDRAW_BUTTON: {
action: 'Withdraw tokens button',
},
LOCK_SUCCESS: {
action: 'Transaction proposed',
label: 'locking',
},
UNLOCK_SUCCESS: {
action: 'Transaction proposed',
label: 'unlocking',
},
WITHDRAW_SUCCESS: {
action: 'Transaction proposed',
label: 'withdrawal',
},
}
14 changes: 14 additions & 0 deletions src/analytics/navigation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const NAVIGATION_EVENTS = {
OPEN_LOCKING: {
action: 'Open locking page',
},
OPEN_UNLOCKING: {
action: 'Open unlocking/withdrawal page',
},
OPEN_CLAIM: {
action: 'Open claim page',
},
LEADERBOARD_SHOW_MORE: {
action: 'Leaderboard show more',
},
}
39 changes: 22 additions & 17 deletions src/components/NavTabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import NextLink, { type LinkProps as NextLinkProps } from 'next/link'
import { Tab, Tabs, Typography, type TabProps } from '@mui/material'
import { useRouter } from 'next/router'
import css from './styles.module.css'
import { useIsSafeApp } from '@/hooks/useIsSafeApp'
import Track from '../Track'

export type NavItem = {
label: string
icon?: ReactElement
href: string
event: { action: string }
}

type Props = TabProps & NextLinkProps
Expand Down Expand Up @@ -38,27 +41,29 @@ const NextLinkComposed = forwardRef<HTMLAnchorElement, Props>(function NextCompo
const NavTabs = ({ tabs }: { tabs: NavItem[] }) => {
const router = useRouter()
const activeTab = Math.max(0, tabs.map((tab) => tab.href).indexOf(router.pathname))
const query = router.query.safe ? { safe: router.query.safe } : undefined

const isSafeApp = useIsSafeApp()

return (
<Tabs value={activeTab} variant="scrollable" allowScrollButtonsMobile className={css.tabs}>
{tabs.map((tab, idx) => (
<Tab
component={NextLinkComposed}
key={tab.href}
href={{ pathname: tab.href, query }}
className={css.tab}
label={
<Typography
variant="body2"
fontWeight={700}
color={activeTab === idx ? 'primary' : 'primary.light'}
className={css.label}
>
{tab.label}
</Typography>
}
/>
<Track {...tab.event} label={isSafeApp ? 'safe-app' : 'standalone'} key={tab.href}>
<Tab
component={NextLinkComposed}
href={{ pathname: tab.href }}
className={css.tab}
label={
<Typography
variant="body2"
fontWeight={700}
color={activeTab === idx ? 'primary' : 'primary.light'}
className={css.label}
>
{tab.label}
</Typography>
}
/>
</Track>
))}
</Tabs>
)
Expand Down
3 changes: 3 additions & 0 deletions src/components/PageLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import css from './styles.module.css'
import NavTabs from '../NavTabs'
import { AppRoutes, RoutesWithNavigation, RoutesRequiringWallet } from '@/config/routes'
import { useRouter } from 'next/router'
import { NAVIGATION_EVENTS } from '@/analytics/navigation'
import { useWallet } from '@/hooks/useWallet'
import { useIsSafeApp } from '@/hooks/useIsSafeApp'

Expand Down Expand Up @@ -44,10 +45,12 @@ export const PageLayout = ({ children }: { children: ReactNode }): ReactElement
{
label: 'Activity App',
href: AppRoutes.activity,
event: NAVIGATION_EVENTS.OPEN_LOCKING,
},
{
label: 'Governance / Claiming',
href: AppRoutes.index,
event: NAVIGATION_EVENTS.OPEN_CLAIM,
},
]}
/>
Expand Down
12 changes: 9 additions & 3 deletions src/components/TockenUnlocking/UnlockTokenWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import { getCurrentDays } from '@/utils/date'
import { CHAIN_START_TIMESTAMPS } from '@/config/constants'
import { BoostBreakdown } from '../TokenLocking/BoostBreakdown'
import { SEASON2_START } from '../TokenLocking/BoostGraph/graphConstants'
import Track from '../Track'
import { LOCK_EVENTS } from '@/analytics/lockEvents'
import { trackSafeAppEvent } from '@/utils/analytics'
import MilesReceipt from '@/components/TokenLocking/MilesReceipt'
import { useTxSender } from '@/hooks/useTxSender'

Expand Down Expand Up @@ -69,6 +72,7 @@ export const UnlockTokenWidget = ({

try {
await txSender?.sendTxs([unlockTx])
trackSafeAppEvent(LOCK_EVENTS.UNLOCK_SUCCESS.action)
setReceiptOpen(true)
} catch (err) {
console.error(err)
Expand Down Expand Up @@ -119,9 +123,11 @@ export const UnlockTokenWidget = ({
</Grid>

<Grid item xs={4}>
<Button onClick={onUnlock} variant="contained" fullWidth disableElevation disabled={isDisabled}>
{isUnlocking ? <CircularProgress size={20} /> : 'Unlock'}
</Button>
<Track {...LOCK_EVENTS.UNLOCK_BUTTON}>
<Button onClick={onUnlock} variant="contained" fullWidth disableElevation disabled={isDisabled}>
{isUnlocking ? <CircularProgress size={20} /> : 'Unlock'}
</Button>
</Track>
</Grid>
</Grid>
</Grid>
Expand Down
24 changes: 15 additions & 9 deletions src/components/TockenUnlocking/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import SafeToken from '@/public/images/token.svg'
import { useMemo, useState } from 'react'
import { CHAIN_START_TIMESTAMPS } from '@/config/constants'
import { useSummarizedLockHistory } from '@/hooks/useSummarizedLockHistory'
import Track from '../Track'
import { LOCK_EVENTS } from '@/analytics/lockEvents'
import { trackSafeAppEvent } from '@/utils/analytics'
import { useTxSender } from '@/hooks/useTxSender'

const TokenUnlocking = () => {
Expand All @@ -41,6 +44,7 @@ const TokenUnlocking = () => {
const withdrawTx = createWithdrawTx(chainId)
try {
await txSender?.sendTxs([withdrawTx])
trackSafeAppEvent(LOCK_EVENTS.WITHDRAW_SUCCESS.action)
} catch (error) {
console.error(error)
}
Expand Down Expand Up @@ -98,15 +102,17 @@ const TokenUnlocking = () => {
</Typography>
</Grid>
</Box>
<Button
variant="contained"
color="primary"
onClick={onWithdraw}
disabled={totalWithdrawable.eq(0) || isWithdrawing || !isTransactionPossible}
sx={{ ml: 'auto !important' }}
>
{isWithdrawing ? <CircularProgress size={20} /> : 'Withdraw'}
</Button>
<Track {...LOCK_EVENTS.WITHDRAW_BUTTON}>
<Button
variant="contained"
color="primary"
onClick={onWithdraw}
disabled={totalWithdrawable.eq(0) || isWithdrawing || !isTransactionPossible}
sx={{ ml: 'auto !important' }}
>
{isWithdrawing ? <CircularProgress size={20} /> : 'Withdraw'}
</Button>
</Track>
</Stack>
</Paper>
</Grid>
Expand Down
19 changes: 12 additions & 7 deletions src/components/TokenLocking/ActionNavigation.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { NAVIGATION_EVENTS } from '@/analytics/navigation'
import { AppRoutes } from '@/config/routes'

import { Typography, Stack, Button, Box } from '@mui/material'
import { useRouter } from 'next/router'
import Track from '../Track'

import css from './styles.module.css'

Expand All @@ -19,12 +20,16 @@ export const ActionNavigation = () => {
<Typography variant="subtitle1" fontWeight={700}>
Remove SAFE from locking
</Typography>

<Button variant="outlined" color="primary" onClick={onUnlockAndWithdraw} sx={{ borderWidth: '1px !important' }}>
<Box display="flex" alignItems="center">
<Box>Unlock/Withdraw</Box>
</Box>
</Button>
<Track {...NAVIGATION_EVENTS.OPEN_UNLOCKING}>
<Button
variant="outlined"
color="primary"
onClick={onUnlockAndWithdraw}
sx={{ borderWidth: '1px !important' }}
>
Unlock/Withdraw
</Button>
</Track>
</Box>
</Stack>
)
Expand Down
24 changes: 14 additions & 10 deletions src/components/TokenLocking/Leaderboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { formatAmount } from '@/utils/formatters'
import { formatEther } from 'ethers/lib/utils'
import { ReactElement, useState } from 'react'
import { useEnsLookup } from '@/hooks/useEnsLookup'
import Track from '../Track'
import { NAVIGATION_EVENTS } from '@/analytics/navigation'

const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
Expand Down Expand Up @@ -163,16 +165,18 @@ const LeaderboardPage = ({ index, onLoadMore }: { index: number; onLoadMore?: ()
{onLoadMore && leaderboardPage?.next && (
<tr>
<td colSpan={3} style={{ textAlign: 'center' }}>
<Link
sx={{
'&:hover': {
cursor: 'pointer',
},
}}
onClick={onLoadMore}
>
Load more
</Link>
<Track {...NAVIGATION_EVENTS.LEADERBOARD_SHOW_MORE}>
<Link
sx={{
'&:hover': {
cursor: 'pointer',
},
}}
onClick={onLoadMore}
>
Show more
</Link>
</Track>
</td>
</tr>
)}
Expand Down
13 changes: 9 additions & 4 deletions src/components/TokenLocking/LockTokenWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import { SEASON2_START } from './BoostGraph/graphConstants'
import { CHAIN_START_TIMESTAMPS, UNLIMITED_APPROVAL_AMOUNT } from '@/config/constants'
import { getCurrentDays } from '@/utils/date'
import { BoostBreakdown } from './BoostBreakdown'
import Track from '../Track'
import { LOCK_EVENTS } from '@/analytics/lockEvents'
import { trackSafeAppEvent } from '@/utils/analytics'
import MilesReceipt from '@/components/TokenLocking/MilesReceipt'
import { BaseTransaction, useTxSender } from '@/hooks/useTxSender'
import { useSafeTokenLockingAllowance } from '@/hooks/useSafeTokenBalance'
Expand Down Expand Up @@ -99,7 +102,7 @@ export const LockTokenWidget = ({ safeBalance }: { safeBalance: BigNumberish | u
await txSender?.sendTxs([txs[i]])
}
}

trackSafeAppEvent(LOCK_EVENTS.LOCK_SUCCESS.action)
setReceiptOpen(true)
} catch (error) {
console.error(error)
Expand Down Expand Up @@ -160,9 +163,11 @@ export const LockTokenWidget = ({ safeBalance }: { safeBalance: BigNumberish | u
</Grid>

<Grid item xs={4}>
<Button onClick={onLockTokens} variant="contained" fullWidth disableElevation disabled={isDisabled}>
{isLocking ? <CircularProgress size={20} /> : 'Lock'}
</Button>
<Track {...LOCK_EVENTS.LOCK_BUTTON}>
<Button onClick={onLockTokens} variant="contained" fullWidth disableElevation disabled={isDisabled}>
{isLocking ? <CircularProgress size={20} /> : 'Lock'}
</Button>
</Track>
</Grid>
</Grid>
</Grid>
Expand Down
51 changes: 51 additions & 0 deletions src/components/Track/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { trackSafeAppEvent } from '@/utils/analytics'
import type { ReactElement } from 'react'
import { Fragment, useEffect, useRef } from 'react'

type Props = {
children: ReactElement
as?: 'span' | 'div'
action: string
label?: string
}

const shouldTrack = (el: HTMLDivElement) => {
const disabledChildren = el.querySelectorAll('*[disabled]')
return disabledChildren.length === 0
}

const Track = ({ children, as: Wrapper = 'span', ...trackData }: Props): typeof children => {
const el = useRef<HTMLDivElement>(null)

useEffect(() => {
if (!el.current) {
return
}

const trackEl = el.current

const handleClick = () => {
if (shouldTrack(trackEl)) {
trackSafeAppEvent(trackData.action, trackData.label)
}
}

// We cannot use onClick as events in children do not always bubble up
trackEl.addEventListener('click', handleClick)
return () => {
trackEl.removeEventListener('click', handleClick)
}
}, [el, trackData])

if (children.type === Fragment) {
throw new Error('Fragments cannot be tracked.')
}

return (
<Wrapper data-track={trackData.action} ref={el}>
{children}
</Wrapper>
)
}

export default Track
13 changes: 13 additions & 0 deletions src/utils/analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const SAFE_APPS_ANALYTICS_CATEGORY = 'safe-apps-analytics'

export const trackSafeAppEvent = (action: string, label?: string) => {
window.parent.postMessage(
{
category: SAFE_APPS_ANALYTICS_CATEGORY,
action,
label,
safeAppName: 'Safe{Miles}',
},
'*',
)
}

0 comments on commit bb980cd

Please sign in to comment.