Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dApp: PostHog integration #871

Merged
merged 13 commits into from
Dec 5, 2024
5 changes: 5 additions & 0 deletions dapp/.env
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ VITE_GELATO_RELAY_API_KEY="htaJCy_XHj8WsE3w53WBMurfySDtjLP_TrNPPa6IPIc_" # this
# Get the API key from: https://thegraph.com/studio/apikeys/.
VITE_SUBGRAPH_API_KEY=""

# Posthog
VITE_POSTHOG_API_HOST="https://us.i.posthog.com"
VITE_POSTHOG_API_KEY=""

# Feature flags
VITE_FEATURE_FLAG_GAMIFICATION_ENABLED="false"
VITE_FEATURE_FLAG_WITHDRAWALS_ENABLED="false"
Expand All @@ -26,5 +30,6 @@ VITE_FEATURE_FLAG_XVERSE_WALLET_ENABLED="false"
VITE_FEATURE_FLAG_ACRE_POINTS_ENABLED="true"
VITE_FEATURE_FLAG_TVL_ENABLED="true"
VITE_FEATURE_GATING_DAPP_ENABLED="true"
VITE_FEATURE_POSTHOG_ENABLED="true"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's disable by default.

Suggested change
VITE_FEATURE_POSTHOG_ENABLED="true"
VITE_FEATURE_POSTHOG_ENABLED="false"

VITE_FEATURE_MOBILE_MODE_ENABLED="true"

1 change: 1 addition & 0 deletions dapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"framer-motion": "^10.16.5",
"luxon": "^3.5.0",
"mustache": "^4.2.0",
"posthog-js": "^1.186.1",
"react": "^18.2.0",
"react-confetti-explosion": "^2.1.2",
"react-dom": "^18.2.0",
Expand Down
5 changes: 4 additions & 1 deletion dapp/src/DApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import getWagmiConfig from "./wagmiConfig"
import queryClient from "./queryClient"
import { delay, logPromiseFailure } from "./utils"
import { AcreLogo } from "./assets/icons"
import PostHogProvider from "./posthog/PostHogProvider"

function SplashPage() {
return (
Expand Down Expand Up @@ -69,7 +70,9 @@ function DAppProviders() {
<SidebarContextProvider>
<WalletConnectionErrorContextProvider>
<ReduxProvider store={store}>
<DApp />
<PostHogProvider>
<DApp />
</PostHogProvider>
</ReduxProvider>
</WalletConnectionErrorContextProvider>
</SidebarContextProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from "@chakra-ui/react"
import { IconArrowNarrowRight } from "@tabler/icons-react"
import { AnimatePresence, Variants, motion } from "framer-motion"
import { usePostHogIdentity } from "#/hooks/posthog"
import ArrivingSoonTooltip from "../ArrivingSoonTooltip"
import { TextLg, TextMd } from "../shared/Typography"
import ConnectWalletStatusLabel from "./ConnectWalletStatusLabel"
Expand Down Expand Up @@ -71,6 +72,7 @@ export default function ConnectWalletButton({
const { closeModal } = useModal()
const dispatch = useAppDispatch()
const isMounted = useRef(false)
const { handleIdentification } = usePostHogIdentity()

const [isLoading, setIsLoading] = useState<boolean>(false)

Expand Down Expand Up @@ -117,8 +119,11 @@ export default function ConnectWalletButton({
if (!btcAddress) return

await handleSignMessageAndCreateSession(connector, btcAddress)
handleIdentification(btcAddress, {
connector: connectedConnector.id,
})
},
[connector, handleSignMessageAndCreateSession],
[connector, handleSignMessageAndCreateSession, handleIdentification],
)

const handleConnection = useCallback(() => {
Expand Down
18 changes: 18 additions & 0 deletions dapp/src/components/Header/ConnectWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
IconChevronUp,
} from "@tabler/icons-react"
import { useMatch } from "react-router-dom"
import { usePostHogIdentity } from "#/hooks/posthog"
import Tooltip from "../shared/Tooltip"

function isChangeAccountFeatureSupported(embeddedApp: string | undefined) {
Expand All @@ -45,12 +46,18 @@ export default function ConnectWallet() {
size: "lg",
})
const isDashboardPage = useMatch("/dashboard")
const { resetIdentity } = usePostHogIdentity()
const isMobile = useMobileMode()

const handleConnectWallet = (isReconnecting: boolean = false) => {
openModal(MODAL_TYPES.CONNECT_WALLET, { isReconnecting })
}

const handleDisconnectWallet = () => {
onDisconnect()
resetIdentity()
}

if (!address) {
return (
<Button
Expand Down Expand Up @@ -184,6 +191,17 @@ export default function ConnectWallet() {
</Tooltip>
),
)}

<Tooltip size="xs" label="Disconnect">
<IconButton
variant="ghost"
aria-label="Disconnect"
icon={<Icon as={IconLogout} boxSize={5} />}
px={2}
boxSize={5}
onClick={handleDisconnectWallet}
/>
</Tooltip>
</HStack>
</Flex>
</HStack>
Expand Down
5 changes: 5 additions & 0 deletions dapp/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Outlet } from "react-router-dom"
import { Flex, VStack } from "@chakra-ui/react"
import { useIsEmbed, useMobileMode } from "#/hooks"
import { DappMode } from "#/types"
import { usePostHogPageViewCapture } from "#/hooks/posthog"
import DocsDrawer from "./DocsDrawer"
import Header from "./Header"
import ModalRoot from "./ModalRoot"
Expand All @@ -24,6 +25,10 @@ function Layout() {
const isMobileMode = useMobileMode()
const { isEmbed, embeddedApp } = useIsEmbed()

// It needs to be called here because the scope of `RouterProvider` is
// required to get `location` from `useLocation` hook.
usePostHogPageViewCapture()

if (!isEmbed && isMobileMode) return <MobileModeBanner forceOpen />

const maxWidth = embeddedApp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { setStatus, setTxHash } from "#/store/action-flow"
import { ONE_SEC_IN_MILLISECONDS, queryKeysFactory } from "#/constants"
import { useTimeout } from "@chakra-ui/react"
import { useMutation } from "@tanstack/react-query"
import { PostHogEvent } from "#/posthog/events"
import { usePostHogCapture } from "#/hooks/posthog/usePostHogCapture"
import WalletInteractionModal from "../WalletInteractionModal"

const { userKeys } = queryKeysFactory
Expand All @@ -27,6 +29,7 @@ export default function DepositBTCModal() {
const handleBitcoinBalanceInvalidation = useInvalidateQueries({
queryKey: userKeys.balance(),
})
const { handleCapture, handleCaptureWithCause } = usePostHogCapture()

const onStakeBTCSuccess = useCallback(() => {
handleBitcoinBalanceInvalidation()
Expand All @@ -52,8 +55,11 @@ export default function DepositBTCModal() {
(transactionHash: string) => {
dispatch(setTxHash(transactionHash))
handleStake()
handleCapture(PostHogEvent.DepositSuccess, {
transactionHash,
})
},
[dispatch, handleStake],
[dispatch, handleStake, handleCapture],
)

const onDepositBTCError = useCallback(
Expand All @@ -63,8 +69,10 @@ export default function DepositBTCModal() {
} else {
onError(error)
}

handleCaptureWithCause(error, PostHogEvent.DepositFailure)
},
[onError, handlePause],
[onError, handlePause, handleCaptureWithCause],
)

const { mutate: sendBitcoinTransaction, status } = useDepositBTCTransaction({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { useInitializeWithdraw } from "#/acre-react/hooks"
import { ONE_SEC_IN_MILLISECONDS, queryKeysFactory } from "#/constants"
import { activityInitialized } from "#/store/wallet"
import { useMutation } from "@tanstack/react-query"
import { PostHogEvent } from "#/posthog/events"
import { usePostHogCapture } from "#/hooks/posthog/usePostHogCapture"
import BuildTransactionModal from "./BuildTransactionModal"
import WalletInteractionModal from "../WalletInteractionModal"

Expand Down Expand Up @@ -48,6 +50,7 @@ export default function SignMessageModal() {
amount,
ACTION_FLOW_TYPES.UNSTAKE,
)
const { handleCapture, handleCaptureWithCause } = usePostHogCapture()

useEffect(() => {
let cancel = (_: Error) => {}
Expand Down Expand Up @@ -78,7 +81,8 @@ export default function SignMessageModal() {
const onSignMessageSuccess = useCallback(() => {
handleBitcoinPositionInvalidation()
dispatch(setStatus(PROCESS_STATUSES.SUCCEEDED))
}, [dispatch, handleBitcoinPositionInvalidation])
handleCapture(PostHogEvent.WithdrawalSuccess)
}, [dispatch, handleBitcoinPositionInvalidation, handleCapture])

const onSignMessageError = useCallback(
(error: unknown) => {
Expand All @@ -97,8 +101,10 @@ export default function SignMessageModal() {
} else {
onSignMessageError(error)
}

handleCaptureWithCause(error, PostHogEvent.WithdrawalFailure)
},
[onSignMessageError, handlePause],
[onSignMessageError, handlePause, handleCaptureWithCause],
)

const { mutate: handleSignMessage } = useMutation({
Expand Down
6 changes: 6 additions & 0 deletions dapp/src/constants/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ const LATEST_COMMIT_HASH = import.meta.env.VITE_LATEST_COMMIT_HASH

const ACRE_API_ENDPOINT = import.meta.env.VITE_ACRE_API_ENDPOINT

const POSTHOG_API_HOST = import.meta.env.VITE_POSTHOG_API_HOST

const POSTHOG_API_KEY = import.meta.env.VITE_POSTHOG_API_KEY

export default {
PROD,
USE_TESTNET,
Expand All @@ -35,4 +39,6 @@ export default {
NETWORK_TYPE,
LATEST_COMMIT_HASH,
ACRE_API_ENDPOINT,
POSTHOG_API_HOST,
POSTHOG_API_KEY,
}
3 changes: 3 additions & 0 deletions dapp/src/constants/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const TVL_ENABLED = import.meta.env.VITE_FEATURE_FLAG_TVL_ENABLED === "true"
const GATING_DAPP_ENABLED =
import.meta.env.VITE_FEATURE_GATING_DAPP_ENABLED === "true"

const POSTHOG_ENABLED = import.meta.env.VITE_FEATURE_POSTHOG_ENABLED === "true"

const MOBILE_MODE_ENABLED =
import.meta.env.VITE_FEATURE_MOBILE_MODE_ENABLED === "true"

Expand All @@ -29,6 +31,7 @@ const featureFlags = {
ACRE_POINTS_ENABLED,
TVL_ENABLED,
GATING_DAPP_ENABLED,
POSTHOG_ENABLED,
MOBILE_MODE_ENABLED,
}

Expand Down
3 changes: 3 additions & 0 deletions dapp/src/hooks/posthog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./usePostHogIdentity"
export * from "./usePostHogCapture"
export * from "./usePostHogPageViewCapture"
40 changes: 40 additions & 0 deletions dapp/src/hooks/posthog/usePostHogCapture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { PostHogEvent } from "#/posthog/events"
import { PostHog, usePostHog } from "posthog-js/react"
import { useCallback } from "react"

type CaptureArgs = [
eventName: PostHogEvent,
...rest: Parameters<PostHog["capture"]> extends [unknown, ...infer R]
? R
: never,
]

export const usePostHogCapture = () => {
const posthog = usePostHog()

const handleCapture = useCallback(
(...captureArgs: CaptureArgs) => {
posthog.capture(...captureArgs)
},
[posthog],
)

const handleCaptureWithCause = useCallback(
(error: unknown, ...captureArgs: CaptureArgs) => {
const [eventName, parameters, ...rest] = captureArgs

const captureParameters =
error instanceof Error
? {
...parameters,
cause: error.message,
}
: undefined

handleCapture(eventName, captureParameters, ...rest)
},
[handleCapture],
)

return { handleCapture, handleCaptureWithCause }
}
27 changes: 27 additions & 0 deletions dapp/src/hooks/posthog/usePostHogIdentity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { PostHog, usePostHog } from "posthog-js/react"
import { useCallback } from "react"
import { sha256, toUtf8Bytes } from "ethers"

type IdentifyArgs = Parameters<PostHog["identify"]>

export const usePostHogIdentity = () => {
const posthog = usePostHog()

const handleIdentification = useCallback(
(...identifyArgs: IdentifyArgs) => {
const [id, ...rest] = identifyArgs
if (!id) return

const hashedId = sha256(toUtf8Bytes(id.toLowerCase())).slice(2, 12)

posthog.identify(hashedId, ...rest)
},
[posthog],
)

const resetIdentity = useCallback(() => {
posthog.reset()
}, [posthog])

return { handleIdentification, resetIdentity }
}
15 changes: 15 additions & 0 deletions dapp/src/hooks/posthog/usePostHogPageViewCapture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { PostHogEvent } from "#/posthog/events"
import { useEffect } from "react"
import { useLocation } from "react-router-dom"
import { usePostHogCapture } from "./usePostHogCapture"

export const usePostHogPageViewCapture = () => {
const { handleCapture } = usePostHogCapture()
const location = useLocation()

useEffect(() => {
handleCapture(PostHogEvent.PageView)
}, [location, handleCapture])

return handleCapture
}
13 changes: 11 additions & 2 deletions dapp/src/hooks/useAcrePoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { useMutation, useQuery } from "@tanstack/react-query"
import { acreApi } from "#/utils"
import { queryKeysFactory, REFETCH_INTERVAL_IN_MILLISECONDS } from "#/constants"
import { MODAL_TYPES } from "#/types"
import { PostHogEvent } from "#/posthog/events"
import { useWallet } from "./useWallet"
import { useModal } from "./useModal"
import { usePostHogCapture } from "./posthog/usePostHogCapture"

const { userKeys, acreKeys } = queryKeysFactory

Expand All @@ -21,6 +23,7 @@ type UseAcrePointsReturnType = {
export default function useAcrePoints(): UseAcrePointsReturnType {
const { ethAddress = "" } = useWallet()
const { openModal } = useModal()
const { handleCapture, handleCaptureWithCause } = usePostHogCapture()

const userPointsDataQuery = useQuery({
queryKey: [...userKeys.pointsData(), ethAddress],
Expand All @@ -47,9 +50,15 @@ export default function useAcrePoints(): UseAcrePointsReturnType {
claimedAmount,
totalAmount,
})

handleCapture(PostHogEvent.PointsClaimSuccess, {
claimedAmount,
totalAmount,
})
},
onError: (error) => {
handleCaptureWithCause(error, PostHogEvent.PointsClaimFailure)
},
// TODO: Add the case when something goes wrong
// onError: (error) => {},
})

return {
Expand Down
Loading
Loading