mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
add mixpanel events
This commit is contained in:
+26
-4
@@ -6,11 +6,13 @@ export enum EventTypes {
|
||||
SEARCH_QUERY = 'Search query',
|
||||
LOCAL_SEARCH = 'Local search',
|
||||
ADD_TO_WALLET = 'Add to wallet',
|
||||
ACCOUNT_ACCESS = 'Account access', // deprecated
|
||||
ACCOUNT_ACCESS = 'Account access',
|
||||
LOGIN = 'Login',
|
||||
ACCOUNT_LINK_INFO = 'Account link info',
|
||||
PRIVATE_TAG = 'Private tag',
|
||||
VERIFY_ADDRESS = 'Verify address',
|
||||
VERIFY_TOKEN = 'Verify token',
|
||||
WALLET_CONNECT = 'Wallet connect', // 🦆
|
||||
WALLET_CONNECT = 'Wallet connect',
|
||||
WALLET_ACTION = 'Wallet action',
|
||||
CONTRACT_INTERACTION = 'Contract interaction',
|
||||
CONTRACT_VERIFICATION = 'Contract verification',
|
||||
@@ -54,7 +56,27 @@ Type extends EventTypes.ADD_TO_WALLET ? (
|
||||
}
|
||||
) :
|
||||
Type extends EventTypes.ACCOUNT_ACCESS ? {
|
||||
'Action': 'Logged out';
|
||||
'Action': 'Dropdown open' | 'Logged out';
|
||||
} :
|
||||
Type extends EventTypes.LOGIN ? (
|
||||
{
|
||||
'Action': 'Started';
|
||||
'Source': string;
|
||||
} | {
|
||||
'Action': 'Wallet' | 'Email';
|
||||
'Source': 'Options selector';
|
||||
} | {
|
||||
'Action': 'OTP sent';
|
||||
'Source': 'Email';
|
||||
} | {
|
||||
'Action': 'Success';
|
||||
'Source': 'Email' | 'Wallet';
|
||||
}
|
||||
) :
|
||||
Type extends EventTypes.ACCOUNT_LINK_INFO ? {
|
||||
'Source': 'Profile' | 'Login modal' | 'Profile dropdown';
|
||||
'Status': 'Started' | 'OTP sent' | 'Finished';
|
||||
'Type': 'Email' | 'Wallet';
|
||||
} :
|
||||
Type extends EventTypes.PRIVATE_TAG ? {
|
||||
'Action': 'Form opened' | 'Submit';
|
||||
@@ -75,7 +97,7 @@ Type extends EventTypes.VERIFY_TOKEN ? {
|
||||
'Action': 'Form opened' | 'Submit';
|
||||
} :
|
||||
Type extends EventTypes.WALLET_CONNECT ? {
|
||||
'Source': 'Header' | 'Smart contracts' | 'Swap button';
|
||||
'Source': 'Header' | 'Login' | 'Profile' | 'Profile dropdown' | 'Smart contracts' | 'Swap button';
|
||||
'Status': 'Started' | 'Connected';
|
||||
} :
|
||||
Type extends EventTypes.WALLET_ACTION ? (
|
||||
|
||||
+13
-2
@@ -13,6 +13,15 @@ import AuthModal from 'ui/snippets/auth/AuthModal';
|
||||
import useProfileQuery from 'ui/snippets/auth/useProfileQuery';
|
||||
import useRedirectForInvalidAuthToken from 'ui/snippets/auth/useRedirectForInvalidAuthToken';
|
||||
|
||||
const MIXPANEL_CONFIG = {
|
||||
wallet_connect: {
|
||||
source: 'Profile' as const,
|
||||
},
|
||||
account_link_info: {
|
||||
source: 'Profile' as const,
|
||||
},
|
||||
};
|
||||
|
||||
const MyProfile = () => {
|
||||
const [ authInitialScreen, setAuthInitialScreen ] = React.useState<Screen>();
|
||||
const authModal = useDisclosure();
|
||||
@@ -38,9 +47,11 @@ const MyProfile = () => {
|
||||
<>
|
||||
<Flex maxW="480px" mt={ 8 } flexDir="column" rowGap={ 12 }>
|
||||
<MyProfileEmail profileQuery={ profileQuery }/>
|
||||
{ config.features.blockchainInteraction.isEnabled && <MyProfileWallet profileQuery={ profileQuery } onAddWallet={ handleAddWalletClick }/> }
|
||||
{ config.features.blockchainInteraction.isEnabled &&
|
||||
<MyProfileWallet profileQuery={ profileQuery } onAddWallet={ handleAddWalletClick }/> }
|
||||
</Flex>
|
||||
{ authModal.isOpen && authInitialScreen && <AuthModal initialScreen={ authInitialScreen } onClose={ authModal.onClose }/> }
|
||||
{ authModal.isOpen && authInitialScreen &&
|
||||
<AuthModal initialScreen={ authInitialScreen } onClose={ authModal.onClose } mixpanelConfig={ MIXPANEL_CONFIG }/> }
|
||||
</>
|
||||
);
|
||||
})();
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
import type { Screen, ScreenSuccess } from './types';
|
||||
|
||||
import * as mixpanel from 'lib/mixpanel';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
import AuthModalScreenConnectWallet from './screens/AuthModalScreenConnectWallet';
|
||||
@@ -16,13 +18,38 @@ import useProfileQuery from './useProfileQuery';
|
||||
interface Props {
|
||||
initialScreen: Screen;
|
||||
onClose: (isSuccess?: boolean) => void;
|
||||
mixpanelConfig?: {
|
||||
'wallet_connect': {
|
||||
source: mixpanel.EventPayload<mixpanel.EventTypes.WALLET_CONNECT>['Source'];
|
||||
};
|
||||
'account_link_info': {
|
||||
source: mixpanel.EventPayload<mixpanel.EventTypes.ACCOUNT_LINK_INFO>['Source'];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const AuthModal = ({ initialScreen, onClose }: Props) => {
|
||||
const AuthModal = ({ initialScreen, onClose, mixpanelConfig }: Props) => {
|
||||
const [ steps, setSteps ] = React.useState<Array<Screen>>([ initialScreen ]);
|
||||
const [ isSuccess, setIsSuccess ] = React.useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const profileQuery = useProfileQuery();
|
||||
|
||||
React.useEffect(() => {
|
||||
if ('isAuth' in initialScreen && initialScreen.isAuth) {
|
||||
mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_LINK_INFO, {
|
||||
Status: 'Started',
|
||||
Type: initialScreen.type === 'connect_wallet' ? 'Wallet' : 'Email',
|
||||
Source: mixpanelConfig?.account_link_info.source ?? 'Profile dropdown',
|
||||
});
|
||||
} else {
|
||||
mixpanel.logEvent(mixpanel.EventTypes.LOGIN, {
|
||||
Action: 'Started',
|
||||
Source: mixpanel.getPageType(router.pathname),
|
||||
});
|
||||
}
|
||||
}, [ initialScreen, mixpanelConfig, router.pathname ]);
|
||||
|
||||
const onNextStep = React.useCallback((screen: Screen) => {
|
||||
setSteps((prev) => [ ...prev, screen ]);
|
||||
}, []);
|
||||
@@ -37,12 +64,26 @@ const AuthModal = ({ initialScreen, onClose }: Props) => {
|
||||
|
||||
const onAuthSuccess = React.useCallback(async(screen: ScreenSuccess) => {
|
||||
setIsSuccess(true);
|
||||
|
||||
if ('isAuth' in initialScreen && initialScreen.isAuth) {
|
||||
mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_LINK_INFO, {
|
||||
Status: 'Finished',
|
||||
Type: screen.type === 'success_wallet' ? 'Wallet' : 'Email',
|
||||
Source: mixpanelConfig?.account_link_info.source ?? 'Profile dropdown',
|
||||
});
|
||||
} else {
|
||||
mixpanel.logEvent(mixpanel.EventTypes.LOGIN, {
|
||||
Action: 'Success',
|
||||
Source: screen.type === 'success_wallet' ? 'Wallet' : 'Email',
|
||||
});
|
||||
}
|
||||
|
||||
const { data } = await profileQuery.refetch();
|
||||
if (data) {
|
||||
onNextStep({ ...screen, profile: data });
|
||||
}
|
||||
// TODO @tom2drum handle error case
|
||||
}, [ onNextStep, profileQuery ]);
|
||||
}, [ initialScreen, mixpanelConfig?.account_link_info.source, onNextStep, profileQuery ]);
|
||||
|
||||
const onModalClose = React.useCallback(() => {
|
||||
onClose(isSuccess);
|
||||
@@ -71,9 +112,22 @@ const AuthModal = ({ initialScreen, onClose }: Props) => {
|
||||
case 'select_method':
|
||||
return <AuthModalScreenSelectMethod onSelectMethod={ onNextStep }/>;
|
||||
case 'connect_wallet':
|
||||
return <AuthModalScreenConnectWallet onSuccess={ onAuthSuccess } onError={ onReset } isAuth={ currentStep.isAuth }/>;
|
||||
return (
|
||||
<AuthModalScreenConnectWallet
|
||||
onSuccess={ onAuthSuccess }
|
||||
onError={ onReset }
|
||||
isAuth={ currentStep.isAuth }
|
||||
source={ mixpanelConfig?.wallet_connect.source }
|
||||
/>
|
||||
);
|
||||
case 'email':
|
||||
return <AuthModalScreenEmail onSubmit={ onNextStep } isAuth={ currentStep.isAuth }/>;
|
||||
return (
|
||||
<AuthModalScreenEmail
|
||||
onSubmit={ onNextStep }
|
||||
isAuth={ currentStep.isAuth }
|
||||
mixpanelConfig={ mixpanelConfig }
|
||||
/>
|
||||
);
|
||||
case 'otp_code':
|
||||
return <AuthModalScreenOtpCode email={ currentStep.email } onSuccess={ onAuthSuccess } isAuth={ currentStep.isAuth }/>;
|
||||
case 'success_email':
|
||||
|
||||
@@ -3,15 +3,18 @@ import React from 'react';
|
||||
|
||||
import type { ScreenSuccess } from '../types';
|
||||
|
||||
import type * as mixpanel from 'lib/mixpanel';
|
||||
|
||||
import useSignInWithWallet from '../useSignInWithWallet';
|
||||
|
||||
interface Props {
|
||||
onSuccess: (screen: ScreenSuccess) => void;
|
||||
onError: (isAuth?: boolean) => void;
|
||||
isAuth?: boolean;
|
||||
source?: mixpanel.EventPayload<mixpanel.EventTypes.WALLET_CONNECT>['Source'];
|
||||
}
|
||||
|
||||
const AuthModalScreenConnectWallet = ({ onSuccess, onError, isAuth }: Props) => {
|
||||
const AuthModalScreenConnectWallet = ({ onSuccess, onError, isAuth, source }: Props) => {
|
||||
const isStartedRef = React.useRef(false);
|
||||
|
||||
const handleSignInSuccess = React.useCallback(({ address }: { address: string }) => {
|
||||
@@ -22,7 +25,7 @@ const AuthModalScreenConnectWallet = ({ onSuccess, onError, isAuth }: Props) =>
|
||||
onError(isAuth);
|
||||
}, [ onError, isAuth ]);
|
||||
|
||||
const { start } = useSignInWithWallet({ onSuccess: handleSignInSuccess, onError: handleSignInError });
|
||||
const { start } = useSignInWithWallet({ onSuccess: handleSignInSuccess, onError: handleSignInError, source });
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isStartedRef.current) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { EmailFormFields, Screen } from '../types';
|
||||
import useApiFetch from 'lib/api/useApiFetch';
|
||||
import getErrorMessage from 'lib/errors/getErrorMessage';
|
||||
import useToast from 'lib/hooks/useToast';
|
||||
import * as mixpanel from 'lib/mixpanel';
|
||||
import FormFieldReCaptcha from 'ui/shared/forms/fields/FormFieldReCaptcha';
|
||||
|
||||
import AuthModalFieldEmail from '../fields/AuthModalFieldEmail';
|
||||
@@ -15,9 +16,14 @@ import AuthModalFieldEmail from '../fields/AuthModalFieldEmail';
|
||||
interface Props {
|
||||
onSubmit: (screen: Screen) => void;
|
||||
isAuth?: boolean;
|
||||
mixpanelConfig?: {
|
||||
account_link_info: {
|
||||
source: mixpanel.EventPayload<mixpanel.EventTypes.ACCOUNT_LINK_INFO>['Source'];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const AuthModalScreenEmail = ({ onSubmit, isAuth }: Props) => {
|
||||
const AuthModalScreenEmail = ({ onSubmit, isAuth, mixpanelConfig }: Props) => {
|
||||
|
||||
const apiFetch = useApiFetch();
|
||||
const toast = useToast();
|
||||
@@ -40,6 +46,18 @@ const AuthModalScreenEmail = ({ onSubmit, isAuth }: Props) => {
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
if (isAuth) {
|
||||
mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_LINK_INFO, {
|
||||
Source: mixpanelConfig?.account_link_info.source ?? 'Profile dropdown',
|
||||
Status: 'OTP sent',
|
||||
Type: 'Email',
|
||||
});
|
||||
} else {
|
||||
mixpanel.logEvent(mixpanel.EventTypes.LOGIN, {
|
||||
Action: 'OTP sent',
|
||||
Source: 'Email',
|
||||
});
|
||||
}
|
||||
onSubmit({ type: 'otp_code', email: formData.email, isAuth });
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -49,7 +67,7 @@ const AuthModalScreenEmail = ({ onSubmit, isAuth }: Props) => {
|
||||
description: getErrorMessage(error) || 'Something went wrong',
|
||||
});
|
||||
});
|
||||
}, [ apiFetch, onSubmit, toast, isAuth ]);
|
||||
}, [ apiFetch, isAuth, onSubmit, mixpanelConfig?.account_link_info.source, toast ]);
|
||||
|
||||
return (
|
||||
<FormProvider { ...formApi }>
|
||||
|
||||
@@ -3,6 +3,8 @@ import React from 'react';
|
||||
|
||||
import type { Screen } from '../types';
|
||||
|
||||
import * as mixpanel from 'lib/mixpanel';
|
||||
|
||||
interface Props {
|
||||
onSelectMethod: (screen: Screen) => void;
|
||||
}
|
||||
@@ -10,10 +12,18 @@ interface Props {
|
||||
const AuthModalScreenSelectMethod = ({ onSelectMethod }: Props) => {
|
||||
|
||||
const handleEmailClick = React.useCallback(() => {
|
||||
mixpanel.logEvent(mixpanel.EventTypes.LOGIN, {
|
||||
Action: 'Email',
|
||||
Source: 'Options selector',
|
||||
});
|
||||
onSelectMethod({ type: 'email' });
|
||||
}, [ onSelectMethod ]);
|
||||
|
||||
const handleConnectWalletClick = React.useCallback(() => {
|
||||
mixpanel.logEvent(mixpanel.EventTypes.LOGIN, {
|
||||
Action: 'Wallet',
|
||||
Source: 'Options selector',
|
||||
});
|
||||
onSelectMethod({ type: 'connect_wallet' });
|
||||
}, [ onSelectMethod ]);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { Route } from 'nextjs-routes';
|
||||
|
||||
import { getResourceKey } from 'lib/api/useApiQuery';
|
||||
import * as cookies from 'lib/cookies';
|
||||
import * as mixpanel from 'lib/mixpanel';
|
||||
|
||||
const PROTECTED_ROUTES: Array<Route['pathname']> = [
|
||||
'/account/api-key',
|
||||
@@ -28,6 +29,8 @@ export default function useLogout() {
|
||||
exact: true,
|
||||
});
|
||||
|
||||
mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_ACCESS, { Action: 'Logged out' }, { send_immediately: true });
|
||||
|
||||
if (
|
||||
PROTECTED_ROUTES.includes(router.pathname) ||
|
||||
(router.pathname === '/txs' && router.query.tab === 'watchlist')
|
||||
|
||||
@@ -6,14 +6,16 @@ import config from 'configs/app';
|
||||
import useApiFetch from 'lib/api/useApiFetch';
|
||||
import getErrorMessage from 'lib/errors/getErrorMessage';
|
||||
import useToast from 'lib/hooks/useToast';
|
||||
import * as mixpanel from 'lib/mixpanel';
|
||||
import useAccount from 'lib/web3/useAccount';
|
||||
|
||||
interface Props {
|
||||
onSuccess?: ({ address }: { address: string }) => void;
|
||||
onError?: () => void;
|
||||
source?: mixpanel.EventPayload<mixpanel.EventTypes.WALLET_CONNECT>['Source'];
|
||||
}
|
||||
|
||||
function useSignInWithWallet({ onSuccess, onError }: Props) {
|
||||
function useSignInWithWallet({ onSuccess, onError, source = 'Login' }: Props) {
|
||||
const [ isPending, setIsPending ] = React.useState(false);
|
||||
const isConnectingWalletRef = React.useRef(false);
|
||||
|
||||
@@ -54,15 +56,17 @@ function useSignInWithWallet({ onSuccess, onError }: Props) {
|
||||
} else {
|
||||
isConnectingWalletRef.current = true;
|
||||
web3Modal.open();
|
||||
mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: source, Status: 'Started' });
|
||||
}
|
||||
}, [ address, proceedToAuth, web3Modal ]);
|
||||
}, [ address, proceedToAuth, source, web3Modal ]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (address && isConnectingWalletRef.current) {
|
||||
isConnectingWalletRef.current = false;
|
||||
proceedToAuth(address);
|
||||
mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: source, Status: 'Connected' });
|
||||
}
|
||||
}, [ address, isConnected, proceedToAuth ]);
|
||||
}, [ address, isConnected, proceedToAuth, source ]);
|
||||
|
||||
return React.useMemo(() => ({ start, isPending }), [ start, isPending ]);
|
||||
}
|
||||
|
||||
@@ -63,16 +63,12 @@ const UserProfileButton = ({ profileQuery, size, variant, onClick, isPending }:
|
||||
);
|
||||
}
|
||||
|
||||
if (data.email) {
|
||||
return (
|
||||
<HStack gap={ 2 }>
|
||||
<IconSvg name="profile" boxSize={ 5 }/>
|
||||
<Box display={{ base: 'none', md: 'block' }}>{ getUserHandle(data.email) }</Box>
|
||||
</HStack>
|
||||
);
|
||||
}
|
||||
|
||||
return 'Connected';
|
||||
return (
|
||||
<HStack gap={ 2 }>
|
||||
<IconSvg name="profile" boxSize={ 5 }/>
|
||||
<Box display={{ base: 'none', md: 'block' }}>{ data.email ? getUserHandle(data.email) : 'Profile' }</Box>
|
||||
</HStack>
|
||||
);
|
||||
})();
|
||||
|
||||
return (
|
||||
|
||||
@@ -12,7 +12,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const UserProfileContentWallet = ({ onClose }: Props) => {
|
||||
const web3Wallet = useWeb3Wallet({ source: 'Header' });
|
||||
const web3Wallet = useWeb3Wallet({ source: 'Profile dropdown' });
|
||||
|
||||
const web3AccountWithDomain = useWeb3AccountWithDomain(true);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import React from 'react';
|
||||
import type { Screen } from 'ui/snippets/auth/types';
|
||||
|
||||
import config from 'configs/app';
|
||||
import * as mixpanel from 'lib/mixpanel';
|
||||
import Popover from 'ui/shared/chakra/Popover';
|
||||
import AuthModal from 'ui/snippets/auth/AuthModal';
|
||||
import useProfileQuery from 'ui/snippets/auth/useProfileQuery';
|
||||
@@ -32,6 +33,7 @@ const UserProfileDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) =>
|
||||
|
||||
const handleProfileButtonClick = React.useCallback(() => {
|
||||
if (profileQuery.data) {
|
||||
mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_ACCESS, { Action: 'Dropdown open' });
|
||||
profileMenu.onOpen();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import React from 'react';
|
||||
import type { Screen } from 'ui/snippets/auth/types';
|
||||
|
||||
import config from 'configs/app';
|
||||
import * as mixpanel from 'lib/mixpanel';
|
||||
import AuthModal from 'ui/snippets/auth/AuthModal';
|
||||
import useProfileQuery from 'ui/snippets/auth/useProfileQuery';
|
||||
import useSignInWithWallet from 'ui/snippets/auth/useSignInWithWallet';
|
||||
@@ -26,6 +27,7 @@ const UserProfileMobile = () => {
|
||||
|
||||
const handleProfileButtonClick = React.useCallback(() => {
|
||||
if (profileQuery.data) {
|
||||
mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_ACCESS, { Action: 'Dropdown open' });
|
||||
profileMenu.onOpen();
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user