diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ba5b99a2..c85c6bc0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Improvements +- [#532](https://github.com/alleslabs/celatone-frontend/pull/532) Implement new Amplitude structure - [#538](https://github.com/alleslabs/celatone-frontend/pull/538) Add empty state in query and execute with json schema - [#537](https://github.com/alleslabs/celatone-frontend/pull/537) Change json schema select widget null option wording for readability - [#535](https://github.com/alleslabs/celatone-frontend/pull/535) Improve json schema features diff --git a/package.json b/package.json index 4094740c9..063a62df2 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ }, "dependencies": { "@amplitude/analytics-browser": "^1.8.0", + "@amplitude/marketing-analytics-browser": "^1.0.7", "@chain-registry/types": "^0.13.1", "@chakra-ui/anatomy": "^2.1.0", "@chakra-ui/card": "^2.1.1", diff --git a/src/lib/amplitude/index.ts b/src/lib/amplitude/index.ts new file mode 100644 index 000000000..815ea2ff3 --- /dev/null +++ b/src/lib/amplitude/index.ts @@ -0,0 +1,3 @@ +export * from "./track-event"; +export * from "./useAmplitudeInit"; +export * from "./types"; diff --git a/src/lib/amplitude/track-event/index.ts b/src/lib/amplitude/track-event/index.ts new file mode 100644 index 000000000..fbbcc3792 --- /dev/null +++ b/src/lib/amplitude/track-event/index.ts @@ -0,0 +1,47 @@ +import { track as amplitudeTrack } from "@amplitude/analytics-browser"; +import { useCallback } from "react"; + +import type { AmpEvent, ActionAmpEvent, SpecialAmpEvent } from "../types"; + +import { useMandatoryProperties } from "./useMandatoryProperties"; +import { useTrackAction } from "./useTrackAction"; +import { useTrackComponent } from "./useTrackComponent"; +import { useTrackExternal } from "./useTrackExternal"; +import { useTrackInteraction } from "./useTrackInteraction"; +import { useTrackToPage } from "./useTrackToPage"; +import { useTrackTx } from "./useTrackTx"; + +export const useTrack = () => { + const mandatoryProperties = useMandatoryProperties(); + + const trackAction = useTrackAction(); + const trackComponent = useTrackComponent(); + const trackExternal = useTrackExternal(); + const trackInteraction = useTrackInteraction(); + const trackToPage = useTrackToPage(); + const trackTx = useTrackTx(); + + const track = useCallback( + ( + event: Exclude, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + properties?: Record + ) => { + amplitudeTrack(event, { + ...mandatoryProperties, + ...properties, + }); + }, + [mandatoryProperties] + ); + + return { + track, + ...trackAction, + ...trackComponent, + ...trackExternal, + ...trackInteraction, + ...trackToPage, + ...trackTx, + }; +}; diff --git a/src/lib/amplitude/track-event/useMandatoryProperties.ts b/src/lib/amplitude/track-event/useMandatoryProperties.ts new file mode 100644 index 000000000..8a8e2e907 --- /dev/null +++ b/src/lib/amplitude/track-event/useMandatoryProperties.ts @@ -0,0 +1,35 @@ +import { fromBech32 } from "@cosmjs/encoding"; +import { useRouter } from "next/router"; +import { useMemo } from "react"; + +import { useCelatoneApp, useNavContext } from "lib/app-provider/contexts"; +import { useCurrentChain, useMobile } from "lib/app-provider/hooks"; +import { StorageKeys } from "lib/data"; +import { sha256Hex, getItem } from "lib/utils"; + +export const useMandatoryProperties = () => { + const { currentChainId } = useCelatoneApp(); + const { prevPathname } = useNavContext(); + const { address } = useCurrentChain(); + const isMobile = useMobile(); + const router = useRouter(); + + // TODO: make utility function + const rawAddressHash = address + ? sha256Hex(fromBech32(address).data) + : "Not Connected"; + + return useMemo( + () => ({ + page: router.pathname.replace("/[network]", ""), + prevPathname, + rawAddressHash, + chain: currentChainId, + mobile: isMobile, + navSidebar: getItem(StorageKeys.NavSidebar, ""), + devSidebar: getItem(StorageKeys.DevSidebar, ""), + projectSidebar: getItem(StorageKeys.ProjectSidebar, ""), + }), + [currentChainId, isMobile, prevPathname, router.pathname, rawAddressHash] + ); +}; diff --git a/src/lib/amplitude/track-event/useTrackAction.ts b/src/lib/amplitude/track-event/useTrackAction.ts new file mode 100644 index 000000000..e32916bc8 --- /dev/null +++ b/src/lib/amplitude/track-event/useTrackAction.ts @@ -0,0 +1,54 @@ +import { track } from "@amplitude/analytics-browser"; +import { useCallback } from "react"; + +import type { ActionAmpEvent } from "../types"; +import type { AttachFundsType } from "lib/components/fund/types"; + +import { useMandatoryProperties } from "./useMandatoryProperties"; + +export const useTrackAction = () => { + const mandatoryProperties = useMandatoryProperties(); + + const trackActionWithFunds = useCallback( + ( + event: ActionAmpEvent, + funds: number, + attachFundsOption: AttachFundsType, + method: "json-input" | "schema" + ) => + track(event, { + ...mandatoryProperties, + funds, + attachFundsOption, + method, + }), + [mandatoryProperties] + ); + + const trackAction = useCallback( + (event: ActionAmpEvent, method: "json-input" | "schema") => + track(event, { + ...mandatoryProperties, + method, + }), + [mandatoryProperties] + ); + + const trackActionQuery = useCallback( + ( + event: ActionAmpEvent, + method: "json-input" | "schema", + isInputRequired: boolean + ) => + track(event, { + ...mandatoryProperties, + method, + isInputRequired, + }), + [mandatoryProperties] + ); + + // TODO: implement custom action here + + return { trackAction, trackActionWithFunds, trackActionQuery }; +}; diff --git a/src/lib/amplitude/track-event/useTrackComponent.ts b/src/lib/amplitude/track-event/useTrackComponent.ts new file mode 100644 index 000000000..eb29f6295 --- /dev/null +++ b/src/lib/amplitude/track-event/useTrackComponent.ts @@ -0,0 +1,20 @@ +import { track } from "@amplitude/analytics-browser"; +import { useCallback } from "react"; + +import { AmpEvent } from "../types"; + +import { useMandatoryProperties } from "./useMandatoryProperties"; + +export const useTrackComponent = () => { + const mandatoryProperties = useMandatoryProperties(); + + const trackInvalidState = useCallback( + (title: string) => + track(AmpEvent.INVALID_STATE, { ...mandatoryProperties, title }), + [mandatoryProperties] + ); + + return { + trackInvalidState, + }; +}; diff --git a/src/lib/amplitude/track-event/useTrackExternal.ts b/src/lib/amplitude/track-event/useTrackExternal.ts new file mode 100644 index 000000000..446011bd8 --- /dev/null +++ b/src/lib/amplitude/track-event/useTrackExternal.ts @@ -0,0 +1,61 @@ +import { track } from "@amplitude/analytics-browser"; +import { useCallback } from "react"; + +import { AmpEvent } from "../types"; +import type { Dict } from "lib/types"; + +import { useMandatoryProperties } from "./useMandatoryProperties"; + +export const useTrackExternal = () => { + const mandatoryProperties = useMandatoryProperties(); + + const trackMintScan = useCallback( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (type: string, properties?: Dict, section?: string) => { + track(AmpEvent.MINTSCAN, { + ...mandatoryProperties, + type, + properties, + section, + }); + }, + [mandatoryProperties] + ); + + const trackWebsite = useCallback( + (url: string, section?: string) => + track(AmpEvent.WEBSITE, { + ...mandatoryProperties, + url, + section, + }), + [mandatoryProperties] + ); + + const trackSocial = useCallback( + (url: string, section?: string) => + track(AmpEvent.SOCIAL, { + ...mandatoryProperties, + url, + section, + }), + [mandatoryProperties] + ); + + const trackCelatone = useCallback( + (url: string, section?: string) => + track(AmpEvent.CELATONE, { + ...mandatoryProperties, + url, + section, + }), + [mandatoryProperties] + ); + + return { + trackMintScan, + trackWebsite, + trackSocial, + trackCelatone, + }; +}; diff --git a/src/lib/amplitude/track-event/useTrackInteraction.ts b/src/lib/amplitude/track-event/useTrackInteraction.ts new file mode 100644 index 000000000..a5f87ed10 --- /dev/null +++ b/src/lib/amplitude/track-event/useTrackInteraction.ts @@ -0,0 +1,282 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { track } from "@amplitude/analytics-browser"; +import big from "big.js"; +import { useCallback } from "react"; + +import { AmpEvent } from "../types"; +import type { Token, Option } from "lib/types"; + +import { useMandatoryProperties } from "./useMandatoryProperties"; + +// TODO: implement custom event properties including section +export const useTrackInteraction = () => { + const mandatoryProperties = useMandatoryProperties(); + + const trackUseMainSearch = useCallback( + (isClick: boolean, section?: string) => + track(AmpEvent.USE_MAIN_SEARCH, { + ...mandatoryProperties, + isClick, + section, + }), + [mandatoryProperties] + ); + + const trackUseTab = useCallback( + (tab: string, section?: string) => + track(AmpEvent.USE_TAB, { + ...mandatoryProperties, + tab, + section, + }), + [mandatoryProperties] + ); + + const trackUseRadio = useCallback( + (radio: string, section?: string) => + track(AmpEvent.USE_RADIO, { + ...mandatoryProperties, + radio, + section, + }), + [mandatoryProperties] + ); + + const trackUseOtherModal = useCallback( + (title: string, section?: string) => + track(AmpEvent.USE_OTHER_MODAL, { + ...mandatoryProperties, + title, + section, + }), + [mandatoryProperties] + ); + + const trackUseViewJSON = useCallback( + (section?: string) => + track(AmpEvent.USE_VIEW_JSON, { + ...mandatoryProperties, + section, + }), + [mandatoryProperties] + ); + + const trackUseUnsupportedToken = useCallback( + (section?: string) => + track(AmpEvent.USE_UNSUPPORTED_ASSETS, { + ...mandatoryProperties, + section, + }), + [mandatoryProperties] + ); + + const trackUseCopier = useCallback( + (type: string, section?: string) => + track(AmpEvent.USE_COPIER, { + ...mandatoryProperties, + type, + section, + }), + [mandatoryProperties] + ); + + const trackUseExpand = useCallback( + ({ + action, + component, + info, + section, + }: { + action: "expand" | "collapse"; + component: + | "assets" + | "json" + | "permission_address" + | "event_box" + | "unsupported_pool" + | "pool_tx_msg"; + info?: object; + section?: string; + }) => + track(AmpEvent.USE_EXPAND, { + ...mandatoryProperties, + action, + component, + info, + section, + }), + [mandatoryProperties] + ); + + const trackUseExpandAll = useCallback( + (action: "expand" | "collapse", section?: string) => + track(AmpEvent.USE_EXPAND, { + ...mandatoryProperties, + action, + section, + }), + [mandatoryProperties] + ); + + const trackUseClickWallet = useCallback( + (component?: string) => + track(AmpEvent.USE_CLICK_WALLET, { + ...mandatoryProperties, + component, + }), + [mandatoryProperties] + ); + + const trackUseRightHelperPanel = useCallback( + (action: string, section?: string) => + track(AmpEvent.USE_RIGHT_HELPER_PANEL, { + ...mandatoryProperties, + action, + section, + }), + [mandatoryProperties] + ); + + const trackUseUnpin = useCallback( + (check: boolean, section?: string) => + track(AmpEvent.USE_UNPIN, { + ...mandatoryProperties, + check, + section, + }), + [mandatoryProperties] + ); + + const trackUseInstantiatePermission = useCallback( + ( + type: string, + emptyAddressesLength: number, + addressesLength: number, + section?: string + ) => + track(AmpEvent.USE_INSTANTIATE_PERMISSION, { + ...mandatoryProperties, + type, + emptyAddressesLength, + addressesLength, + section, + }), + [mandatoryProperties] + ); + + const trackUseWhitelistedAddress = useCallback( + ( + emptyAddressesLength: number, + filledAddressesLength: number, + section?: string + ) => + track(AmpEvent.USE_WHITELISTED_ADDRESSES, { + ...mandatoryProperties, + emptyAddressesLength, + filledAddressesLength, + section, + }), + [mandatoryProperties] + ); + + const trackUseDepositFill = useCallback( + (amount: Token, section?: string) => { + track(AmpEvent.USE_DEPOSIT_FILL, { + ...mandatoryProperties, + amount, + section, + }); + }, + [mandatoryProperties] + ); + + const trackUseSubmitProposal = useCallback( + ( + properties: { + initialDeposit: string; + minDeposit: Option; + assetDenom: Option; + [key: string]: any; + }, + section?: string + ) => { + const proposalPeriod = big(properties.initialDeposit).lt( + properties.minDeposit || "0" + ) + ? "Deposit" + : "Voting"; + track(AmpEvent.USE_SUBMIT_PROPOSAL, { + ...mandatoryProperties, + ...properties, + proposalPeriod, + section, + }); + }, + [mandatoryProperties] + ); + + const trackUseFilter = useCallback( + ( + event: + | AmpEvent.USE_FILTER_POOL_TYPE + | AmpEvent.USE_FILTER_PROPOSALS_TYPE + | AmpEvent.USE_FILTER_PROPOSALS_STATUS, + filters: string[], + action: string + ) => track(event, { ...mandatoryProperties, action, filters }), + [mandatoryProperties] + ); + + const trackUsePaginationNavigate = useCallback( + (navigate: string, pageSize: number, currentPage: number) => + track(AmpEvent.USE_PAGINATION_NAVIGATION, { + ...mandatoryProperties, + navigate, + pageSize, + currentPage, + }), + [mandatoryProperties] + ); + + const trackUseSort = useCallback( + (order: "ascending" | "descending") => + track(AmpEvent.USE_SORT, { ...mandatoryProperties, order }), + [mandatoryProperties] + ); + + const trackUseView = useCallback( + (view: string) => + track(AmpEvent.USE_VIEW, { ...mandatoryProperties, view }), + [mandatoryProperties] + ); + + const trackUseToggle = useCallback( + (name: string, isActive: boolean) => + track(AmpEvent.USE_TOGGLE, { ...mandatoryProperties, name, isActive }), + [mandatoryProperties] + ); + + return { + trackUseMainSearch, + trackUseTab, + trackUseRadio, + trackUseOtherModal, + trackUseViewJSON, + trackUseUnsupportedToken, + trackUseCopier, + trackUseExpand, + trackUseExpandAll, + trackUseClickWallet, + trackUseRightHelperPanel, + trackUseUnpin, + trackUseInstantiatePermission, + trackUseWhitelistedAddress, + trackUseDepositFill, + trackUseSubmitProposal, + trackUseFilter, + trackUsePaginationNavigate, + trackUseSort, + trackUseView, + trackUseToggle, + }; +}; diff --git a/src/lib/amplitude/track-event/useTrackToPage.ts b/src/lib/amplitude/track-event/useTrackToPage.ts new file mode 100644 index 000000000..7c480108a --- /dev/null +++ b/src/lib/amplitude/track-event/useTrackToPage.ts @@ -0,0 +1,68 @@ +import { track } from "@amplitude/analytics-browser"; +import { useCallback } from "react"; + +import { AmpEvent } from "../types"; + +import { useMandatoryProperties } from "./useMandatoryProperties"; + +export const useTrackToPage = () => { + const mandatoryProperties = useMandatoryProperties(); + + const trackToQuery = useCallback( + (contract: boolean, msg: boolean) => + track(AmpEvent.TO_QUERY, { + ...mandatoryProperties, + contract, + msg, + }), + [mandatoryProperties] + ); + + const trackToExecute = useCallback( + (contract: boolean, msg: boolean) => + track(AmpEvent.TO_EXECUTE, { + ...mandatoryProperties, + contract, + msg, + }), + [mandatoryProperties] + ); + + const trackToInstantiate = useCallback( + (msg: boolean, codeId: boolean, section?: string) => + track(AmpEvent.TO_INSTANTIATE, { + ...mandatoryProperties, + codeId, + msg, + section, + }), + [mandatoryProperties] + ); + + const trackToMigrate = useCallback( + (contract: boolean, codeId: boolean) => + track(AmpEvent.TO_MIGRATE, { + ...mandatoryProperties, + codeId, + contract, + }), + [mandatoryProperties] + ); + + const trackToAdminUpdate = useCallback( + (contract: boolean) => + track(AmpEvent.TO_ADMIN_UPDATE, { + ...mandatoryProperties, + contract, + }), + [mandatoryProperties] + ); + + return { + trackToQuery, + trackToExecute, + trackToInstantiate, + trackToMigrate, + trackToAdminUpdate, + }; +}; diff --git a/src/lib/amplitude/track-event/useTrackTx.ts b/src/lib/amplitude/track-event/useTrackTx.ts new file mode 100644 index 000000000..3e7c5fd27 --- /dev/null +++ b/src/lib/amplitude/track-event/useTrackTx.ts @@ -0,0 +1,27 @@ +import { track } from "@amplitude/analytics-browser"; +import { useCallback } from "react"; + +import { AmpEvent } from "../types"; + +import { useMandatoryProperties } from "./useMandatoryProperties"; + +export const useTrackTx = () => { + const mandatoryProperties = useMandatoryProperties(); + + const trackTxSucceed = useCallback( + () => track(AmpEvent.TX_SUCCEED, { ...mandatoryProperties }), + [mandatoryProperties] + ); + + const trackTxFailed = useCallback( + () => track(AmpEvent.TX_FAILED, { ...mandatoryProperties }), + [mandatoryProperties] + ); + + const trackTxRejected = useCallback( + () => track(AmpEvent.TX_REJECTED, { ...mandatoryProperties }), + [mandatoryProperties] + ); + + return { trackTxSucceed, trackTxFailed, trackTxRejected }; +}; diff --git a/src/lib/amplitude/types.ts b/src/lib/amplitude/types.ts new file mode 100644 index 000000000..7037c8c04 --- /dev/null +++ b/src/lib/amplitude/types.ts @@ -0,0 +1,178 @@ +export enum AmpEvent { + INVALID_STATE = "To Invalid State", + // CODE + CODE_SAVE = "Code Save", + CODE_EDIT = "Code Edit", + CODE_REMOVE = "Code Remove", + // CONTRACT + CONTRACT_SAVE_AFTER_INIT = "Contract Save After Init", + CONTRACT_SAVE = "Contract Save", + CONTRACT_EDIT = "Contract Edit", + CONTRACT_EDIT_TAGS = "Contract Edit Tags", + CONTRACT_EDIT_LISTS = "Contract Edit Lists", + CONTRACT_REMOVE = "Contract Remove", + // TAG + TAG_CREATE = "Tag Create", + // LIST + LIST_CREATE = "List Create", + LIST_EDIT = "List Edit", + LIST_REMOVE = "List Remove", + // PUBLIC PROJECT + PUBLIC_SAVE = "Public Project Save", + PUBLIC_REMOVE = "Public Project Remove", + // NAVIGATE + TO_OVERVIEW = "To Overview", + TO_BLOCKS = "To Blocks", + TO_BLOCK_DETAIL = "To Block Detail", + TO_TXS = "To Txs", + TO_PAST_TXS = "To Past Txs", + TO_DEPLOY = "To Deploy", + TO_UPLOAD = "To Upload", + TO_INSTANTIATE = "To Instantiate", + TO_PROPOSAL_LIST = "To Proposal List", + TO_QUERY = "To Query", + TO_EXECUTE = "To Execute", + TO_MIGRATE = "To Migrate", + TO_ADMIN_UPDATE = "To Admin Update", + TO_MY_SAVED_CODES = "To My Saved Codes", + TO_MY_STORED_CODES = "To My Stored Codes", + TO_RECENT_CODES = "To Recent Codes", + TO_RECENT_CONTRACT = "To Recent Contract", + TO_INSTANTIATED_BY_ME = "To Instantiated By Me", + TO_SAVED_CONTRACT = "To Saved Contract", + TO_LIST_OTHERS = "To List Others", + TO_ALL_LISTS = "To All Lists", + TO_ALL_PROJECTS = "To All Public Projects", + TO_ACCOUNT_DETAIL = "To Account Detail", + TO_CONTRACT_DETAIL = "To Contract Detail", + TO_CODE_DETAIL = "To Code Detail", + TO_PROJECT_DETAIL = "To Public Project Detail", + TO_TRANSACTION_DETAIL = "To Transaction Detail", + TO_NOT_FOUND = "To 404 Not Found", + TO_FAUCET = "To Faucet", + TO_POOL_LIST = "To Pool List", + TO_POOL_DETAIL = "To Pool Detail", + TO_PROPOSAL_TO_STORE_CODE = "To Proposal To Store Code", + TO_PROPOSAL_TO_WHITELIST = "To Proposal To Whitelist", + // ACTIONS + ACTION_UPLOAD = "Act Upload", + ACTION_INSTANTIATE = "Action Instantiate", + ACTION_EXECUTE = "Action Execute", + ACTION_QUERY = "Action Query", + ACTION_MIGRATE = "Action Migrate", + ACTION_ADMIN_UPDATE = "Action Admin Update", + ACTION_ADMIN_CLEAR = "Action Admin Clear", + ACTION_RESEND = "Action Resend", + ACTION_FAUCET = "Action Faucet", + ACTION_ATTACH_JSON = "Action Attach Json", + // INTERACTS + USE_SELECT_NETWORK = "Use Select Network", + USE_CLICK_WALLET = "Use Click Wallet", + USE_MAIN_SEARCH = "Use Main Search", + USE_SIDEBAR = "Use Sidebar", + USE_TOPBAR = "Use Topbar", + USE_TAB = "Use Tab", + USE_RADIO = "Use Radio", + USE_VIEW_MORE = "Use View More", + USE_CODE_SELECT = "Use Code Select", + USE_CODE_MODAL = "Use Code Modal", + USE_CODE_FILL = "Use Code Fill", + USE_ASSIGN_ME = "Use Assign Me", + USE_ATTACHED_JSON_MODAL = "Use Attached Json Modal", + USE_VIEW_ATTACHED_JSON = "Use View Attached Json", + USE_EDIT_ATTACHED_JSON = "Use Edit Attached Json", + USE_REMOVE_ATTACHED_JSON = "Use Remove Attached Json", + USE_VIEW_JSON_IN_CODE_DETAIL = "Use View Json In Code Detail", + USE_SWITCH_JSON_INPUT = "Use Switch Json Input", + USE_CONTRACT_FORM = "Use Contract Form", + USE_CONTRACT_MODAL = "Use Contract Modal", + USE_CONTRACT_MODAL_SEARCH = "Use Contract Modal Search", + USE_CONTRACT_MODAL_LISTS = "Use Contract Modal Lists", + USE_CONTRACT_SNIPPET = "Use Contract Snippet", + USE_CMD_QUERY = "Use Command Query", + USE_CMD_EXECUTE = "Use Command Execute", + USE_SEE_REDELEGATIONS = "Use See Redelegations", + USE_BACK_BUTTON = "Use Back Button", + USE_COPY_BUTTON = "Use Copy Button", + USE_COPIER = "Use Copier", + USE_QUICK_EDIT_CONTRACT = "Use Quick Edit Contract", + USE_QUICK_EDIT_CODE = "Use Quick Edit Code", + USE_UNSUPPORTED_ASSETS_MODAL = "Use Unsupported Assets Modal", + USE_OTHER_MODAL = "Use Other Modal", + USE_SUBMIT_PROJECT = "Use Submit Project", + USE_VIEW_JSON = "Use View Json", + USE_UNSUPPORTED_ASSETS = "Use Unsupported Assets", + USE_TX_MSG_EXPAND = "Use Transaction Message Expand", + USE_EXPAND = "Use General Expand", + USE_EXPAND_ALL = "Use Expand All", + USE_RIGHT_HELPER_PANEL = "Use Right Helper Panel", // Sticky panel + USE_UNPIN = "Use Unpin", + USE_INSTANTIATE_PERMISSION = "Use Instantiate Permission", + USE_WHITELISTED_ADDRESSES = "Use Whitelisted Addresses", + USE_DEPOSIT_FILL = "Use Deposit Fill", + USE_SUBMIT_PROPOSAL = "Use Submit Proposal", + USE_SEARCH_INPUT = "Use Search Input", + USE_FILTER_MY_PROPOSALS = "Use Filter My Proposals", + USE_FILTER_PROPOSALS_STATUS = "Use Filter Proposals Status", + USE_FILTER_PROPOSALS_TYPE = "Use Filter Proposals Types", + USE_FILTER_POOL_TYPE = "Use Filter Pool Types", + USE_PAGINATION_PAGE_SIZE = "Use Pagination Page Size", + USE_PAGINATION_NAVIGATION = "Use Pagination Navigation", + USE_CREATE_NEW_PROPOSAL = "Use Create New Proposal", + USE_SORT = "Use Sort", + USE_VIEW = "Use View", + USE_TOGGLE = "Use Toggle", + USE_SCHEMA_TOGGLE = "Use Schema Toggle", + USE_JSON_QUERY_AGAIN = "Use Json Query Again", + USE_TO_YOUR_ACCOUNT = "Use To Your Account", + // TX + TX_SUCCEED = "Tx Succeed", + TX_FAILED = "Tx Failed", + TX_REJECTED = "Tx Rejected", + // EXTERNAL + MINTSCAN = "Mintscan", + WEBSITE = "Website", + SOCIAL = "Social", + CELATONE = "Celatone", + FEEDBACK = "Feedback", + ALLESLABS = "AllesLabs", +} + +export type ActionAmpEvent = + | AmpEvent.ACTION_INSTANTIATE + | AmpEvent.ACTION_EXECUTE + | AmpEvent.ACTION_QUERY + | AmpEvent.ACTION_MIGRATE; + +export type SpecialAmpEvent = + | AmpEvent.INVALID_STATE + | AmpEvent.TO_QUERY + | AmpEvent.TO_EXECUTE + | AmpEvent.TO_INSTANTIATE + | AmpEvent.TO_MIGRATE + | AmpEvent.TO_ADMIN_UPDATE + | AmpEvent.USE_MAIN_SEARCH + | AmpEvent.USE_TAB + | AmpEvent.USE_RADIO + | AmpEvent.USE_COPIER + | AmpEvent.USE_OTHER_MODAL + | AmpEvent.MINTSCAN + | AmpEvent.WEBSITE + | AmpEvent.SOCIAL + | AmpEvent.CELATONE + | AmpEvent.USE_VIEW_JSON + | AmpEvent.USE_UNSUPPORTED_ASSETS + | AmpEvent.USE_COPIER + | AmpEvent.USE_EXPAND + | AmpEvent.USE_RIGHT_HELPER_PANEL + | AmpEvent.USE_UNPIN + | AmpEvent.USE_INSTANTIATE_PERMISSION + | AmpEvent.USE_DEPOSIT_FILL + | AmpEvent.USE_EXPAND_ALL + | AmpEvent.USE_PAGINATION_NAVIGATION + | AmpEvent.USE_FILTER_PROPOSALS_STATUS + | AmpEvent.USE_FILTER_PROPOSALS_TYPE + | AmpEvent.USE_FILTER_POOL_TYPE + | AmpEvent.USE_SORT + | AmpEvent.USE_VIEW + | AmpEvent.USE_TOGGLE; diff --git a/src/lib/amplitude/useAmplitudeInit.ts b/src/lib/amplitude/useAmplitudeInit.ts new file mode 100644 index 000000000..8d9ad0f5a --- /dev/null +++ b/src/lib/amplitude/useAmplitudeInit.ts @@ -0,0 +1,59 @@ +import { Identify, identify, init } from "@amplitude/analytics-browser"; +import { fromBech32 } from "@cosmjs/encoding"; + +import { useCelatoneApp } from "lib/app-provider/contexts"; +import { useCurrentChain } from "lib/app-provider/hooks"; +import { StorageKeys } from "lib/data"; +import { useLocalStorage } from "lib/hooks/useLocalStorage"; +import { getItem, sha256Hex } from "lib/utils"; + +export const useAmplitudeInit = () => { + const { currentChainId } = useCelatoneApp(); + const { address } = useCurrentChain(); + const [wallets, setWallets] = useLocalStorage( + StorageKeys.Wallets, + [] + ); + const [networks, setNetworks] = useLocalStorage( + StorageKeys.Networks, + [] + ); + + if (typeof window !== "undefined") { + init(process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY ?? "", undefined, { + trackingOptions: { + attribution: true, + region: false, + ipAddress: false, + }, + serverUrl: "/amplitude", + }); + + // TODO: make util function + if (address) { + const rawAddressHash = sha256Hex(fromBech32(address).data); + + if (!wallets.includes(rawAddressHash)) { + setWallets([...wallets, rawAddressHash]); + } + } + + if (!networks.includes(currentChainId)) { + setNetworks([...networks, currentChainId]); + } + + // Custom user properties + const identifyEvent = new Identify(); + identifyEvent.set("Wallets", wallets); + identifyEvent.set("Wallets Count", wallets.length); + identifyEvent.set("Nav Sidebar", getItem(StorageKeys.NavSidebar, "")); + identifyEvent.set("Dev Sidebar", getItem(StorageKeys.DevSidebar, "")); + identifyEvent.set( + "Project Sidebar", + getItem(StorageKeys.ProjectSidebar, "") + ); + identifyEvent.set("Networks", networks); + identifyEvent.set("Networks Count", networks.length); + identify(identifyEvent); + } +}; diff --git a/src/lib/app-fns/tx/clearAdmin.tsx b/src/lib/app-fns/tx/clearAdmin.tsx index c8892ac36..4d24fea05 100644 --- a/src/lib/app-fns/tx/clearAdmin.tsx +++ b/src/lib/app-fns/tx/clearAdmin.tsx @@ -6,14 +6,13 @@ import type { StdFee } from "@cosmjs/stargate"; import { pipe } from "@rx-stream/pipe"; import type { Observable } from "rxjs"; +import type { CatchTxError } from "lib/app-provider/tx/catchTxError"; import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import { TxStreamPhase } from "lib/types"; import type { TxResultRendering, ContractAddr, HumanAddr } from "lib/types"; import { formatUFee } from "lib/utils"; -import { catchTxError } from "./common/catchTxError"; import { postTx } from "./common/post"; import { sendingTx } from "./common/sending"; @@ -24,6 +23,7 @@ interface ClearAdminTxParams { memo?: string; client: SigningCosmWasmClient; onTxSucceed?: () => void; + catchTxError: CatchTxError; } export const clearAdminTx = ({ @@ -33,6 +33,7 @@ export const clearAdminTx = ({ memo, client, onTxSucceed, + catchTxError, }: ClearAdminTxParams): Observable => { return pipe( sendingTx(fee), @@ -40,7 +41,6 @@ export const clearAdminTx = ({ postFn: () => client.clearAdmin(address, contractAddress, fee, memo), }), ({ value: txInfo }) => { - AmpTrack(AmpEvent.TX_SUCCEED); onTxSucceed?.(); const txFee = txInfo.events.find((e) => e.type === "tx")?.attributes[0] .value; diff --git a/src/lib/app-fns/tx/common/catchTxError.tsx b/src/lib/app-fns/tx/common/catchTxError.tsx index b59d5e76c..fc1dd2684 100644 --- a/src/lib/app-fns/tx/common/catchTxError.tsx +++ b/src/lib/app-fns/tx/common/catchTxError.tsx @@ -3,7 +3,6 @@ import { catchError } from "rxjs"; import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { ActionVariant, ReceiptInfo, @@ -39,15 +38,17 @@ const getActionVariant = (isRejected: boolean): ActionVariant => isRejected ? "rejected" : "failed"; export const catchTxError = ( + trackTxFailed: () => void, + trackTxRejected: () => void, onTxFailed?: () => void ): OperatorFunction => { return catchError((error: Error) => { const txHash = error.message.match("(?:tx )(.*?)(?= at)")?.at(1); - AmpTrack( - error.message === "Request rejected" - ? AmpEvent.TX_REJECTED - : AmpEvent.TX_FAILED - ); + if (error.message === "Request rejected") { + trackTxRejected(); + } else { + trackTxFailed(); + } onTxFailed?.(); return Promise.resolve({ value: null, diff --git a/src/lib/app-fns/tx/execute.tsx b/src/lib/app-fns/tx/execute.tsx index 5d645881b..dd6c1f0f1 100644 --- a/src/lib/app-fns/tx/execute.tsx +++ b/src/lib/app-fns/tx/execute.tsx @@ -6,15 +6,15 @@ import type { Coin, StdFee } from "@cosmjs/stargate"; import { pipe } from "@rx-stream/pipe"; import type { Observable } from "rxjs"; +import type { CatchTxError } from "lib/app-provider/tx/catchTxError"; import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { Activity } from "lib/stores/contract"; import type { ContractAddr, HumanAddr, TxResultRendering } from "lib/types"; import { TxStreamPhase } from "lib/types"; import { encode, formatUFee, getCurrentDate } from "lib/utils"; -import { catchTxError, postTx, sendingTx } from "./common"; +import { postTx, sendingTx } from "./common"; interface ExecuteTxParams { address: HumanAddr; @@ -23,6 +23,7 @@ interface ExecuteTxParams { msg: object; funds: Coin[]; client: SigningCosmWasmClient; + catchTxError: CatchTxError; onTxSucceed?: (activity: Activity) => void; onTxFailed?: () => void; } @@ -34,6 +35,7 @@ export const executeContractTx = ({ msg, funds, client, + catchTxError, onTxSucceed, onTxFailed, }: ExecuteTxParams): Observable => { @@ -44,7 +46,6 @@ export const executeContractTx = ({ client.execute(address, contractAddress, msg, fee, undefined, funds), }), ({ value: txInfo }) => { - AmpTrack(AmpEvent.TX_SUCCEED); onTxSucceed?.({ type: "execute", action: Object.keys(msg)[0], diff --git a/src/lib/app-fns/tx/instantiate.tsx b/src/lib/app-fns/tx/instantiate.tsx index 83dda08a4..21af59ad9 100644 --- a/src/lib/app-fns/tx/instantiate.tsx +++ b/src/lib/app-fns/tx/instantiate.tsx @@ -6,10 +6,9 @@ import type { Coin, StdFee } from "@cosmjs/stargate"; import { pipe } from "@rx-stream/pipe"; import type { Observable } from "rxjs"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; +import type { CatchTxError } from "lib/app-provider/tx/catchTxError"; import type { TxResultRendering } from "lib/types"; -import { catchTxError } from "./common/catchTxError"; import { postTx } from "./common/post"; import { sendingTx } from "./common/sending"; @@ -22,6 +21,7 @@ interface InstantiateTxParams { admin: string; funds: Coin[]; client: SigningCosmWasmClient; + catchTxError: CatchTxError; onTxSucceed?: (txInfo: InstantiateResult, contractLabel: string) => void; onTxFailed?: () => void; } @@ -35,6 +35,7 @@ export const instantiateContractTx = ({ admin, funds, client, + catchTxError, onTxSucceed, onTxFailed, }: InstantiateTxParams): Observable => { @@ -48,7 +49,6 @@ export const instantiateContractTx = ({ }), }), ({ value: txInfo }) => { - AmpTrack(AmpEvent.TX_SUCCEED); onTxSucceed?.(txInfo, label); // TODO: this is type hack return null as unknown as TxResultRendering; diff --git a/src/lib/app-fns/tx/migrate.tsx b/src/lib/app-fns/tx/migrate.tsx index 773363a9e..0b7080a64 100644 --- a/src/lib/app-fns/tx/migrate.tsx +++ b/src/lib/app-fns/tx/migrate.tsx @@ -6,14 +6,13 @@ import type { StdFee } from "@cosmjs/stargate"; import { pipe } from "@rx-stream/pipe"; import type { Observable } from "rxjs"; +import type { CatchTxError } from "lib/app-provider/tx/catchTxError"; import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { ContractAddr, HumanAddr, TxResultRendering } from "lib/types"; import { TxStreamPhase } from "lib/types"; import { formatUFee } from "lib/utils"; -import { catchTxError } from "./common/catchTxError"; import { postTx } from "./common/post"; import { sendingTx } from "./common/sending"; @@ -24,6 +23,7 @@ interface MigrateTxParams { migrateMsg: object; fee: StdFee; client: SigningCosmWasmClient; + catchTxError: CatchTxError; onTxSucceed?: (txHash: string) => void; onTxFailed?: () => void; } @@ -35,6 +35,7 @@ export const migrateContractTx = ({ migrateMsg, fee, client, + catchTxError, onTxSucceed, onTxFailed, }: MigrateTxParams): Observable => { @@ -52,7 +53,6 @@ export const migrateContractTx = ({ ), }), ({ value: txInfo }) => { - AmpTrack(AmpEvent.TX_SUCCEED); onTxSucceed?.(txInfo.transactionHash); const txFee = txInfo.events.find((e) => e.type === "tx")?.attributes[0] .value; diff --git a/src/lib/app-fns/tx/resend.tsx b/src/lib/app-fns/tx/resend.tsx index aba1356f0..9463ecad2 100644 --- a/src/lib/app-fns/tx/resend.tsx +++ b/src/lib/app-fns/tx/resend.tsx @@ -5,31 +5,33 @@ import type { StdFee } from "@cosmjs/stargate"; import { pipe } from "@rx-stream/pipe"; import type { Observable } from "rxjs"; +import type { CatchTxError } from "lib/app-provider/tx/catchTxError"; import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { HumanAddr, TxResultRendering } from "lib/types"; import { TxStreamPhase } from "lib/types"; import { formatUFee } from "lib/utils"; -import { catchTxError, postTx, sendingTx } from "./common"; +import { postTx, sendingTx } from "./common"; interface ResendTxParams { address: HumanAddr; client: SigningCosmWasmClient; - onTxSucceed?: (txHash: string) => void; - onTxFailed?: () => void; fee: StdFee; messages: EncodeObject[]; + catchTxError: CatchTxError; + onTxSucceed?: (txHash: string) => void; + onTxFailed?: () => void; } export const resendTx = ({ address, client, - onTxSucceed, - onTxFailed, fee, messages, + catchTxError, + onTxSucceed, + onTxFailed, }: ResendTxParams): Observable => { return pipe( sendingTx(fee), @@ -37,7 +39,6 @@ export const resendTx = ({ postFn: () => client.signAndBroadcast(address, messages, fee), }), ({ value: txInfo }) => { - AmpTrack(AmpEvent.TX_SUCCEED); onTxSucceed?.(txInfo.transactionHash); const txFee = txInfo.events.find((e) => e.type === "tx")?.attributes[0] .value; diff --git a/src/lib/app-fns/tx/submitProposal.tsx b/src/lib/app-fns/tx/submitProposal.tsx index d1b5d3699..1b134e4d9 100644 --- a/src/lib/app-fns/tx/submitProposal.tsx +++ b/src/lib/app-fns/tx/submitProposal.tsx @@ -5,35 +5,37 @@ import { pipe } from "@rx-stream/pipe"; import { capitalize } from "lodash"; import type { Observable } from "rxjs"; +import type { CatchTxError } from "lib/app-provider/tx/catchTxError"; import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { HumanAddr, TxResultRendering } from "lib/types"; import { TxStreamPhase } from "lib/types"; import { findAttr, formatUFee } from "lib/utils"; -import { catchTxError, postTx, sendingTx } from "./common"; +import { postTx, sendingTx } from "./common"; interface SubmitWhitelistProposalTxParams { address: HumanAddr; client: SigningCosmWasmClient; - onTxSucceed?: () => void; - onTxFailed?: () => void; fee: StdFee; messages: EncodeObject[]; whitelistNumber: number; amountToVote: string | null; + catchTxError: CatchTxError; + onTxSucceed?: () => void; + onTxFailed?: () => void; } export const submitWhitelistProposalTx = ({ address, client, - onTxSucceed, - onTxFailed, fee, messages, whitelistNumber, amountToVote, + catchTxError, + onTxSucceed, + onTxFailed, }: SubmitWhitelistProposalTxParams): Observable => { return pipe( sendingTx(fee), @@ -41,7 +43,6 @@ export const submitWhitelistProposalTx = ({ postFn: () => client.signAndBroadcast(address, messages, fee), }), ({ value: txInfo }) => { - AmpTrack(AmpEvent.TX_SUCCEED); onTxSucceed?.(); const mimicLog: logs.Log = { msg_index: 0, @@ -109,6 +110,7 @@ interface SubmitStoreCodeProposalTxParams { wasmFileName: string; messages: EncodeObject[]; amountToVote: string | null; + catchTxError: CatchTxError; onTxSucceed?: () => void; onTxFailed?: () => void; } @@ -121,6 +123,7 @@ export const submitStoreCodeProposalTx = ({ wasmFileName, messages, amountToVote, + catchTxError, onTxSucceed, onTxFailed, }: SubmitStoreCodeProposalTxParams): Observable => { @@ -130,7 +133,6 @@ export const submitStoreCodeProposalTx = ({ postFn: () => client.signAndBroadcast(address, messages, fee), }), ({ value: txInfo }) => { - AmpTrack(AmpEvent.TX_SUCCEED); onTxSucceed?.(); const mimicLog: logs.Log = { msg_index: 0, diff --git a/src/lib/app-fns/tx/updateAdmin.tsx b/src/lib/app-fns/tx/updateAdmin.tsx index c17d9b23e..13cc11326 100644 --- a/src/lib/app-fns/tx/updateAdmin.tsx +++ b/src/lib/app-fns/tx/updateAdmin.tsx @@ -6,9 +6,9 @@ import type { StdFee } from "@cosmjs/stargate"; import { pipe } from "@rx-stream/pipe"; import type { Observable } from "rxjs"; +import type { CatchTxError } from "lib/app-provider/tx/catchTxError"; import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { Addr, ContractAddr, @@ -18,7 +18,7 @@ import type { import { TxStreamPhase } from "lib/types"; import { formatUFee } from "lib/utils"; -import { catchTxError, postTx, sendingTx } from "./common"; +import { postTx, sendingTx } from "./common"; interface UpdateAdminTxParams { address: HumanAddr; @@ -26,6 +26,7 @@ interface UpdateAdminTxParams { newAdmin: Addr; fee: StdFee; client: SigningCosmWasmClient; + catchTxError: CatchTxError; onTxSucceed?: () => void; onTxFailed?: () => void; } @@ -36,6 +37,7 @@ export const updateAdminTx = ({ newAdmin, fee, client, + catchTxError, onTxSucceed, onTxFailed, }: UpdateAdminTxParams): Observable => { @@ -46,7 +48,6 @@ export const updateAdminTx = ({ client.updateAdmin(address, contractAddress, newAdmin, fee, undefined), }), ({ value: txInfo }) => { - AmpTrack(AmpEvent.TX_SUCCEED); onTxSucceed?.(); const txFee = txInfo.events.find((e) => e.type === "tx")?.attributes[0] .value; diff --git a/src/lib/app-fns/tx/upload.tsx b/src/lib/app-fns/tx/upload.tsx index 35a7f7c68..31ba50340 100644 --- a/src/lib/app-fns/tx/upload.tsx +++ b/src/lib/app-fns/tx/upload.tsx @@ -4,15 +4,14 @@ import { pipe } from "@rx-stream/pipe"; import type { Observable } from "rxjs"; import type { UploadSucceedCallback } from "lib/app-provider"; +import type { CatchTxError } from "lib/app-provider/tx/catchTxError"; import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import { TxStreamPhase } from "lib/types"; import type { HumanAddr, TxResultRendering, ComposedMsg } from "lib/types"; import { findAttr } from "lib/utils"; import { formatUFee } from "lib/utils/formatter/denom"; -import { catchTxError } from "./common/catchTxError"; import { postTx } from "./common/post"; import { sendingTx } from "./common/sending"; @@ -24,8 +23,9 @@ interface UploadTxParams { fee: StdFee; memo?: string; client: SigningCosmWasmClient; - onTxSucceed: UploadSucceedCallback; isMigrate: boolean; + catchTxError: CatchTxError; + onTxSucceed: UploadSucceedCallback; } export const uploadContractTx = ({ @@ -36,8 +36,9 @@ export const uploadContractTx = ({ fee, memo, client, - onTxSucceed, isMigrate, + catchTxError, + onTxSucceed, }: UploadTxParams): Observable => { return pipe( sendingTx(fee), @@ -45,7 +46,6 @@ export const uploadContractTx = ({ postFn: () => client.signAndBroadcast(address, messages, fee, memo), }), ({ value: txInfo }) => { - AmpTrack(AmpEvent.TX_SUCCEED); const mimicLog: logs.Log = { msg_index: 0, log: "", diff --git a/src/lib/app-provider/contexts/nav.tsx b/src/lib/app-provider/contexts/nav.tsx index 8eea19795..1a42305f4 100644 --- a/src/lib/app-provider/contexts/nav.tsx +++ b/src/lib/app-provider/contexts/nav.tsx @@ -1,37 +1,32 @@ import type { Dispatch, ReactNode, SetStateAction } from "react"; import { useContext, createContext, useMemo } from "react"; +import { usePreviousPathname } from "../hooks/usePreviousPathname"; +import { StorageKeys } from "lib/data"; import { useLocalStorage } from "lib/hooks/useLocalStorage"; -import type { Option } from "lib/types"; interface NavContextInterface { isExpand: boolean; - isDevMode: Option; + prevPathname: string | null; setIsExpand: Dispatch>; - setIsDevMode: Dispatch>>; } const NavContext = createContext({ isExpand: false, - isDevMode: undefined, + prevPathname: null, setIsExpand: () => {}, - setIsDevMode: () => {}, }); export const NavProvider = ({ children }: { children: ReactNode }) => { - const [isDevMode, setIsDevMode] = useLocalStorage>( - "devMode", - undefined - ); - const [isExpand, setIsExpand] = useLocalStorage("navbar", false); + const [isExpand, setIsExpand] = useLocalStorage(StorageKeys.NavSidebar, true); + const prevPathname = usePreviousPathname(); const states = useMemo( () => ({ - isDevMode, isExpand, - setIsDevMode, + prevPathname, setIsExpand, }), - [isDevMode, isExpand, setIsDevMode, setIsExpand] + [isExpand, prevPathname, setIsExpand] ); return {children}; diff --git a/src/lib/app-provider/hooks/index.ts b/src/lib/app-provider/hooks/index.ts index 76a5b2de0..2058471c1 100644 --- a/src/lib/app-provider/hooks/index.ts +++ b/src/lib/app-provider/hooks/index.ts @@ -1,5 +1,4 @@ export * from "./useAddress"; -export * from "./useAmplitude"; export * from "./useDummyWallet"; export * from "./useExampleAddresses"; export * from "./useFabricateFee"; @@ -13,3 +12,4 @@ export * from "./useBaseApiRoute"; export * from "./useRPCEndpoint"; export * from "./useConfig"; export * from "./useCurrentChain"; +export * from "./usePreviousPathname"; diff --git a/src/lib/app-provider/hooks/useAmplitude.ts b/src/lib/app-provider/hooks/useAmplitude.ts deleted file mode 100644 index 95cc5d226..000000000 --- a/src/lib/app-provider/hooks/useAmplitude.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { init, setDeviceId, setUserId } from "@amplitude/analytics-browser"; -import { useChain } from "@cosmos-kit/react"; -import { createHash } from "crypto"; -import { useEffect } from "react"; -import * as uuid from "uuid"; - -import type { Option } from "lib/types"; - -export const useAmplitude = (chainName: Option) => { - /** - * @remarks Revisit default chain name - */ - const { address } = useChain(chainName ?? "osmosis"); - if (typeof window !== "undefined") { - init(process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY ?? "", undefined, { - trackingOptions: { - deviceManufacturer: false, - deviceModel: false, - ipAddress: false, - language: false, - osName: false, - osVersion: false, - platform: false, - }, - serverUrl: "/amplitude", - }); - - let deviceId = localStorage.getItem("deviceId"); - if (!deviceId) { - deviceId = uuid.v4(); - localStorage.setItem("deviceId", deviceId); - } - setDeviceId(deviceId); - } - - useEffect(() => { - const timeoutId = setTimeout(() => { - if (chainName) { - const userId = address - ? createHash("sha256").update(address).digest("hex") - : undefined; - setUserId(`${chainName}/${userId}`); - } - }, 300); - return () => clearTimeout(timeoutId); - }, [address, chainName]); -}; diff --git a/src/lib/app-provider/hooks/usePreviousPathname.ts b/src/lib/app-provider/hooks/usePreviousPathname.ts new file mode 100644 index 000000000..7d15ccaf5 --- /dev/null +++ b/src/lib/app-provider/hooks/usePreviousPathname.ts @@ -0,0 +1,14 @@ +import { useRouter } from "next/router"; +import { useEffect, useRef } from "react"; + +export const usePreviousPathname = () => { + const { pathname } = useRouter(); + + const ref = useRef(null); + + useEffect(() => { + ref.current = pathname; + }, [pathname]); + + return ref.current; +}; diff --git a/src/lib/app-provider/tx/catchTxError.ts b/src/lib/app-provider/tx/catchTxError.ts new file mode 100644 index 000000000..fe0409ea3 --- /dev/null +++ b/src/lib/app-provider/tx/catchTxError.ts @@ -0,0 +1,17 @@ +import { useCallback } from "react"; + +import { useTrack } from "lib/amplitude"; +import { catchTxError } from "lib/app-fns/tx/common"; + +// HACK: This shouldn't be here. It should be in src/lib/app-fns/tx/common/catchTxError.ts +export type CatchTxError = ReturnType; + +export const useCatchTxError = () => { + const { trackTxFailed, trackTxRejected } = useTrack(); + + return useCallback( + (onTxFailed?: () => void) => + catchTxError(trackTxFailed, trackTxRejected, onTxFailed), + [trackTxFailed, trackTxRejected] + ); +}; diff --git a/src/lib/app-provider/tx/clearAdmin.ts b/src/lib/app-provider/tx/clearAdmin.ts index 13c360257..1f9cbbcf2 100644 --- a/src/lib/app-provider/tx/clearAdmin.ts +++ b/src/lib/app-provider/tx/clearAdmin.ts @@ -1,10 +1,14 @@ import { useQueryClient } from "@tanstack/react-query"; import { useCallback } from "react"; +import { CELATONE_QUERY_KEYS } from "../env"; import { useCurrentChain, useFabricateFee, useWasmConfig } from "../hooks"; +import { useTrack } from "lib/amplitude"; import { clearAdminTx } from "lib/app-fns/tx/clearAdmin"; import type { ContractAddr, HumanAddr } from "lib/types"; +import { useCatchTxError } from "./catchTxError"; + export interface ClearAdminStreamParams { onTxSucceed?: () => void; } @@ -14,6 +18,8 @@ export const useClearAdminTx = (contractAddress: ContractAddr) => { const queryClient = useQueryClient(); const fabricateFee = useFabricateFee(); const wasm = useWasmConfig({ shouldRedirect: false }); + const { trackTxSucceed } = useTrack(); + const catchTxError = useCatchTxError(); return useCallback( async ({ onTxSucceed }: ClearAdminStreamParams) => { @@ -34,20 +40,24 @@ export const useClearAdminTx = (contractAddress: ContractAddr) => { fee: clearAdminFee, client, onTxSucceed: () => { + trackTxSucceed(); onTxSucceed?.(); Promise.all([ queryClient.invalidateQueries({ - queryKey: ["admin_by_contracts"], + queryKey: [CELATONE_QUERY_KEYS.ADMINS_BY_CONTRACTS], }), queryClient.invalidateQueries({ - queryKey: ["query", "instantiate_info"], + queryKey: [CELATONE_QUERY_KEYS.CONTRACT_INSTANTIATE_DETAIL], }), ]); }, + catchTxError, }); }, [ getSigningCosmWasmClient, + trackTxSucceed, + catchTxError, address, wasm, fabricateFee, diff --git a/src/lib/app-provider/tx/execute.ts b/src/lib/app-provider/tx/execute.ts index 257b0b94b..c39c4df81 100644 --- a/src/lib/app-provider/tx/execute.ts +++ b/src/lib/app-provider/tx/execute.ts @@ -2,10 +2,13 @@ import type { Coin, StdFee } from "@cosmjs/stargate"; import { useCallback } from "react"; import { useCurrentChain } from "../hooks"; +import { useTrack } from "lib/amplitude"; import { executeContractTx } from "lib/app-fns/tx/execute"; import type { Activity } from "lib/stores/contract"; import type { ContractAddr, HumanAddr } from "lib/types"; +import { useCatchTxError } from "./catchTxError"; + export interface ExecuteStreamParams { onTxSucceed?: (activity: Activity) => void; onTxFailed?: () => void; @@ -17,6 +20,8 @@ export interface ExecuteStreamParams { export const useExecuteContractTx = () => { const { address, getSigningCosmWasmClient } = useCurrentChain(); + const { trackTxSucceed } = useTrack(); + const catchTxError = useCatchTxError(); return useCallback( async ({ @@ -39,10 +44,14 @@ export const useExecuteContractTx = () => { msg, funds, client, - onTxSucceed, + catchTxError, + onTxSucceed: (activity) => { + trackTxSucceed(); + onTxSucceed?.(activity); + }, onTxFailed, }); }, - [address, getSigningCosmWasmClient] + [address, getSigningCosmWasmClient, trackTxSucceed, catchTxError] ); }; diff --git a/src/lib/app-provider/tx/instantiate.ts b/src/lib/app-provider/tx/instantiate.ts index ef3935eef..34370e0b1 100644 --- a/src/lib/app-provider/tx/instantiate.ts +++ b/src/lib/app-provider/tx/instantiate.ts @@ -3,8 +3,11 @@ import type { Coin, StdFee } from "@cosmjs/stargate"; import { useCallback } from "react"; import { useCurrentChain } from "../hooks"; +import { useTrack } from "lib/amplitude"; import { instantiateContractTx } from "lib/app-fns/tx/instantiate"; +import { useCatchTxError } from "./catchTxError"; + export interface InstantiateStreamParams { estimatedFee: StdFee | undefined; codeId: number; @@ -18,6 +21,8 @@ export interface InstantiateStreamParams { export const useInstantiateTx = () => { const { address, getSigningCosmWasmClient } = useCurrentChain(); + const { trackTxSucceed } = useTrack(); + const catchTxError = useCatchTxError(); return useCallback( async ({ @@ -44,10 +49,14 @@ export const useInstantiateTx = () => { admin, funds, client, - onTxSucceed, + catchTxError, + onTxSucceed: (txResult, contractLabel) => { + trackTxSucceed(); + onTxSucceed?.(txResult, contractLabel); + }, onTxFailed, }); }, - [address, getSigningCosmWasmClient] + [address, getSigningCosmWasmClient, trackTxSucceed, catchTxError] ); }; diff --git a/src/lib/app-provider/tx/migrate.ts b/src/lib/app-provider/tx/migrate.ts index 804f83f74..ba65ebb90 100644 --- a/src/lib/app-provider/tx/migrate.ts +++ b/src/lib/app-provider/tx/migrate.ts @@ -2,9 +2,12 @@ import type { StdFee } from "@cosmjs/stargate"; import { useCallback } from "react"; import { useCurrentChain } from "../hooks"; +import { useTrack } from "lib/amplitude"; import { migrateContractTx } from "lib/app-fns/tx/migrate"; import type { ContractAddr, HumanAddr, Option } from "lib/types"; +import { useCatchTxError } from "./catchTxError"; + export interface MigrateStreamParams { contractAddress: ContractAddr; codeId: number; @@ -16,6 +19,9 @@ export interface MigrateStreamParams { export const useMigrateTx = () => { const { address, getSigningCosmWasmClient } = useCurrentChain(); + const { trackTxSucceed } = useTrack(); + const catchTxError = useCatchTxError(); + return useCallback( async ({ contractAddress, @@ -37,10 +43,14 @@ export const useMigrateTx = () => { migrateMsg, fee: estimatedFee, client, - onTxSucceed, + catchTxError, + onTxSucceed: (txHash) => { + trackTxSucceed(); + onTxSucceed?.(txHash); + }, onTxFailed, }); }, - [address, getSigningCosmWasmClient] + [address, getSigningCosmWasmClient, trackTxSucceed, catchTxError] ); }; diff --git a/src/lib/app-provider/tx/resend.ts b/src/lib/app-provider/tx/resend.ts index faa19911d..a2f623484 100644 --- a/src/lib/app-provider/tx/resend.ts +++ b/src/lib/app-provider/tx/resend.ts @@ -3,9 +3,12 @@ import type { StdFee } from "@cosmjs/stargate"; import { useCallback } from "react"; import { useCurrentChain } from "../hooks"; +import { useTrack } from "lib/amplitude"; import { resendTx } from "lib/app-fns/tx/resend"; import type { HumanAddr } from "lib/types"; +import { useCatchTxError } from "./catchTxError"; + export interface ResendStreamParams { onTxSucceed?: (txHash: string) => void; onTxFailed?: () => void; @@ -15,6 +18,9 @@ export interface ResendStreamParams { export const useResendTx = () => { const { address, getSigningCosmWasmClient } = useCurrentChain(); + const { trackTxSucceed } = useTrack(); + const catchTxError = useCatchTxError(); + return useCallback( async ({ onTxSucceed, @@ -29,12 +35,16 @@ export const useResendTx = () => { return resendTx({ address: address as HumanAddr, client, - onTxSucceed, - onTxFailed, fee: estimatedFee, messages, + catchTxError, + onTxSucceed: (txHash) => { + trackTxSucceed(); + onTxSucceed?.(txHash); + }, + onTxFailed, }); }, - [address, getSigningCosmWasmClient] + [address, getSigningCosmWasmClient, trackTxSucceed, catchTxError] ); }; diff --git a/src/lib/app-provider/tx/submitProposal.ts b/src/lib/app-provider/tx/submitProposal.ts index 7083b0083..7c771b6c2 100644 --- a/src/lib/app-provider/tx/submitProposal.ts +++ b/src/lib/app-provider/tx/submitProposal.ts @@ -3,23 +3,28 @@ import type { StdFee } from "@cosmjs/stargate"; import { useCallback } from "react"; import { useCurrentChain } from "../hooks"; +import { useTrack } from "lib/amplitude"; import { submitStoreCodeProposalTx, submitWhitelistProposalTx, } from "lib/app-fns/tx/submitProposal"; import type { HumanAddr } from "lib/types"; +import { useCatchTxError } from "./catchTxError"; + export interface SubmitWhitelistProposalStreamParams { - onTxSucceed?: () => void; - onTxFailed?: () => void; estimatedFee?: StdFee; messages: EncodeObject[]; whitelistNumber: number; amountToVote: string | null; + onTxSucceed?: () => void; + onTxFailed?: () => void; } export const useSubmitWhitelistProposalTx = () => { const { address, getSigningCosmWasmClient } = useCurrentChain(); + const { trackTxSucceed } = useTrack(); + const catchTxError = useCatchTxError(); return useCallback( async ({ @@ -37,15 +42,19 @@ export const useSubmitWhitelistProposalTx = () => { return submitWhitelistProposalTx({ address: address as HumanAddr, client, - onTxSucceed, - onTxFailed, fee: estimatedFee, messages, whitelistNumber, amountToVote, + catchTxError, + onTxSucceed: () => { + trackTxSucceed(); + onTxSucceed?.(); + }, + onTxFailed, }); }, - [address, getSigningCosmWasmClient] + [address, getSigningCosmWasmClient, trackTxSucceed, catchTxError] ); }; @@ -60,6 +69,9 @@ interface SubmitStoreCodeProposalStreamParams { export const useSubmitStoreCodeProposalTx = () => { const { address, getSigningCosmWasmClient, chain } = useCurrentChain(); + const { trackTxSucceed } = useTrack(); + const catchTxError = useCatchTxError(); + return useCallback( async ({ estimatedFee, @@ -77,14 +89,24 @@ export const useSubmitStoreCodeProposalTx = () => { address: address as HumanAddr, chainName: chain.chain_name, client, - onTxSucceed, - onTxFailed, fee: estimatedFee, messages, wasmFileName, amountToVote, + catchTxError, + onTxSucceed: () => { + trackTxSucceed(); + onTxSucceed?.(); + }, + onTxFailed, }); }, - [address, chain.chain_name, getSigningCosmWasmClient] + [ + address, + chain.chain_name, + getSigningCosmWasmClient, + trackTxSucceed, + catchTxError, + ] ); }; diff --git a/src/lib/app-provider/tx/updateAdmin.ts b/src/lib/app-provider/tx/updateAdmin.ts index 861e8aaad..567534040 100644 --- a/src/lib/app-provider/tx/updateAdmin.ts +++ b/src/lib/app-provider/tx/updateAdmin.ts @@ -2,9 +2,12 @@ import type { StdFee } from "@cosmjs/stargate"; import { useCallback } from "react"; import { useCurrentChain } from "../hooks"; +import { useTrack } from "lib/amplitude"; import { updateAdminTx } from "lib/app-fns/tx/updateAdmin"; import type { Addr, ContractAddr, HumanAddr, Option } from "lib/types"; +import { useCatchTxError } from "./catchTxError"; + export interface UpdateAdminStreamParams { contractAddress: ContractAddr; newAdmin: Addr; @@ -15,6 +18,8 @@ export interface UpdateAdminStreamParams { export const useUpdateAdminTx = () => { const { address, getSigningCosmWasmClient } = useCurrentChain(); + const { trackTxSucceed } = useTrack(); + const catchTxError = useCatchTxError(); return useCallback( async ({ @@ -35,10 +40,14 @@ export const useUpdateAdminTx = () => { newAdmin, fee: estimatedFee, client, - onTxSucceed, + catchTxError, + onTxSucceed: () => { + trackTxSucceed(); + onTxSucceed?.(); + }, onTxFailed, }); }, - [address, getSigningCosmWasmClient] + [address, getSigningCosmWasmClient, trackTxSucceed, catchTxError] ); }; diff --git a/src/lib/app-provider/tx/upload.ts b/src/lib/app-provider/tx/upload.ts index 9718212f0..4f11e8a8b 100644 --- a/src/lib/app-provider/tx/upload.ts +++ b/src/lib/app-provider/tx/upload.ts @@ -3,10 +3,13 @@ import { gzip } from "node-gzip"; import { useCallback } from "react"; import { useCurrentChain } from "../hooks"; +import { useTrack } from "lib/amplitude"; import { uploadContractTx } from "lib/app-fns/tx/upload"; import type { AccessType, Addr, HumanAddr, Option } from "lib/types"; import { composeStoreCodeMsg } from "lib/utils"; +import { useCatchTxError } from "./catchTxError"; + export interface UploadTxInternalResult { codeDisplayName: string; codeId: string; @@ -29,6 +32,8 @@ export interface UploadStreamParams { export const useUploadContractTx = (isMigrate: boolean) => { const { address, getSigningCosmWasmClient } = useCurrentChain(); + const { trackTxSucceed } = useTrack(); + const catchTxError = useCatchTxError(); return useCallback( async ({ @@ -59,10 +64,14 @@ export const useUploadContractTx = (isMigrate: boolean) => { wasmFileName, fee: estimatedFee, client, - onTxSucceed, isMigrate, + catchTxError, + onTxSucceed: (txResult) => { + trackTxSucceed(); + onTxSucceed(txResult); + }, }); }, - [address, getSigningCosmWasmClient, isMigrate] + [address, getSigningCosmWasmClient, isMigrate, trackTxSucceed, catchTxError] ); }; diff --git a/src/lib/components/ConnectWalletAlert.tsx b/src/lib/components/ConnectWalletAlert.tsx index 4b4a07b82..5aa209dcd 100644 --- a/src/lib/components/ConnectWalletAlert.tsx +++ b/src/lib/components/ConnectWalletAlert.tsx @@ -9,27 +9,26 @@ import { import type { AlertProps } from "@chakra-ui/react"; import type { MouseEventHandler } from "react"; +import { useTrack } from "lib/amplitude"; import { useCurrentChain } from "lib/app-provider"; -import { AmpTrackUseClickWallet } from "lib/services/amplitude"; import { CustomIcon } from "./icon"; interface ConnectWalletAlertProps extends AlertProps { title?: string; subtitle?: string; - page?: string; } export const ConnectWalletAlert = ({ title, subtitle, - page, ...alertProps }: ConnectWalletAlertProps) => { const { address, connect } = useCurrentChain(); + const { trackUseClickWallet } = useTrack(); const onClickConnect: MouseEventHandler = async (e) => { - AmpTrackUseClickWallet(page, "alert"); + trackUseClickWallet("alert"); e.preventDefault(); await connect(); }; diff --git a/src/lib/components/CopyLink.tsx b/src/lib/components/CopyLink.tsx index 16530a7c7..896ead015 100644 --- a/src/lib/components/CopyLink.tsx +++ b/src/lib/components/CopyLink.tsx @@ -2,8 +2,8 @@ import type { FlexProps, IconProps } from "@chakra-ui/react"; import { Flex, Text, useClipboard } from "@chakra-ui/react"; import { useMemo, useState } from "react"; +import { useTrack } from "lib/amplitude"; import { useCurrentChain } from "lib/app-provider"; -import { AmpTrackCopier } from "lib/services/amplitude"; import { CustomIcon } from "./icon"; import { Tooltip } from "./Tooltip"; @@ -25,6 +25,7 @@ export const CopyLink = ({ ...flexProps }: CopyLinkProps) => { const { address } = useCurrentChain(); + const { trackUseCopier } = useTrack(); const { onCopy, hasCopied } = useClipboard(value); const [isHover, setIsHover] = useState(false); @@ -49,7 +50,7 @@ export const CopyLink = ({ align="center" display={{ base: "inline", md: "flex" }} onClick={() => { - AmpTrackCopier(amptrackSection, type); + trackUseCopier(type, amptrackSection); onCopy(); }} _hover={{ diff --git a/src/lib/components/ExplorerLink.tsx b/src/lib/components/ExplorerLink.tsx index 00c277d5e..142d4ad2d 100644 --- a/src/lib/components/ExplorerLink.tsx +++ b/src/lib/components/ExplorerLink.tsx @@ -2,12 +2,12 @@ import type { BoxProps, TextProps } from "@chakra-ui/react"; import { Box, Text, Flex } from "@chakra-ui/react"; import type { ExplorerConfig } from "config/chain/types"; +import { useTrack } from "lib/amplitude"; import type { AddressReturnType } from "lib/app-provider"; import { useCelatoneApp } from "lib/app-provider/contexts"; import { useBaseApiRoute } from "lib/app-provider/hooks/useBaseApiRoute"; import { useCurrentChain } from "lib/app-provider/hooks/useCurrentChain"; import { useMobile } from "lib/app-provider/hooks/useMediaQuery"; -import { AmpTrackMintscan } from "lib/services/amplitude"; import type { Option } from "lib/types"; import { truncate } from "lib/utils"; @@ -118,6 +118,7 @@ const LinkRender = ({ textVariant: TextProps["variant"]; openNewTab: Option; }) => { + const { trackMintScan } = useTrack(); const { currentChainId } = useCelatoneApp(); const textElement = ( { - AmpTrackMintscan(type); + trackMintScan(type); e.stopPropagation(); }} > diff --git a/src/lib/components/InputWithIcon.tsx b/src/lib/components/InputWithIcon.tsx index 2b44a377b..b628fdbee 100644 --- a/src/lib/components/InputWithIcon.tsx +++ b/src/lib/components/InputWithIcon.tsx @@ -2,7 +2,7 @@ import type { InputProps } from "@chakra-ui/react"; import { Input, InputGroup, InputLeftElement } from "@chakra-ui/react"; import type { ChangeEvent } from "react"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { CustomIcon } from "./icon"; @@ -22,21 +22,25 @@ const InputWithIcon = ({ action, autoFocus = false, onChange, -}: InputWithIconProps) => ( - - - - - AmpTrack(AmpEvent.USE_SEARCH_INPUT) : undefined} - /> - -); +}: InputWithIconProps) => { + const { track } = useTrack(); + + return ( + + + + + track(AmpEvent.USE_SEARCH_INPUT) : undefined} + /> + + ); +}; export default InputWithIcon; diff --git a/src/lib/components/StickySidebar.tsx b/src/lib/components/StickySidebar.tsx index 7af7fe432..c275fbc41 100644 --- a/src/lib/components/StickySidebar.tsx +++ b/src/lib/components/StickySidebar.tsx @@ -12,8 +12,8 @@ import { } from "@chakra-ui/react"; import Link from "next/link"; +import { useTrack } from "lib/amplitude"; import { useInternalNavigate } from "lib/app-provider"; -import { AmpTrackUseRightHelperPanel } from "lib/services/amplitude"; import { CustomIcon } from "./icon"; @@ -63,6 +63,7 @@ export const StickySidebar = ({ hasForumAlert = false, ...boxProps }: StickySidebarProps) => { + const { trackUseRightHelperPanel } = useTrack(); const navigate = useInternalNavigate(); const { title, description, toPagePath, toPageTitle, toPage, page } = metadata; @@ -131,7 +132,7 @@ export const StickySidebar = ({ {toPage && toPagePath && toPageTitle && ( { - AmpTrackUseRightHelperPanel(page, `to-${toPagePath}`); + trackUseRightHelperPanel(page, `to-${toPagePath}`); navigate({ pathname: toPagePath }); }} title={toPageTitle} diff --git a/src/lib/components/ViewPermissionAddresses.tsx b/src/lib/components/ViewPermissionAddresses.tsx index 144e89d88..99cdf9ad5 100644 --- a/src/lib/components/ViewPermissionAddresses.tsx +++ b/src/lib/components/ViewPermissionAddresses.tsx @@ -1,8 +1,8 @@ import { Button } from "@chakra-ui/react"; import { useState } from "react"; +import { useTrack } from "lib/amplitude"; import { useGetAddressType } from "lib/app-provider"; -import { AmpTrackExpand } from "lib/services/amplitude"; import type { PermissionAddresses } from "lib/types"; import { ExplorerLink } from "./ExplorerLink"; @@ -17,6 +17,7 @@ export const ViewPermissionAddresses = ({ }) => { const [viewAll, setViewAll] = useState(false); const getAddressType = useGetAddressType(); + const { trackUseExpand } = useTrack(); const showAddressses = viewAll || (typeof permissionAddresses === "object" && @@ -37,7 +38,7 @@ export const ViewPermissionAddresses = ({ - } - /> -); +}: CopyButtonProps) => { + const { track } = useTrack(); + + return ( + + track(AmpEvent.USE_COPY_BUTTON, { section: amptrackSection }) + } + leftIcon={ + hasIcon ? : undefined + } + {...buttonProps} + > + {buttonText} + + } + /> + ); +}; diff --git a/src/lib/components/json-schema/AttachSchemaCard.tsx b/src/lib/components/json-schema/AttachSchemaCard.tsx index 6510f91bb..8d0dc0863 100644 --- a/src/lib/components/json-schema/AttachSchemaCard.tsx +++ b/src/lib/components/json-schema/AttachSchemaCard.tsx @@ -1,7 +1,9 @@ import { Button, Flex, IconButton, Text } from "@chakra-ui/react"; +import { useCallback } from "react"; +import { RemoveSchemaModal } from "../modal/RemoveSchemaModal"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { CustomIcon } from "lib/components/icon"; -import { useSchemaStore } from "lib/providers/store"; import type { CodeSchema } from "lib/stores/schema"; import type { Option } from "lib/types"; @@ -12,7 +14,7 @@ interface AttachSchemaCardProps { codeId: string; codeHash: string; schema: Option; - openDrawer: () => void; + openModal: () => void; } export const AttachSchemaCard = ({ @@ -20,9 +22,19 @@ export const AttachSchemaCard = ({ codeId, codeHash, schema, - openDrawer, + openModal, }: AttachSchemaCardProps) => { - const { deleteSchema } = useSchemaStore(); + const { track } = useTrack(); + + const handleAttach = useCallback(() => { + openModal(); + track(AmpEvent.USE_ATTACHED_JSON_MODAL); + }, [track, openModal]); + + const handleReattach = useCallback(() => { + openModal(); + track(AmpEvent.USE_EDIT_ATTACHED_JSON); + }, [track, openModal]); return ( Attach JSON Schema - @@ -55,15 +67,21 @@ export const AttachSchemaCard = ({ - - deleteSchema(codeHash)} - icon={} + } + aria-label="delete schema" + /> + } /> diff --git a/src/lib/components/json-schema/EditSchemaButtons.tsx b/src/lib/components/json-schema/EditSchemaButtons.tsx index 84d999832..9044722a6 100644 --- a/src/lib/components/json-schema/EditSchemaButtons.tsx +++ b/src/lib/components/json-schema/EditSchemaButtons.tsx @@ -1,45 +1,56 @@ import { Flex, IconButton } from "@chakra-ui/react"; +import { useCallback } from "react"; import { CustomIcon } from "../icon"; import { RemoveSchemaModal } from "../modal/RemoveSchemaModal"; import { Tooltip } from "../Tooltip"; +import { AmpEvent, useTrack } from "lib/amplitude"; interface EditSchemaButtonsProps { codeId: string; codeHash: string; - openDrawer: () => void; + openModal: () => void; } export const EditSchemaButtons = ({ codeId, codeHash, - openDrawer, -}: EditSchemaButtonsProps) => ( - - - } - aria-label="reattach schema" + openModal, +}: EditSchemaButtonsProps) => { + const { track } = useTrack(); + + const handleReattach = useCallback(() => { + track(AmpEvent.USE_EDIT_ATTACHED_JSON); + openModal(); + }, [openModal, track]); + + return ( + + + } + aria-label="reattach schema" + /> + + + } + aria-label="delete schema" + /> + + } /> - - - } - aria-label="delete schema" - /> - - } - /> - -); + + ); +}; diff --git a/src/lib/components/json-schema/JsonSchemaModal.tsx b/src/lib/components/json-schema/JsonSchemaModal.tsx index 92ded001a..7aa9176d6 100644 --- a/src/lib/components/json-schema/JsonSchemaModal.tsx +++ b/src/lib/components/json-schema/JsonSchemaModal.tsx @@ -51,6 +51,7 @@ export const JsonSchemaModal = ({ diff --git a/src/lib/components/json-schema/MessageInputSwitch.tsx b/src/lib/components/json-schema/MessageInputSwitch.tsx index 86c4a4b24..2879a7f7e 100644 --- a/src/lib/components/json-schema/MessageInputSwitch.tsx +++ b/src/lib/components/json-schema/MessageInputSwitch.tsx @@ -1,8 +1,9 @@ import { Flex } from "@chakra-ui/react"; -import { useMemo } from "react"; +import { useCallback, useMemo } from "react"; import type { CSSProperties, Dispatch, SetStateAction } from "react"; import { Tooltip } from "../Tooltip"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { MotionBox } from "lib/components/MotionBox"; import type { Option } from "lib/types"; @@ -38,14 +39,23 @@ export const MessageInputSwitch = < tooltipLabel = "Select or fill code id first", ml, isOutput = false, - onTabChange, + onTabChange: onTabChangeProps, }: MessageInputSwitchProps) => { + const { track } = useTrack(); const tabs = useMemo( () => Object.values(isOutput ? OutputMessageTabs : MessageTabs), [isOutput] ); const activeIndex = currentTab ? tabs.indexOf(currentTab) : 0; + const onTabChange = useCallback( + (tab: T) => { + track(AmpEvent.USE_SCHEMA_TOGGLE, { tab }); + onTabChangeProps(tab); + }, + [onTabChangeProps, track] + ); + /** * @todos current implementation of sliding box dimensions and position is hardcoded due to issues with ref, improve this later */ diff --git a/src/lib/components/json-schema/UploadTemplate.tsx b/src/lib/components/json-schema/UploadTemplate.tsx index d714b673c..581729de7 100644 --- a/src/lib/components/json-schema/UploadTemplate.tsx +++ b/src/lib/components/json-schema/UploadTemplate.tsx @@ -11,6 +11,7 @@ import type { Dispatch } from "react"; import { useMemo, useCallback, useReducer, useState } from "react"; import { CustomIcon } from "../icon"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { DropZone } from "lib/components/dropzone"; import type { ResponseState } from "lib/components/forms"; import { TextInput } from "lib/components/forms"; @@ -181,6 +182,7 @@ const MethodRender = ({ interface UploadTemplateInterface { codeHash: string; codeId: string; + isReattach: boolean; closeDrawer: () => void; onSchemaSave?: () => void; } @@ -188,9 +190,11 @@ interface UploadTemplateInterface { export const UploadTemplate = ({ codeHash, codeId, + isReattach, closeDrawer, onSchemaSave, }: UploadTemplateInterface) => { + const { track } = useTrack(); const { saveNewSchema } = useSchemaStore(); const [method, setMethod] = useState(Method.UPLOAD_FILE); const [jsonState, dispatchJsonState] = useReducer(reducer, initialJsonState); @@ -241,6 +245,7 @@ export const UploadTemplate = ({ }); } saveNewSchema(codeHash, codeId, JSON.parse(schemaString)); + track(AmpEvent.ACTION_ATTACH_JSON, { method, isReattach }); toast({ title: `Attached JSON Schema`, status: "success", @@ -260,7 +265,9 @@ export const UploadTemplate = ({ codeId, jsonState, method, + isReattach, onSchemaSave, + track, saveNewSchema, toast, ]); diff --git a/src/lib/components/json-schema/section/SchemaInputSection.tsx b/src/lib/components/json-schema/section/SchemaInputSection.tsx index e5858d384..9a954f160 100644 --- a/src/lib/components/json-schema/section/SchemaInputSection.tsx +++ b/src/lib/components/json-schema/section/SchemaInputSection.tsx @@ -2,11 +2,13 @@ import { Button, Flex, Text, useDisclosure } from "@chakra-ui/react"; import type { RJSFSchema, RJSFValidationError } from "@rjsf/utils"; import { capitalize } from "lodash"; import { observer } from "mobx-react-lite"; +import { useCallback } from "react"; import { AttachSchemaCard } from "../AttachSchemaCard"; import { JsonSchemaForm } from "../form"; import { JsonSchemaModal } from "../JsonSchemaModal"; import { ViewSchemaModal } from "../view/ViewSchemaModal"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { CustomIcon } from "lib/components/icon"; import type { CodeSchema } from "lib/stores/schema"; import type { Option } from "lib/types"; @@ -32,9 +34,15 @@ export const SchemaInputSection = observer( onSchemaSave, }: SchemaSectionProps) => { const { isOpen, onClose, onOpen } = useDisclosure(); + const { track } = useTrack(); const msgSchema = jsonSchema?.[type]; const prettyType = capitalize(type); + const handleReattach = useCallback(() => { + onOpen(); + track(AmpEvent.USE_EDIT_ATTACHED_JSON); + }, [onOpen, track]); + return ( <> )} @@ -122,7 +130,7 @@ export const SchemaInputSection = observer( - diff --git a/src/lib/components/json-schema/upload/UploadSchema.tsx b/src/lib/components/json-schema/upload/UploadSchema.tsx index 179d7d5ba..6fcd80af9 100644 --- a/src/lib/components/json-schema/upload/UploadSchema.tsx +++ b/src/lib/components/json-schema/upload/UploadSchema.tsx @@ -26,7 +26,7 @@ export const UploadSchema = ({ codeId={codeId} codeHash={codeHash} schema={schema} - openDrawer={onOpen} + openModal={onOpen} /> { const { isOpen, onClose, onOpen } = useDisclosure(); + const { track } = useTrack(); + + const handleView = useCallback(() => { + onOpen(); + track(AmpEvent.USE_VIEW_ATTACHED_JSON, { tab: ALL_TABS[0] }); + }, [onOpen, track]); + + const trackTabOnChange = useCallback( + (index: number) => { + track(AmpEvent.USE_VIEW_ATTACHED_JSON, { + tab: ALL_TABS[index], + }); + }, + [track] + ); + + const trackUseViewJsonInCodeDetail = useCallback( + () => track(AmpEvent.USE_VIEW_JSON_IN_CODE_DETAIL), + [track] + ); + return ( <> {isIcon ? ( @@ -68,14 +93,14 @@ export const ViewSchemaModal = ({ } aria-label="view schema" /> ) : ( - )} @@ -95,7 +120,10 @@ export const ViewSchemaModal = ({ View JSON Schema for code ID “{codeId}” - + - -); +export const ViewMore = ({ onClick }: ViewMoreProps) => { + const { track } = useTrack(); + return ( + + + + ); +}; diff --git a/src/lib/components/table/codes/CodeNameCell.tsx b/src/lib/components/table/codes/CodeNameCell.tsx index 27ac8ff99..03304e0f8 100644 --- a/src/lib/components/table/codes/CodeNameCell.tsx +++ b/src/lib/components/table/codes/CodeNameCell.tsx @@ -1,10 +1,10 @@ import { useToast } from "@chakra-ui/react"; import { EditableCell } from "../EditableCell"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useCelatoneApp } from "lib/app-provider"; import { CustomIcon } from "lib/components/icon"; import { useCodeStore } from "lib/providers/store"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { CodeLocalInfo } from "lib/stores/code"; interface CodeNameCellProps { @@ -16,12 +16,13 @@ export const CodeNameCell = ({ code, isReadOnly = false, }: CodeNameCellProps) => { + const { track } = useTrack(); const { constants } = useCelatoneApp(); const toast = useToast(); const { updateCodeInfo } = useCodeStore(); const onSave = (inputValue?: string) => { - AmpTrack(AmpEvent.USE_QUICK_EDIT_CODE); + track(AmpEvent.USE_QUICK_EDIT_CODE); updateCodeInfo(code.id, code.uploader, inputValue); toast({ title: "New Code Name Saved", diff --git a/src/lib/components/table/contracts/ContractNameCell.tsx b/src/lib/components/table/contracts/ContractNameCell.tsx index db44b80b3..b10fba429 100644 --- a/src/lib/components/table/contracts/ContractNameCell.tsx +++ b/src/lib/components/table/contracts/ContractNameCell.tsx @@ -1,7 +1,7 @@ import { EditableCell } from "../EditableCell"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useCelatoneApp } from "lib/app-provider"; import { useHandleContractSave } from "lib/hooks/useHandleSave"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { ContractLocalInfo } from "lib/stores/contract"; interface ContractNameCellProps { @@ -13,13 +13,14 @@ export const ContractNameCell = ({ contractLocalInfo, isReadOnly = false, }: ContractNameCellProps) => { + const { track } = useTrack(); const { constants } = useCelatoneApp(); const onSave = useHandleContractSave({ title: "Changed name successfully!", contractAddress: contractLocalInfo.contractAddress, instantiator: contractLocalInfo.instantiator, label: contractLocalInfo.label, - actions: () => AmpTrack(AmpEvent.USE_QUICK_EDIT_CONTRACT), + actions: () => track(AmpEvent.USE_QUICK_EDIT_CONTRACT), }); return ( ( getTagsDefault(contractLocalInfo.tags) ); @@ -24,7 +25,7 @@ export function EditTags({ contractLocalInfo }: EditTagsProps) { instantiator: contractLocalInfo.instantiator, label: contractLocalInfo.label, tags: tagResult, - actions: () => AmpTrack(AmpEvent.CONTRACT_EDIT_TAGS), + actions: () => track(AmpEvent.CONTRACT_EDIT_TAGS), }); return ( diff --git a/src/lib/components/table/proposals/ProposalsTableRow.tsx b/src/lib/components/table/proposals/ProposalsTableRow.tsx index 7eb5ebdd0..fd4d71829 100644 --- a/src/lib/components/table/proposals/ProposalsTableRow.tsx +++ b/src/lib/components/table/proposals/ProposalsTableRow.tsx @@ -2,11 +2,11 @@ import type { DividerProps, GridProps } from "@chakra-ui/react"; import { Grid } from "@chakra-ui/react"; import { TableRow, TableRowFreeze } from "../tableComponents"; +import { useTrack } from "lib/amplitude"; import { useBaseApiRoute, useCelatoneApp } from "lib/app-provider"; import { ExplorerLink, getNavigationUrl } from "lib/components/ExplorerLink"; import { StopPropagationBox } from "lib/components/StopPropagationBox"; import { Proposer } from "lib/components/table/proposals/Proposer"; -import { AmpTrackMintscan } from "lib/services/amplitude"; import type { Option, Proposal } from "lib/types"; import { ProposalStatus } from "lib/types"; import { openNewTab } from "lib/utils"; @@ -31,6 +31,7 @@ export const ProposalsTableRow = ({ chainConfig: { explorerLink }, } = useCelatoneApp(); const lcdEndpoint = useBaseApiRoute("rest"); + const { trackMintScan } = useTrack(); // TODO - Revisit split columnsWidth const columnsWidth = templateColumns?.toString().split(" "); @@ -53,7 +54,7 @@ export const ProposalsTableRow = ({ onClick={ !isDepositFailed ? () => { - AmpTrackMintscan("proposal-detail", { + trackMintScan("proposal-detail", { type: proposal.type, status: proposal.status, }); diff --git a/src/lib/components/upload/InstantiatePermissionRadio.tsx b/src/lib/components/upload/InstantiatePermissionRadio.tsx index d562dc1cc..b9001f1b5 100644 --- a/src/lib/components/upload/InstantiatePermissionRadio.tsx +++ b/src/lib/components/upload/InstantiatePermissionRadio.tsx @@ -5,13 +5,9 @@ import { useController, useFieldArray, useWatch } from "react-hook-form"; import { AddressInput } from "../AddressInput"; import { AssignMe } from "../AssignMe"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useCelatoneApp, useCurrentChain } from "lib/app-provider"; import { CustomIcon } from "lib/components/icon"; -import { - AmpEvent, - AmpTrack, - AmpTrackUseInstantiatePermission, -} from "lib/services/amplitude"; import type { Addr, UploadSectionState } from "lib/types"; import { AccessType } from "lib/types"; @@ -19,7 +15,6 @@ interface InstantiatePermissionRadioProps { control: Control; setValue: UseFormSetValue; trigger: UseFormTrigger; - page?: string; } interface PermissionRadioProps { @@ -38,8 +33,8 @@ export const InstantiatePermissionRadio = ({ control, setValue, trigger, - page, }: InstantiatePermissionRadioProps) => { + const { track, trackUseInstantiatePermission } = useTrack(); const { address: walletAddress } = useCurrentChain(); const { chainConfig: { @@ -68,17 +63,14 @@ export const InstantiatePermissionRadio = ({ const emptyAddressesLength = addresses.filter( (addr) => addr.address.trim().length === 0 ).length; - if (page) { - AmpTrackUseInstantiatePermission( - page, - AccessType[permission], - emptyAddressesLength, - addresses.length - emptyAddressesLength - ); - } + trackUseInstantiatePermission( + AccessType[permission], + emptyAddressesLength, + addresses.length - emptyAddressesLength + ); // Run this effect only when the amount of address input or selected permission changes // eslint-disable-next-line react-hooks/exhaustive-deps - }, [addresses.length, page, permission]); + }, [addresses.length, permission]); return ( { - AmpTrack(AmpEvent.USE_ASSIGN_ME); + track(AmpEvent.USE_ASSIGN_ME); setValue( `addresses.${idx}.address`, walletAddress as Addr diff --git a/src/lib/components/upload/UploadSection.tsx b/src/lib/components/upload/UploadSection.tsx index 7356d3a75..586837231 100644 --- a/src/lib/components/upload/UploadSection.tsx +++ b/src/lib/components/upload/UploadSection.tsx @@ -5,6 +5,7 @@ import { useForm } from "react-hook-form"; import { DropZone } from "../dropzone"; import { ControllerInput } from "../forms"; +import { AmpEvent, useTrack } from "lib/amplitude"; import type { UploadSucceedCallback, UploadTxInternalResult, @@ -22,7 +23,6 @@ import { CustomIcon } from "lib/components/icon"; import { useGetMaxLengthError } from "lib/hooks"; import { useCodeStore } from "lib/providers/store"; import { useTxBroadcast } from "lib/providers/tx-broadcast"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { Addr, HumanAddr, @@ -48,6 +48,7 @@ export const UploadSection = ({ onComplete, isMigrate = false, }: UploadSectionProps) => { + const { track } = useTrack(); const { constants, chainConfig: { @@ -148,7 +149,7 @@ export const UploadSection = ({ const proceed = useCallback(async () => { if (address) { - AmpTrack(AmpEvent.ACTION_UPLOAD); + track(AmpEvent.ACTION_UPLOAD); const stream = await postUploadTx({ wasmFileName: wasmFile?.name, wasmCode: wasmFile?.arrayBuffer(), @@ -174,6 +175,7 @@ export const UploadSection = ({ }, [ address, postUploadTx, + track, wasmFile, addresses, permission, diff --git a/src/lib/data/constant.ts b/src/lib/data/constant.ts index 44e3073b2..047ba327d 100644 --- a/src/lib/data/constant.ts +++ b/src/lib/data/constant.ts @@ -44,3 +44,11 @@ export const DEFAULT_TX_FILTERS = { }; export const UPPERBOUND_COUNT = 10000; + +export enum StorageKeys { + NavSidebar = "nav-sidebar", + DevSidebar = "dev-sidebar", + ProjectSidebar = "project-sidebar", + Wallets = "wallets", + Networks = "networks", +} diff --git a/src/lib/hooks/index.ts b/src/lib/hooks/index.ts index cd025d8c0..0864e2797 100644 --- a/src/lib/hooks/index.ts +++ b/src/lib/hooks/index.ts @@ -9,3 +9,4 @@ export * from "./useUserKey"; export * from "./useOpenTab"; export * from "./useIsCurrentPage"; export * from "./useGetMaxLengthError"; +export * from "./useLocalStorage"; diff --git a/src/lib/hooks/useLocalStorage.ts b/src/lib/hooks/useLocalStorage.ts index 6f83fa03f..3c9c80ae8 100644 --- a/src/lib/hooks/useLocalStorage.ts +++ b/src/lib/hooks/useLocalStorage.ts @@ -1,29 +1,19 @@ -/* eslint-disable no-console */ import type { Dispatch, SetStateAction } from "react"; import { useEffect, useState } from "react"; +import { setItem, getItem } from "lib/utils"; + type PersistedState = [T, Dispatch>]; export const useLocalStorage = ( key: string, defaultValue: T ): PersistedState => { - const [storedValue, setStoredValue] = useState(() => { - try { - const value = window.localStorage.getItem(key); - return value ? (JSON.parse(value) as T) : defaultValue; - } catch (e) { - return defaultValue as T; - } - }); + const [storedValue, setStoredValue] = useState(() => + getItem(key, defaultValue) + ); - useEffect(() => { - try { - window.localStorage.setItem(key, JSON.stringify(storedValue)); - } catch (e) { - // I want application to not crush, but don't care about the message - } - }, [key, storedValue]); + useEffect(() => setItem(key, storedValue), [key, storedValue]); return [storedValue, setStoredValue]; }; diff --git a/src/lib/layout/Footer.tsx b/src/lib/layout/Footer.tsx index 1874e4202..8756dfa14 100644 --- a/src/lib/layout/Footer.tsx +++ b/src/lib/layout/Footer.tsx @@ -3,9 +3,9 @@ import { Flex, Text, Image } from "@chakra-ui/react"; import Link from "next/link"; import { CURR_THEME } from "env"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { CustomIcon } from "lib/components/icon"; import type { IconKeys } from "lib/components/icon"; -import { AmpEvent, AmpTrack, AmpTrackSocial } from "lib/services/amplitude"; interface SocialMenuType { url: string; @@ -65,53 +65,60 @@ const SocialMenuRender = ({ }: { isThemed?: boolean; iconSize: IconProps["boxSize"]; -}) => ( - <> - {(isThemed ? themedSocial : socialMenu).map((item) => ( - { - AmpTrackSocial(item.url); - }} - > - { + const { trackSocial } = useTrack(); + return ( + <> + {(isThemed ? themedSocial : socialMenu).map((item) => ( + { + trackSocial(item.url); + }} > - - - - ))} - -); + + + + + ))} + + ); +}; -const AllesFeedback = () => ( - AmpTrack(AmpEvent.FEEDBACK)} - > - { + const { track } = useTrack(); + + return ( + track(AmpEvent.FEEDBACK)} > - - - Feedback - - - -); + + + + Feedback + + + + ); +}; const IconLink = ({ href, @@ -123,43 +130,47 @@ const IconLink = ({ icon: IconKeys; text1: string; text2: string; -}) => ( - AmpTrack(AmpEvent.ALLESLABS)} - > - { + const { track } = useTrack(); + + return ( + track(AmpEvent.ALLESLABS)} > - - - {text1} - - {text2} + + + + {text1} + + {text2} + - - - -); + + + ); +}; const Footer = () => { const isThemed = CURR_THEME.footer; diff --git a/src/lib/layout/Header.tsx b/src/lib/layout/Header.tsx index deb98929a..166dba493 100644 --- a/src/lib/layout/Header.tsx +++ b/src/lib/layout/Header.tsx @@ -10,18 +10,20 @@ import { import { CHAIN_CONFIGS } from "config/chain"; import { CURR_THEME } from "env"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useCelatoneApp, useSelectChain } from "lib/app-provider"; import { AppLink } from "lib/components/AppLink"; import { FaucetBtn } from "lib/components/button"; import { CustomIcon } from "lib/components/icon"; import { WalletSection } from "lib/components/Wallet"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import Searchbar from "./Searchbar"; const Header = () => { + const { track } = useTrack(); const { availableChainIds, currentChainId } = useCelatoneApp(); const selectChain = useSelectChain(); + return ( { - AmpTrack(AmpEvent.USE_SELECT_NETWORK)}> + track(AmpEvent.USE_SELECT_NETWORK)}> { const [displayResults, setDisplayResults] = useState(false); const [isTyping, setIsTyping] = useState(false); const [cursor, setCursor] = useState(); + const { trackUseMainSearch } = useTrack(); const { chainConfig: { @@ -255,7 +256,7 @@ const Searchbar = () => { const handleSelectResult = useCallback( (type?: SearchResultType, isClick = false) => { - AmpTrackUseMainSearch(isClick); + trackUseMainSearch(isClick); const routeOptions = getRouteOptions(type); if (routeOptions) { navigate({ @@ -266,7 +267,7 @@ const Searchbar = () => { setKeyword(""); } }, - [metadata.icns.address, keyword, navigate] + [trackUseMainSearch, navigate, metadata.icns.address, keyword] ); const handleOnKeyEnter = useCallback( diff --git a/src/lib/layout/SubHeader.tsx b/src/lib/layout/SubHeader.tsx index 4696d0e22..46100bd98 100644 --- a/src/lib/layout/SubHeader.tsx +++ b/src/lib/layout/SubHeader.tsx @@ -1,5 +1,7 @@ import { Flex, Text } from "@chakra-ui/react"; +import { useCallback } from "react"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { usePoolConfig, useGovConfig, useWasmConfig } from "lib/app-provider"; import { AppLink } from "lib/components/AppLink"; import type { IconKeys } from "lib/components/icon"; @@ -16,6 +18,7 @@ const SubHeader = () => { const wasmConfig = useWasmConfig({ shouldRedirect: false }); const poolConfig = usePoolConfig({ shouldRedirect: false }); const govConfig = useGovConfig({ shouldRedirect: false }); + const { track } = useTrack(); const subHeaderMenu: SubHeaderMenuInfo[] = [ { name: "Overview", slug: "/", icon: "home" }, @@ -38,11 +41,22 @@ const SubHeader = () => { const activeColor = "primary.light"; + const trackOnClick = useCallback( + (tab: string) => { + track(AmpEvent.USE_TOPBAR, { tab }); + }, + [track] + ); + return ( {subHeaderMenu.map((item) => ( - + trackOnClick(item.name)} + > { + const { track } = useTrack(); const { isOpen, onOpen, onClose } = useDisclosure(); const { currentChainId, availableChainIds } = useCelatoneApp(); const isCurrentPage = useIsCurrentPage(); @@ -40,6 +41,7 @@ export const NavDrawer = () => { const mobileMenu: MenuInfo[] = [ { category: "Overview", + slug: "overview", submenu: [ { name: "Overview", slug: "/", icon: "home" }, { @@ -74,6 +76,7 @@ export const NavDrawer = () => { if (publicProject.enabled) { mobileMenu.push({ category: "Public Projects", + slug: "public-projects", submenu: [ ...getSavedPublicProjects().map((list) => ({ name: list.name, @@ -100,7 +103,7 @@ export const NavDrawer = () => { - AmpTrack(AmpEvent.USE_SELECT_NETWORK)}> + track(AmpEvent.USE_SELECT_NETWORK)}> { href={submenu.slug} key={submenu.slug} onClick={() => { - AmpTrack(AmpEvent.USE_SIDEBAR); + track(AmpEvent.USE_SIDEBAR); onClose(); }} > diff --git a/src/lib/layout/navbar/Collapse.tsx b/src/lib/layout/navbar/Collapse.tsx index feaf064ce..3c4c09119 100644 --- a/src/lib/layout/navbar/Collapse.tsx +++ b/src/lib/layout/navbar/Collapse.tsx @@ -1,10 +1,10 @@ import { Box, Flex, IconButton, Image } from "@chakra-ui/react"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useMobile } from "lib/app-provider"; import { AppLink } from "lib/components/AppLink"; import { CustomIcon } from "lib/components/icon"; import { Tooltip } from "lib/components/Tooltip"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { NavMenuProps, SubmenuInfo } from "./type"; @@ -59,6 +59,7 @@ export const CollapseNavMenu = ({ setIsExpand, }: NavMenuProps) => { const isMobile = useMobile(); + const { track } = useTrack(); return ( @@ -105,7 +106,7 @@ export const CollapseNavMenu = ({ AmpTrack(AmpEvent.USE_SIDEBAR)} + onClick={() => track(AmpEvent.USE_SIDEBAR)} > AmpTrack(AmpEvent.USE_SIDEBAR)} + onClick={() => track(AmpEvent.USE_SIDEBAR)} > boolean; } -const SubMenuRenderer = ({ isCurrentPage, submenu }: SubMenuProps) => ( - <> - {submenu.map((subitem) => - subitem.isDisable ? ( - -
+const SubMenuRender = ({ isCurrentPage, submenu }: SubMenuProps) => { + const { track } = useTrack(); + + return ( + <> + {submenu.map((subitem) => + subitem.isDisable ? ( + +
+ +
+
+ ) : ( + { + track(AmpEvent.USE_SIDEBAR); + subitem.trackEvent?.(); + }} + > -
-
- ) : ( - AmpTrack(AmpEvent.USE_SIDEBAR)} - > - - - ) - )} - -); +
+ ) + )} + + ); +}; + +interface NavbarRenderProps { + menuInfo: MenuInfo; + isCurrentPage: (slug: string) => boolean; +} + +const NavbarRender = ({ menuInfo, isCurrentPage }: NavbarRenderProps) => { + const { track } = useTrack(); + const [isExpand, setIsExpand] = useLocalStorage(menuInfo.slug, true); + const defaultIndex = isExpand ? [0] : []; + + const handleChange = (index: number[]) => { + setIsExpand(index.includes(0)); + }; + + return ( + + + + + {menuInfo.category} + + + + + + {menuInfo.subSection && ( + + {menuInfo.subSection.map((subitem) => ( +
+ + {subitem.category} + + {subitem.submenu.map((submenu) => + submenu.isDisable ? ( + +
+ +
+
+ ) : ( + track(AmpEvent.USE_SIDEBAR)} + > + + + ) + )} +
+ ))} +
+ )} +
+
+
+ ); +}; export const ExpandNavMenu = ({ navMenu, isCurrentPage, setIsExpand, -}: NavMenuProps) => ( - - {navMenu.map((item) => ( - - - {item.category === "Your Account" ? ( - - - {item.category} - +}: NavMenuProps) => { + const yourAccountMenu = navMenu[0]; + const restNavMenu = navMenu.slice(1); - - - ) : ( - - - {item.category} - - - - )} - {item.category === "Your Account" ? ( - - ) : ( - - - {item.subSection && ( - - {item.subSection.map((subitem) => ( -
- - {subitem.category} - - {subitem.submenu.map((submenu) => - submenu.isDisable ? ( - -
- -
-
- ) : ( - AmpTrack(AmpEvent.USE_SIDEBAR)} - > - - - ) - )} -
- ))} -
- )} -
- )} -
-
- ))} -
-); + return ( + + + + {yourAccountMenu.category} + + + + + + + {restNavMenu.map((item) => ( + + ))} + + ); +}; diff --git a/src/lib/layout/navbar/index.tsx b/src/lib/layout/navbar/index.tsx index 028d714bb..c9ad548a2 100644 --- a/src/lib/layout/navbar/index.tsx +++ b/src/lib/layout/navbar/index.tsx @@ -1,14 +1,15 @@ import { Flex } from "@chakra-ui/react"; import { observer } from "mobx-react-lite"; -import type { Dispatch, SetStateAction } from "react"; +import { useMemo, type Dispatch, type SetStateAction } from "react"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { usePublicProjectConfig, useCurrentChain, useWasmConfig, } from "lib/app-provider"; import type { IconKeys } from "lib/components/icon"; -import { INSTANTIATED_LIST_NAME, SAVED_LIST_NAME } from "lib/data"; +import { INSTANTIATED_LIST_NAME, SAVED_LIST_NAME, StorageKeys } from "lib/data"; import { useIsCurrentPage } from "lib/hooks"; import { usePublicProjectStore } from "lib/providers/store"; import { formatSlugName, getListIcon } from "lib/utils"; @@ -27,121 +28,137 @@ const Navbar = observer(({ isExpand, setIsExpand }: NavbarProps) => { const publicProject = usePublicProjectConfig({ shouldRedirect: false }); const isCurrentPage = useIsCurrentPage(); const wasm = useWasmConfig({ shouldRedirect: false }); + const { track } = useTrack(); const { address } = useCurrentChain(); - const navMenu: MenuInfo[] = [ - { - category: "Your Account", - submenu: [ - { - name: "Past Transactions", - slug: "/past-txs", - icon: "history" as IconKeys, - }, - { - name: "Your Account Details", - slug: `/accounts/${address}`, - icon: "admin" as IconKeys, - isDisable: !address, - tooltipText: - "You need to connect wallet to view your account details.", - }, - ], - }, - ...(publicProject.enabled - ? [ + const navMenu: MenuInfo[] = useMemo( + () => [ + { + category: "Your Account", + slug: "your-account", + submenu: [ { - category: "Public Projects", - submenu: [ - ...getSavedPublicProjects().map((list) => ({ - name: list.name, - slug: `/projects/${list.slug}`, - logo: list.logo as IconKeys, - })), - { - name: "View All Projects", - slug: "/projects", - icon: "public-project" as IconKeys, - }, - ], + name: "Past Transactions", + slug: "/past-txs", + icon: "history" as IconKeys, }, - ] - : []), - ...(wasm.enabled - ? [ { - category: "Developer Tools", - submenu: [ - { - name: "Deploy Contract", - slug: "/deploy", - icon: "add-new" as IconKeys, - }, - { - name: "Query", - slug: "/query", - icon: "query" as IconKeys, - }, - { - name: "Execute", - slug: "/execute", - icon: "execute" as IconKeys, - }, - { - name: "Migrate", - slug: "/migrate", - icon: "migrate" as IconKeys, - }, - // { - // name: "Recent Activities", - // slug: "/", - // icon: "list" as IconKeys, - // }, - ], - subSection: [ - { - category: "This Wallet", - submenu: [ - { - name: "My Stored Codes", - slug: "/stored-codes", - icon: "code" as IconKeys, - }, - { - name: INSTANTIATED_LIST_NAME, - slug: `/contract-lists/${formatSlugName( - INSTANTIATED_LIST_NAME - )}`, - icon: getListIcon(INSTANTIATED_LIST_NAME), - }, - ], - }, - { - category: "This Device", - submenu: [ - { - name: "Saved Codes", - slug: "/saved-codes", - icon: "code" as IconKeys, - }, - { - name: SAVED_LIST_NAME, - slug: `/contract-lists/${formatSlugName(SAVED_LIST_NAME)}`, - icon: "contract-address" as IconKeys, - }, - { - name: "View All Contract List", - slug: "/contract-lists", - icon: "more" as IconKeys, - }, - ], - }, - ], + name: "Your Account Details", + slug: `/accounts/${address}`, + icon: "admin" as IconKeys, + isDisable: !address, + tooltipText: + "You need to connect wallet to view your account details.", + trackEvent: () => track(AmpEvent.USE_TO_YOUR_ACCOUNT), }, - ] - : []), - ]; + ], + }, + ...(publicProject.enabled + ? [ + { + category: "Public Projects", + slug: StorageKeys.ProjectSidebar, + submenu: [ + ...getSavedPublicProjects().map((list) => ({ + name: list.name, + slug: `/projects/${list.slug}`, + logo: list.logo as IconKeys, + })), + { + name: "View All Projects", + slug: "/projects", + icon: "public-project" as IconKeys, + }, + ], + }, + ] + : []), + ...(wasm.enabled + ? [ + { + category: "Developer Tools", + slug: StorageKeys.DevSidebar, + submenu: [ + { + name: "Deploy Contract", + slug: "/deploy", + icon: "add-new" as IconKeys, + }, + { + name: "Query", + slug: "/query", + icon: "query" as IconKeys, + }, + { + name: "Execute", + slug: "/execute", + icon: "execute" as IconKeys, + }, + { + name: "Migrate", + slug: "/migrate", + icon: "migrate" as IconKeys, + }, + // { + // name: "Recent Activities", + // slug: "/", + // icon: "list" as IconKeys, + // }, + ], + subSection: [ + { + category: "This Wallet", + submenu: [ + { + name: "My Stored Codes", + slug: "/stored-codes", + icon: "code" as IconKeys, + }, + { + name: INSTANTIATED_LIST_NAME, + slug: `/contract-lists/${formatSlugName( + INSTANTIATED_LIST_NAME + )}`, + icon: getListIcon(INSTANTIATED_LIST_NAME), + }, + ], + }, + { + category: "This Device", + submenu: [ + { + name: "Saved Codes", + slug: "/saved-codes", + icon: "code" as IconKeys, + }, + { + name: SAVED_LIST_NAME, + slug: `/contract-lists/${formatSlugName( + SAVED_LIST_NAME + )}`, + icon: "contract-address" as IconKeys, + }, + { + name: "View All Contract List", + slug: "/contract-lists", + icon: "more" as IconKeys, + }, + ], + }, + ], + }, + ] + : []), + ], + [ + address, + getSavedPublicProjects, + publicProject.enabled, + track, + wasm.enabled, + ] + ); return ( diff --git a/src/lib/layout/navbar/type.ts b/src/lib/layout/navbar/type.ts index 52a4e0681..8f01065ae 100644 --- a/src/lib/layout/navbar/type.ts +++ b/src/lib/layout/navbar/type.ts @@ -7,6 +7,7 @@ export interface SubmenuInfo { logo?: string; isDisable?: boolean; tooltipText?: string; + trackEvent?: () => void; } export interface SubSection { @@ -16,6 +17,7 @@ export interface SubSection { export interface MenuInfo { category: string; + slug: string; submenu: SubmenuInfo[]; subSection?: SubSection[]; } diff --git a/src/lib/pages/account-details/components/asset/index.tsx b/src/lib/pages/account-details/components/asset/index.tsx index 4150029b9..3ae441814 100644 --- a/src/lib/pages/account-details/components/asset/index.tsx +++ b/src/lib/pages/account-details/components/asset/index.tsx @@ -2,6 +2,7 @@ import { Flex, Grid, Text, Button } from "@chakra-ui/react"; import type { Big } from "big.js"; import big from "big.js"; +import { useTrack } from "lib/amplitude"; import { useMobile } from "lib/app-provider"; import { CustomIcon } from "lib/components/icon"; import { Loading } from "lib/components/Loading"; @@ -10,7 +11,6 @@ import { TableTitle, ViewMore } from "lib/components/table"; import { TokenCard } from "lib/components/TokenCard"; import { useOpenAssetTab } from "lib/hooks"; import { useUserAssetInfos } from "lib/pages/account-details/data"; -import { AmpTrackViewJson } from "lib/services/amplitude"; import type { BalanceWithAssetInfo, HumanAddr, Option, USD } from "lib/types"; import { calTotalValue, formatPrice } from "lib/utils"; @@ -70,6 +70,7 @@ const AssetTitle = ({ const AssetCta = ({ walletAddress, totalAsset }: AssetCtaProps) => { const { unsupportedAssets } = useUserAssetInfos(walletAddress); const openAssetTab = useOpenAssetTab(); + const { trackUseViewJSON } = useTrack(); return ( { size="sm" rightIcon={} onClick={() => { - AmpTrackViewJson("account_details_page_assets"); + trackUseViewJSON("account_details_page_assets"); openAssetTab(walletAddress); }} > diff --git a/src/lib/pages/account-details/components/delegations/DelegationInfo.tsx b/src/lib/pages/account-details/components/delegations/DelegationInfo.tsx index 033fb4fc8..944ebd472 100644 --- a/src/lib/pages/account-details/components/delegations/DelegationInfo.tsx +++ b/src/lib/pages/account-details/components/delegations/DelegationInfo.tsx @@ -2,9 +2,9 @@ import { Button, Flex, Heading } from "@chakra-ui/react"; import type { MouseEventHandler, ReactNode } from "react"; import { MobileDelegationTitle } from "../mobile/MobileDelegationTitle"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useMobile } from "lib/app-provider"; import { CustomIcon } from "lib/components/icon"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; interface DelegationInfoProps { onViewMore?: () => void; @@ -40,6 +40,7 @@ const DelegationsTitle = ({ ); }; + export const DelegationInfo = ({ onViewMore, onClickToggle, @@ -47,13 +48,16 @@ export const DelegationInfo = ({ infoCards, TotalBondedCard, }: DelegationInfoProps) => { + const { track } = useTrack(); const isMobile = useMobile(); + let isMobileDetail = null; if (isMobile && onViewMore) { isMobileDetail = false; } else { isMobileDetail = true; } + return ( <> } onClick={() => { - AmpTrack(AmpEvent.USE_VIEW_MORE); + track(AmpEvent.USE_VIEW_MORE); onViewMore(); }} > diff --git a/src/lib/pages/account-details/components/delegations/DelegationsBody.tsx b/src/lib/pages/account-details/components/delegations/DelegationsBody.tsx index 2398d6752..acca90752 100644 --- a/src/lib/pages/account-details/components/delegations/DelegationsBody.tsx +++ b/src/lib/pages/account-details/components/delegations/DelegationsBody.tsx @@ -1,8 +1,8 @@ import { Flex, RadioGroup, Stack } from "@chakra-ui/react"; import { useState } from "react"; +import { useTrack } from "lib/amplitude"; import type { Delegation, Unbonding } from "lib/pages/account-details/data"; -import { AmpTrackUseRadio } from "lib/services/amplitude"; import type { Option, TokenWithValue } from "lib/types"; import { DelegationsTab } from "./DelegationsTab"; @@ -32,11 +32,12 @@ export const DelegationsBody = ({ }: DelegationsBodyProps) => { // NOTE: set between "Delegated" and "Unbonding" const [value, setValue] = useState("Delegated"); + const { trackUseRadio } = useTrack(); return ( { - AmpTrackUseRadio(newValue.toLocaleLowerCase()); + trackUseRadio(newValue.toLocaleLowerCase()); setValue(newValue); }} value={value} diff --git a/src/lib/pages/account-details/components/delegations/index.tsx b/src/lib/pages/account-details/components/delegations/index.tsx index c32127757..a12187c78 100644 --- a/src/lib/pages/account-details/components/delegations/index.tsx +++ b/src/lib/pages/account-details/components/delegations/index.tsx @@ -2,10 +2,10 @@ import { Flex, useDisclosure } from "@chakra-ui/react"; import type Big from "big.js"; import big from "big.js"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { Loading } from "lib/components/Loading"; import { EmptyState } from "lib/components/state"; import { useUserDelegationInfos } from "lib/pages/account-details/data"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { HumanAddr, Option, @@ -37,6 +37,7 @@ export const DelegationsSection = ({ walletAddress, onViewMore, }: DelegationsSectionProps) => { + const { track } = useTrack(); const { isOpen, onToggle } = useDisclosure(); const { stakingParams, @@ -134,7 +135,7 @@ export const DelegationsSection = ({ onViewMore={onViewMore} redelegationCount={redelegationCount} onClickToggle={() => { - AmpTrack(AmpEvent.USE_SEE_REDELEGATIONS); + track(AmpEvent.USE_SEE_REDELEGATIONS); onToggle(); }} /> diff --git a/src/lib/pages/account-details/index.tsx b/src/lib/pages/account-details/index.tsx index bc69af68d..0bdd77312 100644 --- a/src/lib/pages/account-details/index.tsx +++ b/src/lib/pages/account-details/index.tsx @@ -9,6 +9,7 @@ import { import { useRouter } from "next/router"; import { useCallback, useEffect } from "react"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useInternalNavigate, useValidateAddress, @@ -22,7 +23,6 @@ import PageContainer from "lib/components/PageContainer"; import { InvalidState } from "lib/components/state"; import { useAccountDetailsTableCounts } from "lib/model/account"; import { useAccountId } from "lib/services/accountService"; -import { AmpEvent, AmpTrack, AmpTrackUseTab } from "lib/services/amplitude"; import { useICNSNamesByAddress } from "lib/services/nameService"; import { usePublicProjectByAccountAddress, @@ -66,6 +66,7 @@ const AccountDetailsBody = ({ accountAddress }: AccountDetailsBodyProps) => { const wasm = useWasmConfig({ shouldRedirect: false }); const navigate = useInternalNavigate(); const router = useRouter(); + const { trackUseTab } = useTrack(); // TODO: remove assertion later const tab = getFirstQueryParam(router.query.tab) as TabIndex; const { data: publicInfo } = usePublicProjectByAccountAddress(accountAddress); @@ -86,7 +87,7 @@ const AccountDetailsBody = ({ accountAddress }: AccountDetailsBodyProps) => { const handleTabChange = useCallback( (nextTab: TabIndex) => () => { if (nextTab === tab) return; - AmpTrackUseTab(nextTab); + trackUseTab(nextTab); navigate({ pathname: "/accounts/[accountAddress]/[tab]", query: { @@ -98,7 +99,7 @@ const AccountDetailsBody = ({ accountAddress }: AccountDetailsBodyProps) => { }, }); }, - [accountAddress, tab, navigate] + [tab, trackUseTab, navigate, accountAddress] ); useEffect(() => { @@ -331,17 +332,18 @@ const AccountDetailsBody = ({ accountAddress }: AccountDetailsBodyProps) => { const AccountDetails = () => { const router = useRouter(); + const { track } = useTrack(); const { validateUserAddress, validateContractAddress } = useValidateAddress(); // TODO: change to `Addr` for correctness (i.e. interchain account) const accountAddressParam = getFirstQueryParam( router.query.accountAddress ).toLowerCase() as HumanAddr; + // TODO: fix assertion later const tab = getFirstQueryParam(router.query.tab) as TabIndex; useEffect(() => { - if (router.isReady) - AmpTrack(AmpEvent.TO_ACCOUNT_DETAIL, { ...(tab && { tab }) }); - }, [router.isReady, tab]); + if (router.isReady && tab) track(AmpEvent.TO_ACCOUNT_DETAIL, { tab }); + }, [router.isReady, tab, track]); if (!router.isReady) return ; diff --git a/src/lib/pages/admin/index.tsx b/src/lib/pages/admin/index.tsx index 989490056..c01e4bca5 100644 --- a/src/lib/pages/admin/index.tsx +++ b/src/lib/pages/admin/index.tsx @@ -3,6 +3,7 @@ import type { StdFee } from "@cosmjs/stargate"; import { useRouter } from "next/router"; import { useCallback, useEffect, useState } from "react"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useFabricateFee, useInternalNavigate, @@ -21,11 +22,6 @@ import type { FormStatus } from "lib/components/forms"; import { TextInput } from "lib/components/forms"; import WasmPageContainer from "lib/components/WasmPageContainer"; import { useTxBroadcast } from "lib/providers/tx-broadcast"; -import { - AmpEvent, - AmpTrack, - AmpTrackToAdminUpdate, -} from "lib/services/amplitude"; import { useContractDetailByContractAddress } from "lib/services/contractService"; import type { Addr, ContractAddr, HumanAddr } from "lib/types"; import { MsgType } from "lib/types"; @@ -34,7 +30,9 @@ import { composeMsg, getFirstQueryParam } from "lib/utils"; const UpdateAdmin = () => { useWasmConfig({ shouldRedirect: true }); const router = useRouter(); + const { track } = useTrack(); const { address } = useCurrentChain(); + const { trackToAdminUpdate } = useTrack(); const { validateContractAddress, validateUserAddress } = useValidateAddress(); const getAddressType = useGetAddressType(); const navigate = useInternalNavigate(); @@ -89,7 +87,7 @@ const UpdateAdmin = () => { }); const proceed = useCallback(async () => { - AmpTrack(AmpEvent.ACTION_ADMIN_UPDATE); + track(AmpEvent.ACTION_ADMIN_UPDATE); const stream = await updateAdminTx({ contractAddress: contractAddressParam, newAdmin: adminAddress as Addr, @@ -102,6 +100,7 @@ const UpdateAdmin = () => { contractAddressParam, updateAdminTx, broadcast, + track, estimatedFee, ]); @@ -166,8 +165,8 @@ const UpdateAdmin = () => { ]); useEffect(() => { - if (router.isReady) AmpTrackToAdminUpdate(!!contractAddressParam); - }, [router.isReady, contractAddressParam]); + if (router.isReady) trackToAdminUpdate(!!contractAddressParam); + }, [contractAddressParam, router.isReady, trackToAdminUpdate]); return ( diff --git a/src/lib/pages/block-details/index.tsx b/src/lib/pages/block-details/index.tsx index 070681206..2e25fd1bb 100644 --- a/src/lib/pages/block-details/index.tsx +++ b/src/lib/pages/block-details/index.tsx @@ -1,24 +1,25 @@ import { useRouter } from "next/router"; import { useEffect } from "react"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { Breadcrumb } from "lib/components/Breadcrumb"; import { Loading } from "lib/components/Loading"; import PageContainer from "lib/components/PageContainer"; import { EmptyState } from "lib/components/state"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import { useBlockInfoQuery } from "lib/services/blockService"; import { getFirstQueryParam } from "lib/utils"; import { BlockDetailsTop, BlockInfo, BlockTxsTable } from "./components"; const BlockDetail = () => { + const { track } = useTrack(); const router = useRouter(); const heightParam = getFirstQueryParam(router.query.height); const { data: blockData, isLoading } = useBlockInfoQuery(heightParam); useEffect(() => { - if (router.isReady) AmpTrack(AmpEvent.TO_BLOCK_DETAIL); - }, [router.isReady]); + if (router.isReady) track(AmpEvent.TO_BLOCK_DETAIL); + }, [router.isReady, track]); if (isLoading) return ; diff --git a/src/lib/pages/blocks/index.tsx b/src/lib/pages/blocks/index.tsx index 2222e9afc..2775fc7fb 100644 --- a/src/lib/pages/blocks/index.tsx +++ b/src/lib/pages/blocks/index.tsx @@ -2,16 +2,18 @@ import { Heading, Text } from "@chakra-ui/react"; import { useRouter } from "next/router"; import { useEffect } from "react"; +import { AmpEvent, useTrack } from "lib/amplitude"; import PageContainer from "lib/components/PageContainer"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import { BlocksTable } from "./components/BlocksTable"; const BlocksPage = () => { const router = useRouter(); + const { track } = useTrack(); + useEffect(() => { - if (router.isReady) AmpTrack(AmpEvent.TO_BLOCKS); - }, [router.isReady]); + if (router.isReady) track(AmpEvent.TO_BLOCKS); + }, [router.isReady, track]); return ( diff --git a/src/lib/pages/code-details/components/code-info/CodeInfoSection.tsx b/src/lib/pages/code-details/components/code-info/CodeInfoSection.tsx index 984b00337..2b6adbe59 100644 --- a/src/lib/pages/code-details/components/code-info/CodeInfoSection.tsx +++ b/src/lib/pages/code-details/components/code-info/CodeInfoSection.tsx @@ -8,7 +8,9 @@ import { useDisclosure, Spinner, } from "@chakra-ui/react"; +import { useCallback } from "react"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useGetAddressType, useMobile } from "lib/app-provider"; import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; @@ -132,6 +134,18 @@ export const CodeInfoSection = ({ ); const uploaderType = getAddressType(uploader); const isMobile = useMobile(); + const { track } = useTrack(); + + const handleView = useCallback(() => { + toJsonSchemaTab(); + track(AmpEvent.USE_VIEW_ATTACHED_JSON); + }, [toJsonSchemaTab, track]); + + const handleAttach = useCallback(() => { + onOpen(); + track(AmpEvent.USE_ATTACHED_JSON_MODAL); + }, [onOpen, track]); + return ( @@ -195,7 +209,7 @@ export const CodeInfoSection = ({ ) } - onClick={attached ? toJsonSchemaTab : onOpen} + onClick={attached ? handleView : handleAttach} > {attached ? "View Schema" : "Attach"} diff --git a/src/lib/pages/code-details/components/json-schema/CodeSchemaSection.tsx b/src/lib/pages/code-details/components/json-schema/CodeSchemaSection.tsx index 2285bd505..9fc4f0a59 100644 --- a/src/lib/pages/code-details/components/json-schema/CodeSchemaSection.tsx +++ b/src/lib/pages/code-details/components/json-schema/CodeSchemaSection.tsx @@ -68,7 +68,7 @@ export const CodeSchemaSection = ({ )} diff --git a/src/lib/pages/code-details/index.tsx b/src/lib/pages/code-details/index.tsx index dc9621eea..e44c226bf 100644 --- a/src/lib/pages/code-details/index.tsx +++ b/src/lib/pages/code-details/index.tsx @@ -3,6 +3,7 @@ import { observer } from "mobx-react-lite"; import { useRouter } from "next/router"; import { useCallback, useEffect } from "react"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useWasmConfig, useMobile, @@ -15,7 +16,6 @@ import { InvalidState } from "lib/components/state"; import type { CodeDataState } from "lib/model/code"; import { useCodeData } from "lib/model/code"; import { useSchemaStore } from "lib/providers/store"; -import { AmpEvent, AmpTrack, AmpTrackUseTab } from "lib/services/amplitude"; import { getFirstQueryParam, isCodeId } from "lib/utils"; import { CodeInfoSection, CodeContractsTable } from "./components/code-info"; @@ -48,11 +48,17 @@ const CodeDetailsBody = observer( const jsonSchema = codeHash ? getSchemaByCodeHash(codeHash) : undefined; const isMobile = useMobile(); const tab = getFirstQueryParam(router.query.tab) as TabIndex; + const { track } = useTrack(); + + useEffect(() => { + if (router.isReady) track(AmpEvent.TO_CODE_DETAIL, { tab }); + // Note: we don't want to track when tab changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [router.isReady, track]); const handleTabChange = useCallback( (nextTab: TabIndex) => () => { if (nextTab === tab) return; - AmpTrackUseTab(nextTab); navigate({ pathname: "/codes/[codeId]/[tab]", query: { @@ -142,10 +148,6 @@ const CodeDetails = observer(() => { const codeIdParam = getFirstQueryParam(router.query.codeId); const data = useCodeData(codeIdParam); - useEffect(() => { - if (router.isReady) AmpTrack(AmpEvent.TO_CODE_DETAIL); - }, [router.isReady]); - if (data.isLoading) return ; return ( diff --git a/src/lib/pages/codes/index.tsx b/src/lib/pages/codes/index.tsx index ec67d18c5..b6cb79961 100644 --- a/src/lib/pages/codes/index.tsx +++ b/src/lib/pages/codes/index.tsx @@ -4,6 +4,7 @@ import { useRouter } from "next/router"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useInternalNavigate, useWasmConfig, @@ -16,7 +17,6 @@ import PageContainer from "lib/components/PageContainer"; import { EmptyState } from "lib/components/state"; import { CodesTable } from "lib/components/table"; import type { PermissionFilterValue } from "lib/hooks"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import { useRecentCodesData } from "./data"; @@ -27,6 +27,7 @@ interface RecentCodesState { const RecentCodes = observer(() => { useWasmConfig({ shouldRedirect: true }); + const { track } = useTrack(); const router = useRouter(); const navigate = useInternalNavigate(); const onRowSelect = (codeId: number) => @@ -48,8 +49,8 @@ const RecentCodes = observer(() => { ); useEffect(() => { - if (router.isReady) AmpTrack(AmpEvent.TO_RECENT_CODES); - }, [router.isReady]); + if (router.isReady) track(AmpEvent.TO_RECENT_CODES); + }, [router.isReady, track]); const emptyState = ( ) : ( diff --git a/src/lib/pages/contract-details/index.tsx b/src/lib/pages/contract-details/index.tsx index eaf9b1b40..7850c524a 100644 --- a/src/lib/pages/contract-details/index.tsx +++ b/src/lib/pages/contract-details/index.tsx @@ -10,6 +10,7 @@ import { observer } from "mobx-react-lite"; import { useRouter } from "next/router"; import { useEffect } from "react"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useValidateAddress, useWasmConfig, useMobile } from "lib/app-provider"; import { CustomTab } from "lib/components/CustomTab"; import { Loading } from "lib/components/Loading"; @@ -17,7 +18,6 @@ import PageContainer from "lib/components/PageContainer"; import { InvalidState } from "lib/components/state"; import { useContractDetailsTableCounts } from "lib/model/contract"; import { useAccountId } from "lib/services/accountService"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { ContractAddr } from "lib/types"; import { getFirstQueryParam, jsonPrettify } from "lib/utils"; @@ -164,6 +164,7 @@ const ContractDetailsBody = observer( const ContractDetails = observer(() => { useWasmConfig({ shouldRedirect: true }); + const { track } = useTrack(); const router = useRouter(); const { validateContractAddress } = useValidateAddress(); const contractAddressParam = getFirstQueryParam( @@ -172,8 +173,8 @@ const ContractDetails = observer(() => { const contractData = useContractData(contractAddressParam); useEffect(() => { - if (router.isReady) AmpTrack(AmpEvent.TO_CONTRACT_DETAIL); - }, [router.isReady]); + if (router.isReady) track(AmpEvent.TO_CONTRACT_DETAIL); + }, [router.isReady, track]); if (contractData.isContractDetailLoading) return ; return ( diff --git a/src/lib/pages/contract-list/index.tsx b/src/lib/pages/contract-list/index.tsx index db872083f..ebf1cde50 100644 --- a/src/lib/pages/contract-list/index.tsx +++ b/src/lib/pages/contract-list/index.tsx @@ -3,6 +3,7 @@ import { observer } from "mobx-react-lite"; import { useRouter } from "next/router"; import { useEffect } from "react"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useInternalNavigate, useWasmConfig } from "lib/app-provider"; import { CustomIcon } from "lib/components/icon"; import { CreateNewListModal } from "lib/components/modal"; @@ -10,10 +11,10 @@ import PageContainer from "lib/components/PageContainer"; import { AllContractLists } from "lib/components/select-contract"; import { useInstantiatedMockInfoByMe } from "lib/model/contract"; import { useContractStore } from "lib/providers/store"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; const AllContractListsPage = observer(() => { useWasmConfig({ shouldRedirect: true }); + const { track } = useTrack(); const router = useRouter(); const navigate = useInternalNavigate(); const { getContractLists } = useContractStore(); @@ -24,8 +25,8 @@ const AllContractListsPage = observer(() => { }; useEffect(() => { - if (router.isReady) AmpTrack(AmpEvent.TO_ALL_LISTS); - }, [router.isReady]); + if (router.isReady) track(AmpEvent.TO_ALL_LISTS); + }, [router.isReady, track]); return ( diff --git a/src/lib/pages/contract-list/slug.tsx b/src/lib/pages/contract-list/slug.tsx index 1399831bb..c03a43ce2 100644 --- a/src/lib/pages/contract-list/slug.tsx +++ b/src/lib/pages/contract-list/slug.tsx @@ -10,6 +10,7 @@ import { observer } from "mobx-react-lite"; import { useRouter } from "next/router"; import { useEffect } from "react"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useInternalNavigate, useWasmConfig } from "lib/app-provider"; import { Breadcrumb } from "lib/components/Breadcrumb"; import { CustomIcon } from "lib/components/icon"; @@ -23,12 +24,12 @@ import { ContractListDetail } from "lib/components/select-contract"; import { INSTANTIATED_LIST_NAME, SAVED_LIST_NAME } from "lib/data"; import { useInstantiatedByMe } from "lib/model/contract"; import { useContractStore } from "lib/providers/store"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { ContractAddr } from "lib/types"; import { formatSlugName, getFirstQueryParam } from "lib/utils"; const ContractsByList = observer(() => { useWasmConfig({ shouldRedirect: true }); + const { track } = useTrack(); const router = useRouter(); const navigate = useInternalNavigate(); const listSlug = getFirstQueryParam(router.query.slug); @@ -63,16 +64,16 @@ const ContractsByList = observer(() => { if (router.isReady) { switch (listSlug) { case formatSlugName(INSTANTIATED_LIST_NAME): - AmpTrack(AmpEvent.TO_LIST_BY_ME); + track(AmpEvent.TO_INSTANTIATED_BY_ME); break; case formatSlugName(SAVED_LIST_NAME): - AmpTrack(AmpEvent.TO_LIST_SAVED); + track(AmpEvent.TO_SAVED_CONTRACT); break; default: - AmpTrack(AmpEvent.TO_LIST_OTHERS); + track(AmpEvent.TO_LIST_OTHERS); } } - }, [router.isReady, listSlug]); + }, [router.isReady, listSlug, track]); if (!contractListInfo) return null; diff --git a/src/lib/pages/contracts/index.tsx b/src/lib/pages/contracts/index.tsx index 074a8fd31..584e97bc3 100644 --- a/src/lib/pages/contracts/index.tsx +++ b/src/lib/pages/contracts/index.tsx @@ -1,6 +1,9 @@ import { Heading, Box, Flex, Text } from "@chakra-ui/react"; import { observer } from "mobx-react-lite"; +import { useRouter } from "next/router"; +import { useEffect } from "react"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useInternalNavigate, useWasmConfig, @@ -17,7 +20,10 @@ import { useRecentContractsData } from "./data"; const RecentContracts = observer(() => { useWasmConfig({ shouldRedirect: true }); + const { track } = useTrack(); + const router = useRouter(); const navigate = useInternalNavigate(); + const isMobile = useMobile(); const onRowSelect = (contract: ContractAddr) => navigate({ pathname: "/contracts/[contract]", @@ -26,6 +32,10 @@ const RecentContracts = observer(() => { const { recentContracts, isLoading } = useRecentContractsData(""); + useEffect(() => { + if (router.isReady) track(AmpEvent.TO_RECENT_CONTRACT); + }, [router.isReady, track]); + const emptyState = ( { withBorder /> ); - const isMobile = useMobile(); const MobileSection = () => { if (isLoading) return ; if (!recentContracts?.length) return emptyState; @@ -48,6 +57,7 @@ const RecentContracts = observer(() => { ); }; + return ( diff --git a/src/lib/pages/deploy/index.tsx b/src/lib/pages/deploy/index.tsx index 52eb8cd7c..5ab32c65b 100644 --- a/src/lib/pages/deploy/index.tsx +++ b/src/lib/pages/deploy/index.tsx @@ -10,6 +10,7 @@ import { import { useRouter } from "next/router"; import { useEffect } from "react"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useCelatoneApp, useCurrentChain, @@ -22,7 +23,6 @@ import { CustomIcon } from "lib/components/icon"; import { Loading } from "lib/components/Loading"; import { Stepper } from "lib/components/stepper"; import WasmPageContainer from "lib/components/WasmPageContainer"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import { useUploadAccessParams } from "lib/services/proposalService"; import type { HumanAddr } from "lib/types"; import { AccessConfigPermission } from "lib/types"; @@ -56,6 +56,7 @@ const getAlertContent = ( }; const Deploy = () => { + const { track } = useTrack(); const router = useRouter(); const navigate = useInternalNavigate(); const { address } = useCurrentChain(); @@ -74,8 +75,8 @@ const Deploy = () => { useWasmConfig({ shouldRedirect: true }); useEffect(() => { - if (router.isReady) AmpTrack(AmpEvent.TO_DEPLOY); - }, [router.isReady]); + if (router.isReady) track(AmpEvent.TO_DEPLOY); + }, [router.isReady, track]); if (isFetching) return ; diff --git a/src/lib/pages/execute/components/ExecuteArea.tsx b/src/lib/pages/execute/components/ExecuteArea.tsx index d4dd3754f..1f6dd6d82 100644 --- a/src/lib/pages/execute/components/ExecuteArea.tsx +++ b/src/lib/pages/execute/components/ExecuteArea.tsx @@ -1,8 +1,9 @@ import { Box, Flex, Heading, TabList, Tabs } from "@chakra-ui/react"; import type { Coin } from "@cosmjs/stargate"; import { observer } from "mobx-react-lite"; -import { useState, useEffect } from "react"; +import { useCallback, useState, useEffect } from "react"; +import { useTrack } from "lib/amplitude"; import { CustomTab } from "lib/components/CustomTab"; import { CustomIcon } from "lib/components/icon"; import { @@ -33,12 +34,21 @@ export const ExecuteArea = observer( codeHash, codeId, }: ExecuteAreaProps) => { + const { trackUseTab } = useTrack(); const [tab, setTab] = useState(); const { getExecuteSchema, getSchemaByCodeHash } = useSchemaStore(); const schema = getExecuteSchema(codeHash); const attached = Boolean(getSchemaByCodeHash(codeHash)); const currentTabIdx = tab ? Object.values(MessageTabs).indexOf(tab) : 0; + const handleTabChange = useCallback( + (nextTab: MessageTabs) => { + trackUseTab(nextTab); + setTab(nextTab); + }, + [trackUseTab] + ); + useEffect(() => { if (!schema) setTab(MessageTabs.JSON_INPUT); else setTab(MessageTabs.YOUR_SCHEMA); @@ -52,11 +62,11 @@ export const ExecuteArea = observer( - setTab(MessageTabs.JSON_INPUT)}> + handleTabChange(MessageTabs.JSON_INPUT)}> JSON Input setTab(MessageTabs.YOUR_SCHEMA)} + onClick={() => handleTabChange(MessageTabs.YOUR_SCHEMA)} isDisabled={!contractAddress} > { addActivity(activity); @@ -175,6 +181,7 @@ export const JsonExecute = ({ contractAddress, msg, getAttachFunds, + trackActionWithFunds, assetsJsonStr, assetsSelect, addActivity, diff --git a/src/lib/pages/execute/components/MsgSuggestion.tsx b/src/lib/pages/execute/components/MsgSuggestion.tsx index 0ed57c46f..fb7bebadf 100644 --- a/src/lib/pages/execute/components/MsgSuggestion.tsx +++ b/src/lib/pages/execute/components/MsgSuggestion.tsx @@ -1,7 +1,7 @@ import { Box, Text, ButtonGroup } from "@chakra-ui/react"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { ContractCmdButton } from "lib/components/ContractCmdButton"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { ContractAddr } from "lib/types"; import { jsonPrettify } from "lib/utils"; @@ -16,6 +16,8 @@ export const MsgSuggestion = ({ cmds, setMsg, }: MsgSuggestionProps) => { + const { track } = useTrack(); + return ( {contractAddress && ( @@ -39,7 +41,7 @@ export const MsgSuggestion = ({ key={`query-cmd-${cmd}`} cmd={cmd} onClickCmd={() => { - AmpTrack(AmpEvent.USE_CMD_EXECUTE); + track(AmpEvent.USE_CMD_EXECUTE); setMsg(jsonPrettify(queryMsg)); }} /> diff --git a/src/lib/pages/execute/components/schema-execute/ExecuteBox.tsx b/src/lib/pages/execute/components/schema-execute/ExecuteBox.tsx index d8656f493..ecb13ec62 100644 --- a/src/lib/pages/execute/components/schema-execute/ExecuteBox.tsx +++ b/src/lib/pages/execute/components/schema-execute/ExecuteBox.tsx @@ -18,6 +18,7 @@ import dynamic from "next/dynamic"; import { useCallback, useEffect, useState, useMemo } from "react"; import { useForm, useFormState } from "react-hook-form"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useCurrentChain, useExecuteContractTx, @@ -40,7 +41,6 @@ import { CustomIcon } from "lib/components/icon"; import { JsonSchemaForm } from "lib/components/json-schema"; import { useContractStore } from "lib/providers/store"; import { useTxBroadcast } from "lib/providers/tx-broadcast"; -import { AmpEvent, AmpTrackAction } from "lib/services/amplitude"; import type { Activity } from "lib/stores/contract"; import type { SchemaInfo } from "lib/stores/schema"; import type { @@ -92,6 +92,7 @@ export const ExecuteBox = ({ const { broadcast } = useTxBroadcast(); const { addActivity } = useContractStore(); const getAttachFunds = useAttachFunds(); + const { trackActionWithFunds } = useTrack(); // ------------------------------------------// // ------------------STATES------------------// @@ -188,7 +189,12 @@ export const ExecuteBox = ({ assetsJsonStr, assetsSelect ); - AmpTrackAction(AmpEvent.ACTION_EXECUTE, funds.length, attachFundsOption); + trackActionWithFunds( + AmpEvent.ACTION_EXECUTE, + funds.length, + attachFundsOption, + "schema" + ); const stream = await executeTx({ onTxSucceed: (activity: Activity) => { addActivity(activity); @@ -211,6 +217,7 @@ export const ExecuteBox = ({ contractAddress, msg, getAttachFunds, + trackActionWithFunds, assetsJsonStr, assetsSelect, addActivity, diff --git a/src/lib/pages/execute/components/schema-execute/index.tsx b/src/lib/pages/execute/components/schema-execute/index.tsx index f0b4f8135..cef3d43b1 100644 --- a/src/lib/pages/execute/components/schema-execute/index.tsx +++ b/src/lib/pages/execute/components/schema-execute/index.tsx @@ -2,12 +2,12 @@ import { Accordion, Button, Flex, Text } from "@chakra-ui/react"; import type { Coin } from "@cosmjs/stargate"; import { useEffect, useMemo, useRef, useState } from "react"; +import { useTrack } from "lib/amplitude"; import { CustomIcon } from "lib/components/icon"; import InputWithIcon from "lib/components/InputWithIcon"; import { UploadSchema } from "lib/components/json-schema"; import { EmptyState, StateImage } from "lib/components/state"; import { useSchemaStore } from "lib/providers/store"; -import { AmpTrackExpandAll } from "lib/services/amplitude"; import type { ExecuteSchema } from "lib/stores/schema"; import type { ContractAddr, Option } from "lib/types"; import { getDefaultMsg, resolveInitialMsg } from "lib/utils"; @@ -32,7 +32,12 @@ export const SchemaExecute = ({ codeHash, }: SchemaExecuteProps) => { // ------------------------------------------// - // --------------------REF-------------------// + // ---------------DEPENDENCIES---------------// + // ------------------------------------------// + const { trackUseExpandAll } = useTrack(); + + // ------------------------------------------// + // -----------------REFERENCE----------------// // ------------------------------------------// const accordionRef = useRef(null); const { getSchemaByCodeHash } = useSchemaStore(); @@ -131,7 +136,7 @@ export const SchemaExecute = ({ } minH="40px" onClick={() => { - AmpTrackExpandAll(expandedIndexes.length ? "collapse" : "expand"); + trackUseExpandAll(expandedIndexes.length ? "collapse" : "expand"); setExpandedIndexes((prev) => !prev.length ? Array.from(Array(schema.length).keys()) : [] ); diff --git a/src/lib/pages/execute/index.tsx b/src/lib/pages/execute/index.tsx index aa5742c35..44901e4df 100644 --- a/src/lib/pages/execute/index.tsx +++ b/src/lib/pages/execute/index.tsx @@ -3,12 +3,12 @@ import type { Coin } from "@cosmjs/stargate"; import { useRouter } from "next/router"; import { useCallback, useEffect, useState } from "react"; +import { useTrack } from "lib/amplitude"; import { useInternalNavigate, useWasmConfig } from "lib/app-provider"; import { ConnectWalletAlert } from "lib/components/ConnectWalletAlert"; import { ContractSelectSection } from "lib/components/ContractSelectSection"; import { CustomIcon } from "lib/components/icon"; import PageContainer from "lib/components/PageContainer"; -import { AmpTrackToExecute } from "lib/services/amplitude"; import type { ContractDetail } from "lib/services/contractService"; import type { ContractAddr } from "lib/types"; import { @@ -37,6 +37,7 @@ const Execute = () => { const [initialFunds, setInitialFunds] = useState([]); const [codeHash, setCodeHash] = useState(""); const [codeId, setCodeId] = useState(""); + const { trackToExecute } = useTrack(); // ------------------------------------------// // ----------------CALLBACKS-----------------// @@ -65,12 +66,11 @@ const Execute = () => { // ---------------SIDE EFFECTS---------------// // ------------------------------------------// useEffect(() => { - const msgParam = getFirstQueryParam(router.query.msg); if (router.isReady) { const contractAddressParam = getFirstQueryParam( router.query.contract ) as ContractAddr; - + const msgParam = getFirstQueryParam(router.query.msg); if (!msgParam.length) { setInitialMsg(""); setInitialFunds([]); @@ -89,9 +89,9 @@ const Execute = () => { } setContractAddress(contractAddressParam); - AmpTrackToExecute(!!contractAddressParam, !!msgParam); + trackToExecute(!!contractAddressParam, !!msgParam); } - }, [router, onContractSelect]); + }, [router, onContractSelect, trackToExecute]); return ( diff --git a/src/lib/pages/faucet/index.tsx b/src/lib/pages/faucet/index.tsx index 08c8c41ec..30868a04c 100644 --- a/src/lib/pages/faucet/index.tsx +++ b/src/lib/pages/faucet/index.tsx @@ -11,6 +11,7 @@ import axios from "axios"; import { useRouter } from "next/router"; import { useEffect, useMemo, useState } from "react"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useCelatoneApp, useCurrentChain, @@ -24,7 +25,6 @@ import { CustomIcon } from "lib/components/icon"; import type { IconKeys } from "lib/components/icon"; import WasmPageContainer from "lib/components/WasmPageContainer"; import { useOpenTxTab } from "lib/hooks"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import { useFaucetInfo } from "lib/services/faucetService"; type ResultStatus = "success" | "error" | "warning"; @@ -42,6 +42,7 @@ const STATUS_ICONS: Record = { }; const Faucet = () => { + const { track } = useTrack(); const { address: walletAddress = "" } = useCurrentChain(); const [address, setAddress] = useState(""); const [isLoading, setIsLoading] = useState(false); @@ -76,8 +77,8 @@ const Faucet = () => { }, [faucet.enabled, faucetInfo?.formattedAmount, faucetInfo?.formattedDenom]); useEffect(() => { - if (router.isReady) AmpTrack(AmpEvent.TO_FAUCET); - }, [router]); + if (router.isReady) track(AmpEvent.TO_FAUCET); + }, [router, track]); useEffect(() => { if (address) { @@ -92,7 +93,7 @@ const Faucet = () => { const onSubmit = async () => { setIsLoading(true); - AmpTrack(AmpEvent.ACTION_FAUCET); + track(AmpEvent.ACTION_FAUCET); if (!faucetUrl) { setResult({ status: "error", message: "Faucet URL not set" }); @@ -122,7 +123,7 @@ const Faucet = () => { ), }); - AmpTrack(AmpEvent.TX_SUCCEED); + track(AmpEvent.TX_SUCCEED); setIsLoading(false); setResult({ status: "success", @@ -155,7 +156,7 @@ const Faucet = () => { }); } - AmpTrack(AmpEvent.TX_FAILED); + track(AmpEvent.TX_FAILED); setIsLoading(false); }); }; @@ -182,7 +183,7 @@ const Faucet = () => { helperAction={ { - AmpTrack(AmpEvent.USE_ASSIGN_ME); + track(AmpEvent.USE_ASSIGN_ME); setAddress(walletAddress); }} isDisable={address === walletAddress} diff --git a/src/lib/pages/home/index.tsx b/src/lib/pages/home/index.tsx index a32d569cf..e5e219936 100644 --- a/src/lib/pages/home/index.tsx +++ b/src/lib/pages/home/index.tsx @@ -4,6 +4,7 @@ import { useRouter } from "next/router"; import { useEffect } from "react"; import { CURR_THEME } from "env"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useCelatoneApp, useInternalNavigate, @@ -16,13 +17,12 @@ import { ViewMore } from "lib/components/table"; import { Tooltip } from "lib/components/Tooltip"; import { BlocksTable } from "lib/pages/blocks/components/BlocksTable"; import { TxsTable } from "lib/pages/txs/components/TxsTable"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import { useAverageBlockTime, useLatestBlockInfo, } from "lib/services/blockService"; import { useTxsCount } from "lib/services/txService"; -import type { Option } from "lib/types"; +import { type Option } from "lib/types"; import { dateFromNow, formatUTC } from "lib/utils"; import { DevShortcut, TopDecorations } from "./components"; @@ -121,6 +121,7 @@ const Home = () => { isLoading: isLoadingLatestBlockInfo, error: latestBlockInfoError, } = useLatestBlockInfo(); + const { track } = useTrack(); const { data } = useAverageBlockTime(); const averageBlockTime = calculateAverageBlockTime( data?.latest, @@ -139,8 +140,8 @@ const Home = () => { }); useEffect(() => { - if (router.isReady) AmpTrack(AmpEvent.TO_NETWORK_OVERVIEW); - }, [router.isReady]); + if (router.isReady) track(AmpEvent.TO_OVERVIEW); + }, [track, router.isReady]); return ( diff --git a/src/lib/pages/instantiate/component/InstantiateOffchainForm.tsx b/src/lib/pages/instantiate/component/InstantiateOffchainForm.tsx index f1632657b..50f281c2e 100644 --- a/src/lib/pages/instantiate/component/InstantiateOffchainForm.tsx +++ b/src/lib/pages/instantiate/component/InstantiateOffchainForm.tsx @@ -2,13 +2,13 @@ import { Text, Flex, Heading, Button } from "@chakra-ui/react"; import { observer } from "mobx-react-lite"; import { useForm } from "react-hook-form"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useCurrentChain, useInternalNavigate } from "lib/app-provider"; import { OffChainForm } from "lib/components/OffChainForm"; import type { OffchainDetail } from "lib/components/OffChainForm"; import { INSTANTIATED_LIST_NAME } from "lib/data"; import { useUserKey } from "lib/hooks"; import { useContractStore } from "lib/providers/store"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { ContractAddr, HumanAddr, LVPair } from "lib/types"; import { formatSlugName } from "lib/utils"; @@ -28,6 +28,7 @@ export const InstantiateOffChainForm = observer( contractAddress, contractLabel, }: InstantiateOffChainFormProps) => { + const { track } = useTrack(); const { address = "" } = useCurrentChain(); const navigate = useInternalNavigate(); const { updateContractLocalInfo } = useContractStore(); @@ -65,7 +66,7 @@ export const InstantiateOffChainForm = observer( const saveContract = () => { handleSubmit((data) => { - AmpTrack(AmpEvent.CONTRACT_SAVE_AFTER_INIT); + track(AmpEvent.CONTRACT_SAVE_AFTER_INIT); updateContractLocalInfo( userKey, contractAddress, diff --git a/src/lib/pages/instantiate/instantiate.tsx b/src/lib/pages/instantiate/instantiate.tsx index 6ca77c319..170e128f2 100644 --- a/src/lib/pages/instantiate/instantiate.tsx +++ b/src/lib/pages/instantiate/instantiate.tsx @@ -7,6 +7,7 @@ import { useRouter } from "next/router"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useFabricateFee, useInstantiateTx, @@ -40,12 +41,6 @@ import { Stepper } from "lib/components/stepper"; import WasmPageContainer from "lib/components/WasmPageContainer"; import { useSchemaStore } from "lib/providers/store"; import { useTxBroadcast } from "lib/providers/tx-broadcast"; -import { - AmpEvent, - AmpTrack, - AmpTrackAction, - AmpTrackToInstantiate, -} from "lib/services/amplitude"; import type { CodeIdInfoResponse } from "lib/services/code"; import { useLCDCodeInfo } from "lib/services/codeService"; import type { ComposedMsg, HumanAddr } from "lib/types"; @@ -92,6 +87,7 @@ const Instantiate = ({ onComplete }: InstantiatePageProps) => { const { validateUserAddress, validateContractAddress } = useValidateAddress(); const getAttachFunds = useAttachFunds(); const { getSchemaByCodeHash } = useSchemaStore(); + const { track, trackActionWithFunds, trackToInstantiate } = useTrack(); // ------------------------------------------// // ------------------STATES------------------// @@ -242,7 +238,12 @@ const Instantiate = ({ onComplete }: InstantiatePageProps) => { ); const proceed = useCallback(async () => { - AmpTrackAction(AmpEvent.ACTION_EXECUTE, funds.length, attachFundsOption); + trackActionWithFunds( + AmpEvent.ACTION_INSTANTIATE, + funds.length, + attachFundsOption, + tab === MessageTabs.YOUR_SCHEMA ? "schema" : "json-input" + ); const stream = await postInstantiateTx({ codeId: Number(codeId), initMsg: JSON.parse(currentInput), @@ -263,8 +264,10 @@ const Instantiate = ({ onComplete }: InstantiatePageProps) => { } }, [ funds, + tab, attachFundsOption, postInstantiateTx, + trackActionWithFunds, codeId, currentInput, label, @@ -366,8 +369,8 @@ const Instantiate = ({ onComplete }: InstantiatePageProps) => { }, [jsonSchema, setValue]); useEffect(() => { - if (router.isReady) AmpTrackToInstantiate(!!msgQuery, !!codeIdQuery); - }, [router.isReady, msgQuery, codeIdQuery]); + if (router.isReady) trackToInstantiate(!!msgQuery, !!codeIdQuery); + }, [router.isReady, msgQuery, codeIdQuery, trackToInstantiate]); return ( <> @@ -426,7 +429,7 @@ const Instantiate = ({ onComplete }: InstantiatePageProps) => { helperAction={ { - AmpTrack(AmpEvent.USE_ASSIGN_ME); + track(AmpEvent.USE_ASSIGN_ME); setValue("adminAddress", address); }} isDisable={adminAddress === address} diff --git a/src/lib/pages/migrate/components/MigrateContract.tsx b/src/lib/pages/migrate/components/MigrateContract.tsx index 0d267c193..065a1ed64 100644 --- a/src/lib/pages/migrate/components/MigrateContract.tsx +++ b/src/lib/pages/migrate/components/MigrateContract.tsx @@ -5,6 +5,7 @@ import Long from "long"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useFabricateFee, useSimulateFeeQuery, @@ -26,7 +27,6 @@ import JsonInput from "lib/components/json/JsonInput"; import { CodeSelectSection } from "lib/components/select-code"; import { useSchemaStore } from "lib/providers/store"; import { useTxBroadcast } from "lib/providers/tx-broadcast"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { CodeIdInfoResponse } from "lib/services/code"; import { useLCDCodeInfo } from "lib/services/codeService"; import type { ComposedMsg, ContractAddr, HumanAddr } from "lib/types"; @@ -57,6 +57,7 @@ export const MigrateContract = ({ const migrateTx = useMigrateTx(); const fabricateFee = useFabricateFee(); const { getSchemaByCodeHash } = useSchemaStore(); + const { trackAction } = useTrack(); // ------------------------------------------// // ----------------FORM HOOKS----------------// @@ -172,7 +173,10 @@ export const MigrateContract = ({ ); const proceed = useCallback(async () => { - AmpTrack(AmpEvent.ACTION_MIGRATE); + trackAction( + AmpEvent.ACTION_MIGRATE, + tab === MessageTabs.YOUR_SCHEMA ? "schema" : "json-input" + ); const stream = await migrateTx({ contractAddress, codeId: Number(codeId), @@ -188,6 +192,8 @@ export const MigrateContract = ({ } }, [ migrateTx, + trackAction, + tab, contractAddress, codeId, currentInput, diff --git a/src/lib/pages/migrate/index.tsx b/src/lib/pages/migrate/index.tsx index 945be1608..e62337124 100644 --- a/src/lib/pages/migrate/index.tsx +++ b/src/lib/pages/migrate/index.tsx @@ -3,6 +3,7 @@ import { useRouter } from "next/router"; import { useCallback, useEffect } from "react"; import { useForm } from "react-hook-form"; +import { useTrack } from "lib/amplitude"; import { useCurrentChain, useInternalNavigate, @@ -13,7 +14,6 @@ import { ContractSelectSection } from "lib/components/ContractSelectSection"; import { Loading } from "lib/components/Loading"; import { Stepper } from "lib/components/stepper"; import WasmPageContainer from "lib/components/WasmPageContainer"; -import { AmpTrackToMigrate } from "lib/services/amplitude"; import { useContractDetailByContractAddress } from "lib/services/contractService"; import { useUploadAccessParams } from "lib/services/proposalService"; import type { ContractAddr } from "lib/types"; @@ -36,6 +36,7 @@ const Migrate = () => { const router = useRouter(); const navigate = useInternalNavigate(); const { data: uploadAccess, isFetching } = useUploadAccessParams(); + const { trackToMigrate } = useTrack(); const { address = "" } = useCurrentChain(); @@ -92,9 +93,8 @@ const Migrate = () => { }, [codeIdParam, contractAddressParam, setValue]); useEffect(() => { - if (router.isReady) - AmpTrackToMigrate(!!contractAddressParam, !!codeIdParam); - }, [router.isReady, codeIdParam, contractAddressParam]); + if (router.isReady) trackToMigrate(!!contractAddressParam, !!codeIdParam); + }, [router.isReady, codeIdParam, contractAddressParam, trackToMigrate]); const renderBody = () => { switch (migrateStep) { diff --git a/src/lib/pages/not-found/index.tsx b/src/lib/pages/not-found/index.tsx index 54ff364b1..c28c4ff4e 100644 --- a/src/lib/pages/not-found/index.tsx +++ b/src/lib/pages/not-found/index.tsx @@ -3,15 +3,16 @@ import { useRouter } from "next/router"; import { useEffect } from "react"; import { CURR_THEME } from "env"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { BackButton } from "lib/components/button"; import PageContainer from "lib/components/PageContainer"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; const NotFoundPage = () => { + const { track } = useTrack(); const router = useRouter(); useEffect(() => { - if (router.isReady) AmpTrack(AmpEvent.TO_NOT_FOUND); - }, [router.isReady]); + if (router.isReady) track(AmpEvent.TO_NOT_FOUND); + }, [router.isReady, track]); return ( diff --git a/src/lib/pages/past-txs/index.tsx b/src/lib/pages/past-txs/index.tsx index f9373b92d..2712e8540 100644 --- a/src/lib/pages/past-txs/index.tsx +++ b/src/lib/pages/past-txs/index.tsx @@ -10,6 +10,7 @@ import type { ChangeEvent } from "react"; import { useEffect, useMemo } from "react"; import { useForm } from "react-hook-form"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { useCurrentChain } from "lib/app-provider"; import { CustomIcon } from "lib/components/icon"; import PageContainer from "lib/components/PageContainer"; @@ -21,7 +22,6 @@ import { TxFilterSelection } from "lib/components/TxFilterSelection"; import { TxRelationSelection } from "lib/components/TxRelationSelection"; import { DEFAULT_TX_FILTERS } from "lib/data"; import { useAccountId } from "lib/services/accountService"; -import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import { useTxsByAddressPagination, useTxsCountByAddress, @@ -35,6 +35,7 @@ interface PastTxsState { } const PastTxs = () => { + const { track } = useTrack(); const router = useRouter(); const { address, @@ -120,8 +121,8 @@ const PastTxs = () => { }, [pastTxsState]); useEffect(() => { - if (router.isReady) AmpTrack(AmpEvent.TO_PAST_TXS); - }, [router.isReady]); + if (router.isReady) track(AmpEvent.TO_PAST_TXS); + }, [router.isReady, track]); useEffect(() => { setPageSize(10); diff --git a/src/lib/pages/pools/components/FilterByPoolType.tsx b/src/lib/pages/pools/components/FilterByPoolType.tsx index 236967b21..0053e9dd1 100644 --- a/src/lib/pages/pools/components/FilterByPoolType.tsx +++ b/src/lib/pages/pools/components/FilterByPoolType.tsx @@ -6,8 +6,8 @@ import { STABLESWAP_ICON, CLP_ICON, } from "../constant"; +import { AmpEvent, useTrack } from "lib/amplitude"; import { SelectInput } from "lib/components/forms"; -import { AmpEvent, AmpTrackUseFilter } from "lib/services/amplitude"; import type { PoolTypeFilter } from "lib/types"; import { PoolType } from "lib/types"; @@ -60,17 +60,20 @@ export const FilterByPoolType = ({ setPoolTypeValue, initialSelected, labelBgColor = "background.main", -}: FilterByPoolTypeProps) => ( - - - formLabel="Filter by Pool Type" - options={options} - onChange={(newVal) => { - AmpTrackUseFilter(AmpEvent.USE_FILTER_POOL_TYPE, [newVal], newVal); - setPoolTypeValue(newVal); - }} - initialSelected={initialSelected} - labelBgColor={labelBgColor} - /> - -); +}: FilterByPoolTypeProps) => { + const { trackUseFilter } = useTrack(); + return ( + + + formLabel="Filter by Pool Type" + options={options} + onChange={(newVal) => { + trackUseFilter(AmpEvent.USE_FILTER_POOL_TYPE, [newVal], newVal); + setPoolTypeValue(newVal); + }} + initialSelected={initialSelected} + labelBgColor={labelBgColor} + /> + + ); +}; diff --git a/src/lib/pages/pools/components/pool-details/JsonModalButton.tsx b/src/lib/pages/pools/components/pool-details/JsonModalButton.tsx index ae9fe4829..16e720ab2 100644 --- a/src/lib/pages/pools/components/pool-details/JsonModalButton.tsx +++ b/src/lib/pages/pools/components/pool-details/JsonModalButton.tsx @@ -11,9 +11,9 @@ import { Box, } from "@chakra-ui/react"; +import { useTrack } from "lib/amplitude"; import { CustomIcon } from "lib/components/icon"; import JsonReadOnly from "lib/components/json/JsonReadOnly"; -import { AmpTrackViewJson } from "lib/services/amplitude"; import { jsonPrettify } from "lib/utils"; interface JsonModalButtonProps { @@ -26,6 +26,7 @@ export const JsonModalButton = ({ modalHeader, }: JsonModalButtonProps) => { const { isOpen, onOpen, onClose } = useDisclosure(); + const { trackUseViewJSON } = useTrack(); return ( <>