diff --git a/src/App.vue b/src/App.vue index b58963f..f42de25 100644 --- a/src/App.vue +++ b/src/App.vue @@ -7,13 +7,13 @@ :link-component="RouterLink" :user-consent="userConsent" @update:consent="onUserConsentChange" - :amplitude="{ track }" + :amplitude="amplitudeMock" /> @@ -143,7 +143,7 @@ :curr-project="PROJECTS.BLOG" packageVersion="3.4.5" :link-component="RouterLink" - :amplitude="{ track: () => {} }" + :amplitude="amplitudeMock" :user-consent="userConsent" @update:consent="onUserConsentChange" /> @@ -151,11 +151,19 @@ diff --git a/src/analytics/amplitude.ts b/src/analytics/amplitude.ts new file mode 100644 index 0000000..698c9bb --- /dev/null +++ b/src/analytics/amplitude.ts @@ -0,0 +1,68 @@ +import { AmplitudeLike } from "@/libs/types"; +import { + AnalyticsFooterLinkClickedPartialPayload, + AnalyticsFooterLinkName, + AnalyticsHeaderLinkName, + AnalyticsSubscriptionEventName, + AnalyticsSubscriptionPartialPayload, +} from "./events"; + +export const AmplitudeEvent = { + FOOTER_LINK_CLICKED: 'FOOTER_LINK_CLICKED', + HEADER_LINK_CLICKED: 'HEADER_LINK_CLICKED', + SUBSCRIPTION: 'SUBSCRIPTION', +} + +async function trackEvent( + amplitude: AmplitudeLike, + eventType: string, + eventProperties: Record, +): Promise { + try { + const { promise } = amplitude.track(eventType, eventProperties) + await promise + } catch (err) { + // Silently drop the error + // Maybe consider logging it or something + } +} + +export async function trackHeaderLinkClickedEvent( + amplitude: AmplitudeLike, + linkName: AnalyticsHeaderLinkName, + /** May include more properties than specified in `payload's` type */ + payload: AnalyticsFooterLinkClickedPartialPayload, +): Promise { + await trackEvent(amplitude, AmplitudeEvent.HEADER_LINK_CLICKED, { + linkName, + ...payload, + }) +} + +export async function trackFooterLinkClickedEvent( + amplitude: AmplitudeLike, + linkName: AnalyticsFooterLinkName, + /** May include more properties than specified in the `payload's` type */ + payload: AnalyticsFooterLinkClickedPartialPayload, +): Promise { + await trackEvent(amplitude, AmplitudeEvent.FOOTER_LINK_CLICKED, { + linkName, + ...payload, + }) +} + + +export async function trackSubscriptionEvent( + amplitude: AmplitudeLike, + event: AnalyticsSubscriptionEventName, + /** + * May include more properties than specified in the `payload's` type + * Payload is pretty dynamic, changes a lot with `event` + */ + payload: AnalyticsSubscriptionPartialPayload, +): Promise { + await trackEvent(amplitude, AmplitudeEvent.SUBSCRIPTION, { + event, + ...payload + }) +} diff --git a/src/analytics/events.ts b/src/analytics/events.ts new file mode 100644 index 0000000..8faf6b8 --- /dev/null +++ b/src/analytics/events.ts @@ -0,0 +1,85 @@ + +export const AnalyticsHeaderLinkName = { + LOGO: 'LOGO', + SWAP_TOKENS: 'SWAP_TOKENS', + BUY_CRYPTO: 'BUY_CRYPTO', + NFT: 'NFT', + DAPPS: 'DAPPS', + MEWTOPIA: 'MEWTOPIA', + HELP_CENTER: 'HELP_CENTER', + CUSTOMER_SUPPORT: 'CUSTOMER_SUPPORT', + PRODUCT: 'PRODUCT', + HOME: 'HOME', + + STAKING: 'STAKING', + FAQ: 'FAQ', + ACCESS_WALLET: 'ACCESS_WALLET', + + // extra + CLOSE_MOBILE_MENU: 'CLOSE_MOBILE_MENU', +} as const +export type AnalyticsHeaderLinkName = typeof AnalyticsHeaderLinkName[keyof typeof AnalyticsHeaderLinkName] + +/** Always-present part of the analytics event payload when a header link is clicked */ +export type AnalyticsHeaderLinkClickedPayload = { + // "product" and "network" should be added by consumer in an enrichment + // or by wrapping the "track" function + language: string, + sourceURL: string, + destinationURL: string, +} + +export const AnalyticsFooterLinkName = { + ABOUT_US: 'ABOUT_US', + CAREERS: 'CAREERS', + HOW_IT_WORKS: 'HOW_IT_WORKS', + TEAM: 'TEAM', + ADVERTISE_WITH_US: 'ADVERTISE_WITH_US', + PRIVACY: 'PRIVACY', + TERMS: 'TERMS', + BUG_BOUNTY: 'BUG_BOUNTY', + MEW_MOBILE_APP: 'MEW_MOBILE_APP', + ENKRYPT: 'ENKRYPT', + PORTFOLIO: 'PORTFOLIO', + ETHVM: 'ETHVM', + MEWTOPIA: 'MEWTOPIA', + PRESS_KIT: 'PRESS_KIT', + HELP_CENTER: 'HELP_CENTER', + CUSTOMER_SUPPORT: 'CUSTOMER_SUPPORT', + SECURITY_POLICY: 'SECURITY_POLICY', + VERIFY_MESSAGE: 'VERIFY_MESSAGE', + CONVERT_UNITS: 'CONVERT_UNITS', + SEND_OFFLINE_HELPER: 'SEND_OFFLINE_HELPER', + ETH_DONATION: 'ETH_DONATION', + BTC_DONATION: 'BTC_DONATION', + COINGECKO: 'COINGECKO', + FAQ: 'FAQ', + JOIN_COMMUNITY: 'JOIN_COMMUNITY', + OPEN_MOBILE_MENU: 'OPEN_MOBILE_MENU', +} as const +export type AnalyticsFooterLinkName = typeof AnalyticsFooterLinkName[keyof typeof AnalyticsFooterLinkName] + +/** Always-present part of the analytics event payload when a footer link is clicked */ +export type AnalyticsFooterLinkClickedPartialPayload = { + // "product" and "network" should be added by consumer in an enrichment + // or by wrapping the "track" function + language: string, + sourceURL: string, + destinationURL: string, +} + +export const AnalyticsSubscriptionEventName = { + CLOSE_POPUP: "CLOSE_POPUP", + OPEN_POPUP: "OPEN_POPUP", + CLICK_BUY_CRYPTO_BTN: "CLICK_BUY_CRYPTO_BTN", + CLICK_SIGNUP_BTN: "CLICK_SIGNUP_BTN", + CLICK_FINISH: "CLICK_FINISH", + CREATE_WALLET1: 'CREATE_WALLET1', +} as const +export type AnalyticsSubscriptionEventName = typeof AnalyticsSubscriptionEventName[keyof typeof AnalyticsSubscriptionEventName] + +/** Always-present part of the analytics subscription event payload */ +export type AnalyticsSubscriptionPartialPayload = { + route: string, + language: string, +} diff --git a/src/helpers/amplitudeConfigs.ts b/src/helpers/amplitudeConfigs.ts deleted file mode 100644 index 0f7f044..0000000 --- a/src/helpers/amplitudeConfigs.ts +++ /dev/null @@ -1,51 +0,0 @@ -export default { - headerHome: "LP2HeaderHome", - headerLogo: "LP2HeaderLogo", - headerSwap: "LP2HeaderSwapTokens", - headerBuy: "LP2HeaderBuyCrypto", - headerNft: "LP2HeaderNft", - headerStaking: "LP2HeaderStaking", - headerDapps: "LP2HeaderDapps", - headerMewtopia: "LP2HeaderMewtopia", - headerHelpCenter: "LP2HeaderHelpCenter", - headerFAQ: "LP2HeaderFAQ", - headerCustomerSupport: "LP2HeaderCustomerSupport", - headerProduct: "LP2HeaderProduct", - headerAccessWallet: "LP2HeaderAccessWallet", - accessWallet: "LP2AccessWallet", - createWallet1: "LP2CreateWallet1", - //Footer - footerAboutUs: "LP2FooterAboutUs", - footerCareers: "LP2Footercareers", - footerHowItWorks: "LP2FooterHowItWorks", - footerTeam: "LP2FooterTeam", - footerAdvertiseWithUs: "LP2FooterAdvertiseWithUs", - footerPrivacy: "LP2FooterPrivacy", - footerTerms: "LP2FooterTerms", - footerBugBounty: "LP2FooterBugBounty", - footerMobile: "LP2FooterMewMobileApp", - footerEnkrypt: "LP2FooterEnkrypt", - footerPortfolio: "LP2FooterPortfolio", - footerEthvm: "LP2FooterEthvm", - footerMewtopia: "LP2FooterMewtopia", - footerPressKit: "LP2FooterPressKit", - footerHelpCenter: "LP2FooterHelpCenter", - footerFAQ: "LP2FooterFAQ", - footerCustomerSupport: "LP2FooterCustomerSupport", - footerSecurityPolicy: "LP2FooterSecurityPolicy", - footerVerifyMessage: "LP2FooterVerifyMessage", - footerConvertUnits: "LP2FooterConvertUnits", - footerSendOfflineHelper: "LP2FooterSendOfflineHelper", - footerEthDonation: "LP2FooterEthDonation", - footerBtcDonation: "LP2FooterBtcDonation", - footerCoinGecko: "LP2FooterCoinGecko", - footerJoinMewCommunity: "LP2JoinCommunity", - openMobileMenu: "LP2OpenMobileMenu", - closeMobileMenu: "LP2CloseMobileMenu", - //Subscription - subscriptionClose: "SubscriptionClosePopup", - subscriptionOpen: "SubscriptionOpenPopup", - subscriptionBuyCryptoBtn: "SubscriptionClickBuyCryptoBtn", - subscriptionSignupBtn: "SubscriptionClickSignupBtn", - subscriptionCreateSubscription: "SubscriptionClickFinish", -}; \ No newline at end of file diff --git a/src/libs/layouts/MewFooter.vue b/src/libs/layouts/MewFooter.vue index 4de0798..58909a8 100644 --- a/src/libs/layouts/MewFooter.vue +++ b/src/libs/layouts/MewFooter.vue @@ -284,7 +284,7 @@ href="https://www.facebook.com/MyEtherWallet" target="_blank" :class="linkClass" - @click="trackJoinMewCommunity('facebook')" + @click="trackJoinMewCommunity($event, 'facebook')" > , + type: Object as PropType, }, linkComponent: { type: Object as PropType, @@ -454,10 +455,11 @@ const props = defineProps({ }, useI18n: { required: true, + // type: Function as PropType, type: Function, }, }); -const { t } = props.useI18n({ +const { t, locale } = (props.useI18n as UseI18n)({ messages: { ...messages, }, @@ -471,102 +473,105 @@ const packageVersion = props.packageVersion; /**Amplitude */ const $amplitude = props.amplitude; const route = useRoute(); -const trackAboutUs = () => { - $amplitude.track(amplitudeConfigs.footerAboutUs, { route: route.fullPath }); + +const trackClickEvent = ( + name: AnalyticsFooterLinkName, + destinationURL: string, + extraData?: Record, +): void => { + trackFooterLinkClickedEvent($amplitude, name, { + sourceURL: route.fullPath, // TODO: don't include search & hash? + destinationURL, // TODO: don't include search & hash? + language: locale.value, + ...extraData, + }); }; -const trackCareers = () => { - $amplitude.track(amplitudeConfigs.footerCareers, { route: route.fullPath }); + +const trackAboutUs = (destinationURL: string) => { + trackClickEvent(AnalyticsFooterLinkName.ABOUT_US, destinationURL); }; -const trackHowItWorks = () => { - $amplitude.track(amplitudeConfigs.footerHowItWorks, { - route: route.fullPath, - }); +const trackCareers = (destinationURL: string) => { + trackClickEvent(AnalyticsFooterLinkName.CAREERS, destinationURL); }; -const trackTeam = () => { - $amplitude.track(amplitudeConfigs.footerTeam), { route: route.fullPath }; +const trackHowItWorks = (destinationURL: string) => { + trackClickEvent(AnalyticsFooterLinkName.HOW_IT_WORKS, destinationURL); }; -const trackAdvertiseWithUs = () => { - $amplitude.track(amplitudeConfigs.footerAdvertiseWithUs, { - route: route.fullPath, - }); +const trackTeam = (destinationURL: string) => { + trackClickEvent(AnalyticsFooterLinkName.TEAM, destinationURL); }; -const trackPrivacy = () => { - $amplitude.track(amplitudeConfigs.footerPrivacy, { route: route.fullPath }); +const trackAdvertiseWithUs = (destinationURL: string) => { + trackClickEvent(AnalyticsFooterLinkName.ADVERTISE_WITH_US, destinationURL); }; -const trackTerms = () => { - $amplitude.track(amplitudeConfigs.footerTerms, { route: route.fullPath }); +const trackPrivacy = (destinationURL: string) => { + trackClickEvent(AnalyticsFooterLinkName.PRIVACY, destinationURL); }; -const trackBugBounty = () => { - $amplitude.track(amplitudeConfigs.footerBugBounty, { route: route.fullPath }); +const trackTerms = (destinationURL: string) => { + trackClickEvent(AnalyticsFooterLinkName.TERMS, destinationURL); }; -const trackMobile = () => { - $amplitude.track(amplitudeConfigs.footerMobile, { route: route.fullPath }); +const trackBugBounty = (destinationURL: string) => { + trackClickEvent(AnalyticsFooterLinkName.BUG_BOUNTY, destinationURL); }; -const trackEnkrypt = () => { - $amplitude.track(amplitudeConfigs.footerEnkrypt, { route: route.fullPath }); +const trackMobile = (evt: MouseEvent) => { + const destinationURL = (evt.target as HTMLAnchorElement)?.href; + trackClickEvent(AnalyticsFooterLinkName.MEW_MOBILE_APP, destinationURL); }; -const trackPortfolio = () => { - $amplitude.track(amplitudeConfigs.footerPortfolio, { route: route.fullPath }); +const trackEnkrypt = (evt: MouseEvent) => { + const destinationURL = (evt.target as HTMLAnchorElement)?.href; + trackClickEvent(AnalyticsFooterLinkName.ENKRYPT, destinationURL); }; -const trackEthvm = () => { - $amplitude.track(amplitudeConfigs.footerEthvm, { route: route.fullPath }); +const trackPortfolio = (destinationURL: string) => { + trackClickEvent(AnalyticsFooterLinkName.PORTFOLIO, destinationURL); }; -const trackMewtopia = () => { - $amplitude.track(amplitudeConfigs.footerMewtopia, { route: route.fullPath }); +const trackEthvm = (evt: MouseEvent) => { + const destinationURL = (evt.target as HTMLAnchorElement)?.href; + trackClickEvent(AnalyticsFooterLinkName.ETHVM, destinationURL); }; -const trackPressKit = () => { - $amplitude.track(amplitudeConfigs.footerPressKit, { route: route.fullPath }); +const trackMewtopia = (evt: MouseEvent) => { + const destinationURL = (evt.target as HTMLAnchorElement)?.href; + trackClickEvent(AnalyticsFooterLinkName.MEWTOPIA, destinationURL); }; -const trackHelpCenter = () => { - $amplitude.track(amplitudeConfigs.footerHelpCenter, { - route: route.fullPath, - }); +const trackPressKit = (destinationURL: string) => { + trackClickEvent(AnalyticsFooterLinkName.PRESS_KIT, destinationURL); }; -const trackFAQ = () => { - $amplitude.track(amplitudeConfigs.footerFAQ, { route: route.fullPath }); +const trackHelpCenter = (evt: MouseEvent) => { + const destinationURL = (evt.target as HTMLAnchorElement)?.href; + trackClickEvent(AnalyticsFooterLinkName.HELP_CENTER, destinationURL); }; -const trackCustomerSupport = () => { - $amplitude.track(amplitudeConfigs.footerCustomerSupport, { - route: route.fullPath, - }); +const trackFAQ = (destinationURL: string) => { + trackClickEvent(AnalyticsFooterLinkName.FAQ, destinationURL); }; -const trackSecurityPolicy = () => { - $amplitude.track(amplitudeConfigs.footerSecurityPolicy, { - route: route.fullPath, - }); +const trackCustomerSupport = (evt: MouseEvent) => { + const destinationURL = (evt.target as HTMLAnchorElement)?.href; + trackClickEvent(AnalyticsFooterLinkName.CUSTOMER_SUPPORT, destinationURL); }; -const trackVerifyMessage = () => { - $amplitude.track(amplitudeConfigs.footerVerifyMessage, { - route: route.fullPath, - }); +const trackSecurityPolicy = (destinationURL: string) => { + trackClickEvent(AnalyticsFooterLinkName.SECURITY_POLICY, destinationURL); }; -const trackConvertUnits = () => { - $amplitude.track(amplitudeConfigs.footerConvertUnits, { - route: route.fullPath, - }); +const trackVerifyMessage = (destinationURL: string) => { + trackClickEvent(AnalyticsFooterLinkName.VERIFY_MESSAGE, destinationURL); }; -const trackSendOfflineHelper = () => { - $amplitude.track(amplitudeConfigs.footerSendOfflineHelper, { - route: route.fullPath, - }); +const trackConvertUnits = (destinationURL: string) => { + trackClickEvent(AnalyticsFooterLinkName.CONVERT_UNITS, destinationURL); }; -const trackEthDonation = () => { - $amplitude.track(amplitudeConfigs.footerEthDonation, { - route: route.fullPath, - }); +const trackSendOfflineHelper = (destinationURL: string) => { + trackClickEvent(AnalyticsFooterLinkName.SEND_OFFLINE_HELPER, destinationURL); }; -const trackBtcDonation = () => { - $amplitude.track(amplitudeConfigs.footerBtcDonation, { - route: route.fullPath, - }); +const trackEthDonation = (evt: MouseEvent) => { + const destinationURL = (evt.target as HTMLAnchorElement)?.href; + trackClickEvent(AnalyticsFooterLinkName.ETH_DONATION, destinationURL); +}; +const trackBtcDonation = (evt: MouseEvent) => { + const destinationURL = (evt.target as HTMLAnchorElement)?.href; + trackClickEvent(AnalyticsFooterLinkName.BTC_DONATION, destinationURL); }; -const trackCoinGecko = () => { - $amplitude.track(amplitudeConfigs.footerCoinGecko, { route: route.fullPath }); +const trackCoinGecko = (evt: MouseEvent) => { + const destinationURL = (evt.target as HTMLAnchorElement)?.href; + trackClickEvent(AnalyticsFooterLinkName.COINGECKO, destinationURL); }; -const trackJoinMewCommunity = (item: string) => { - $amplitude.track(amplitudeConfigs.footerJoinMewCommunity, { - item: item, - route: route.fullPath, +const trackJoinMewCommunity = (evt: MouseEvent, community: string) => { + const destinationURL = (evt.target as HTMLAnchorElement)?.href; + trackClickEvent(AnalyticsFooterLinkName.JOIN_COMMUNITY, destinationURL, { + community, }); }; diff --git a/src/libs/layouts/MewHeader.vue b/src/libs/layouts/MewHeader.vue index 06004ce..4b51a88 100644 --- a/src/libs/layouts/MewHeader.vue +++ b/src/libs/layouts/MewHeader.vue @@ -152,7 +152,7 @@ href="https://www.mewwallet.com/" target="_blank" class="flex items-start p-2" - @click="trackProduct({ item: 'MobileApp' })" + @click="trackProductAnchor($event, 'MobileApp')" >
, + type: Object as PropType, }, linkComponent: { type: Object as PropType, @@ -385,50 +389,67 @@ const route = useRoute(); const ampUrl = computed(() => { return props.currUrl ? props.currUrl : route.fullPath || ""; }); -const trackLogo = () => { - $amplitude.track(amplitudeConfigs.headerLogo, { route: ampUrl.value }); + +const trackClickEvent = ( + linkName: AnalyticsHeaderLinkName, + destinationURL: string, + extraData?: Record, +): void => { + trackHeaderLinkClickedEvent($amplitude, linkName, { + sourceURL: route.fullPath, // TODO: don't include search & hash? + destinationURL, // TODO: don't include search & hash? + language: locale.value, + ...extraData, + }); +}; + +const trackLogo = (destinationURL: string) => { + trackClickEvent(AnalyticsHeaderLinkName.LOGO, destinationURL); }; -const trackSwap = () => { - $amplitude.track(amplitudeConfigs.headerSwap, { route: ampUrl.value }); +const trackSwap = (destinationURL: string) => { + trackClickEvent(AnalyticsHeaderLinkName.SWAP_TOKENS, destinationURL); }; -const trackBuy = () => { - $amplitude.track(amplitudeConfigs.headerBuy, { route: ampUrl.value }); +const trackBuy = (evt: MouseEvent) => { + const destinationURL = (evt.target as HTMLAnchorElement)?.href; + trackClickEvent(AnalyticsHeaderLinkName.BUY_CRYPTO, destinationURL); }; -const trackNft = () => { - $amplitude.track(amplitudeConfigs.headerNft, { route: ampUrl.value }); +const trackNft = (destinationURL: string) => { + trackClickEvent(AnalyticsHeaderLinkName.NFT, destinationURL); }; -const trackDapps = () => { - $amplitude.track(amplitudeConfigs.headerDapps, { route: ampUrl.value }); +const trackDapps = (destinationURL: string) => { + trackClickEvent(AnalyticsHeaderLinkName.DAPPS, destinationURL); }; -const trackMewtopia = () => { - $amplitude.track(amplitudeConfigs.headerMewtopia, { route: ampUrl.value }); +const trackMewtopia = (evt: MouseEvent) => { + const destinationURL = (evt.target as HTMLAnchorElement)?.href; + trackClickEvent(AnalyticsHeaderLinkName.MEWTOPIA, destinationURL); }; -const trackHelpCenter = () => { - $amplitude.track(amplitudeConfigs.headerHelpCenter, { - route: ampUrl.value, - }); +const trackHelpCenter = (evt: MouseEvent) => { + const destinationURL = (evt.target as HTMLAnchorElement)?.href; + trackClickEvent(AnalyticsHeaderLinkName.HELP_CENTER, destinationURL); }; -const trackCustomerSupport = () => { - $amplitude.track(amplitudeConfigs.headerCustomerSupport, { - route: ampUrl.value, - }); +const trackCustomerSupport = (evt: MouseEvent) => { + const destinationURL = (evt.target as HTMLAnchorElement)?.href; + trackClickEvent(AnalyticsHeaderLinkName.CUSTOMER_SUPPORT, destinationURL); +}; +const trackAccessWallet = (destinationURL: string) => { + trackClickEvent(AnalyticsHeaderLinkName.ACCESS_WALLET, destinationURL); }; -const trackAccessWallet = () => { - $amplitude.track(amplitudeConfigs.headerAccessWallet, { - route: ampUrl.value, +const trackProduct = (destinationURL: string, targetProduct: string) => { + trackClickEvent(AnalyticsHeaderLinkName.PRODUCT, destinationURL, { + targetProduct, }); }; -const trackProduct = (obj: itemType) => { - $amplitude.track(amplitudeConfigs.headerProduct, { - ...obj, - route: ampUrl.value, +const trackProductAnchor = (evt: MouseEvent, destinationProduct: string) => { + const destinationURL = (evt.target as HTMLAnchorElement)?.href; + trackClickEvent(AnalyticsHeaderLinkName.PRODUCT, destinationURL, { + destinationProduct, }); }; -const trackFAQ = () => { - $amplitude.track(amplitudeConfigs.headerFAQ, { route: ampUrl.value }); +const trackFAQ = (destinationURL: string) => { + trackClickEvent(AnalyticsHeaderLinkName.FAQ, destinationURL); }; -const trackStaking = () => { - $amplitude.track(amplitudeConfigs.headerStaking, { route: ampUrl.value }); +const trackStaking = (destinationURL: string) => { + trackClickEvent(AnalyticsHeaderLinkName.STAKING, destinationURL); }; /** @@ -436,9 +457,10 @@ const trackStaking = () => { */ const isOpenMobileMenu = ref(false); -const openMobileMenu = () => { +const openMobileMenu = (evt: MouseEvent) => { + const destinationURL = (evt.target as HTMLAnchorElement)?.href; + trackClickEvent(AnalyticsHeaderLinkName.CLOSE_MOBILE_MENU, destinationURL); isOpenMobileMenu.value = true; - $amplitude.track(amplitudeConfigs.openMobileMenu, { route: ampUrl.value }); };