diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a8becec3..75b23ba07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,7 +40,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features - [#653](https://github.com/alleslabs/celatone-frontend/pull/653) Migrate from stone-12 to stone-12-1 -- [#633](https://github.com/alleslabs/celatone-frontend/pull/633) Add Amplitude for module detail and breadcrumb +- [#637](https://github.com/alleslabs/celatone-frontend/pull/637) Add amp module interaction and code snippet property +- [#633](https://github.com/alleslabs/celatone-frontend/pull/633) Add amp module detail and breadcrumb ### Improvements diff --git a/src/lib/amplitude/track-event/trackInteraction.ts b/src/lib/amplitude/track-event/trackInteraction.ts index a58aa8eb0..769caa20f 100644 --- a/src/lib/amplitude/track-event/trackInteraction.ts +++ b/src/lib/amplitude/track-event/trackInteraction.ts @@ -2,7 +2,8 @@ import big from "big.js"; import { amp } from "../Amplitude"; import { AmpEvent } from "../types"; -import type { Option, Token } from "lib/types"; +import type { MoveAccountAddr, Option, Token } from "lib/types"; +import { isHexModuleAddress, isHexWalletAddress } from "lib/utils"; export const trackUseMainSearch = (isClick: boolean, section?: string) => amp.track(AmpEvent.USE_MAIN_SEARCH, { @@ -38,10 +39,17 @@ export const trackUseUnsupportedToken = (section?: string) => section, }); -export const trackUseCopier = (type: string, section?: string) => +export const trackUseCopier = ( + type: string, + section?: string, + subSection?: string, + info?: string +) => amp.track(AmpEvent.USE_COPIER, { type, section, + subSection, + info, }); export const trackUseExpand = ({ @@ -59,6 +67,8 @@ export const trackUseExpand = ({ | "unsupported_pool" | "module_function_accordion" | "module_struct_accordion" + | "module_interaction_function_accordion" + | "module_interaction_selected_function_card" | "pool_tx_msg"; info?: object; section?: string; @@ -176,3 +186,15 @@ export const trackUseView = (view: string) => export const trackUseToggle = (name: string, isActive: boolean) => amp.track(AmpEvent.USE_TOGGLE, { name, isActive }); + +export const trackUseModuleSelectionInputFill = ( + address: MoveAccountAddr, + manualModuleName: boolean, + manualFunctionName: boolean +) => + amp.track(AmpEvent.USE_MODULE_SELECTION_INPUT_FILL, { + address: !!address, + isHex: isHexWalletAddress(address) || isHexModuleAddress(address), + manualModuleName, + manualFunctionName, + }); diff --git a/src/lib/amplitude/track-event/trackToPage.ts b/src/lib/amplitude/track-event/trackToPage.ts index ee95b7598..b146bf782 100644 --- a/src/lib/amplitude/track-event/trackToPage.ts +++ b/src/lib/amplitude/track-event/trackToPage.ts @@ -34,3 +34,18 @@ export const trackToAdminUpdate = (contract: boolean) => amp.track(AmpEvent.TO_ADMIN_UPDATE, { contract, }); + +export const trackToModuleInteraction = ( + address: boolean, + moduleName: boolean, + isVerify: boolean, + functionName: boolean, + functionType?: string +) => + amp.track(AmpEvent.TO_MODULE_INTERACTION, { + address, + moduleName, + isVerify, + functionName, + functionType, + }); diff --git a/src/lib/amplitude/types.ts b/src/lib/amplitude/types.ts index 4ece761aa..ddb440c45 100644 --- a/src/lib/amplitude/types.ts +++ b/src/lib/amplitude/types.ts @@ -62,6 +62,8 @@ export enum AmpEvent { TO_PROPOSAL_TO_STORE_CODE = "To Proposal To Store Code", TO_PROPOSAL_TO_WHITELIST = "To Proposal To Whitelist", TO_MODULE_DETAIL = "To Module Detail", + TO_MODULE_INTERACTION = "To Module Interaction", + TO_PUBLISH_MODULE = "To Publish Module", // ACTIONS ACTION_UPLOAD = "Action Upload", ACTION_INSTANTIATE = "Action Instantiate", @@ -73,6 +75,8 @@ export enum AmpEvent { ACTION_RESEND = "Action Resend", ACTION_FAUCET = "Action Faucet", ACTION_ATTACH_JSON = "Action Attach Json", + ACTION_MOVE_VIEW = "Action Move View", + ACTION_MOVE_EXECUTE = "Action Move Execute", // INTERACTS USE_SELECT_NETWORK = "Use Select Network", USE_CLICK_WALLET = "Use Click Wallet", @@ -143,6 +147,12 @@ export enum AmpEvent { USE_MAIN_CTA = "Use Main CTA", USE_MODULE_FUNCTION_CTA = "Use Module Function CTA", USE_BREADCRUMB = "Use Breadcrumb", + USE_MODULE_SELECTION_DRAWER = "Use Module Selection Drawer", + USE_MODULE_SELECTION_INPUT_FILL = "Use Module Selection Input Fill", + USE_SEE_MODULE_BUTTON = "Use See Module Button", + USE_MODULE_SELECTION_FUNCTION = "Use Module Selection Function", + USE_MODULE_SELECTION_MODULE = "Use Module Selection Module", + USE_FUNCTION_SELECTION = "Use Function Selection", // TX TX_SUCCEED = "Tx Succeed", TX_FAILED = "Tx Failed", @@ -168,6 +178,7 @@ export type SpecialAmpEvent = | AmpEvent.TO_INSTANTIATE | AmpEvent.TO_MIGRATE | AmpEvent.TO_ADMIN_UPDATE + | AmpEvent.TO_MODULE_INTERACTION | AmpEvent.USE_MAIN_SEARCH | AmpEvent.USE_TAB | AmpEvent.USE_RADIO @@ -193,4 +204,5 @@ export type SpecialAmpEvent = | AmpEvent.USE_VIEW | AmpEvent.USE_TOGGLE | AmpEvent.USE_CONTRACT_STATES_LOAD_MORE - | AmpEvent.USE_CONTRACT_STATES_DOWNLOAD; + | AmpEvent.USE_CONTRACT_STATES_DOWNLOAD + | AmpEvent.USE_MODULE_SELECTION_INPUT_FILL; diff --git a/src/lib/components/copy/CopyButton.tsx b/src/lib/components/copy/CopyButton.tsx index 19d1dae39..1205ba5c6 100644 --- a/src/lib/components/copy/CopyButton.tsx +++ b/src/lib/components/copy/CopyButton.tsx @@ -13,6 +13,8 @@ interface CopyButtonProps extends ButtonProps { hasIcon?: boolean; buttonText?: string; amptrackSection?: string; + amptrackSubSection?: string; + amptrackInfo?: string; iconGap?: number; } @@ -25,6 +27,8 @@ export const CopyButton = ({ variant = "outline-accent", buttonText = "Copy", amptrackSection, + amptrackSubSection, + amptrackInfo, ml, iconGap, ...buttonProps @@ -41,7 +45,11 @@ export const CopyButton = ({ size={size} float="right" onClick={() => - track(AmpEvent.USE_COPY_BUTTON, { section: amptrackSection }) + track(AmpEvent.USE_COPY_BUTTON, { + section: amptrackSection, + subSection: amptrackSubSection, + info: amptrackInfo, + }) } {...buttonProps} borderRadius={size === "xs" ? 6 : 8} diff --git a/src/lib/components/modal/MoveCodeSnippet.tsx b/src/lib/components/modal/MoveCodeSnippet.tsx index f8ccdc356..ed96dc362 100644 --- a/src/lib/components/modal/MoveCodeSnippet.tsx +++ b/src/lib/components/modal/MoveCodeSnippet.tsx @@ -249,7 +249,9 @@ ${daemonName} tx move execute $MODULE_ADDRESS \\ ml={ml} gap={1} onClick={() => { - track(AmpEvent.USE_CONTRACT_SNIPPET); + track(AmpEvent.USE_CONTRACT_SNIPPET, { + functionType: fn.is_view ? "view" : "Execute", + }); onOpen(); }} > @@ -306,6 +308,8 @@ ${daemonName} tx move execute $MODULE_ADDRESS \\ diff --git a/src/lib/components/modal/WasmCodeSnippet.tsx b/src/lib/components/modal/WasmCodeSnippet.tsx index da506635c..7e8c3cc9c 100644 --- a/src/lib/components/modal/WasmCodeSnippet.tsx +++ b/src/lib/components/modal/WasmCodeSnippet.tsx @@ -211,7 +211,7 @@ execute(); ml={ml} gap={1} onClick={() => { - track(AmpEvent.USE_CONTRACT_SNIPPET); + track(AmpEvent.USE_CONTRACT_SNIPPET, { actionType: type }); onOpen(); }} > @@ -266,6 +266,8 @@ execute(); diff --git a/src/lib/pages/interact/component/FunctionSelectPanel.tsx b/src/lib/pages/interact/component/FunctionSelectPanel.tsx index aaa894524..a34eaa6d8 100644 --- a/src/lib/pages/interact/component/FunctionSelectPanel.tsx +++ b/src/lib/pages/interact/component/FunctionSelectPanel.tsx @@ -62,6 +62,7 @@ const RenderFunctions = ({ )} selectedFn={selectedFn} setSelectedFn={setSelectedFn} + amptrackTab={tab} /> {tab === InteractionTabs.EXECUTE_MODULE && ( )} @@ -115,6 +117,7 @@ export const FunctionSelectPanel = ({ return (
setKeyword(e.target.value)} diff --git a/src/lib/pages/interact/component/SelectedFunctionCard.tsx b/src/lib/pages/interact/component/SelectedFunctionCard.tsx index b3bd5d1aa..7def46abf 100644 --- a/src/lib/pages/interact/component/SelectedFunctionCard.tsx +++ b/src/lib/pages/interact/component/SelectedFunctionCard.tsx @@ -1,6 +1,7 @@ import { Button, Divider, Flex } from "@chakra-ui/react"; import { useState } from "react"; +import { trackUseExpand } from "lib/amplitude"; import { CustomIcon } from "lib/components/icon"; import { LabelText } from "lib/components/LabelText"; import { MotionBox } from "lib/components/MotionBox"; @@ -66,7 +67,13 @@ export const SelectedFunctionCard = ({ fn }: SelectedFunctionCardProps) => { transform={expand ? "rotate(180deg)" : "rotate(0)"} /> } - onClick={() => setExpand((prev) => !prev)} + onClick={() => { + trackUseExpand({ + action: expand ? "collapse" : "expand", + component: "module_interaction_selected_function_card", + }); + setExpand((prev) => !prev); + }} > {expand ? "View Less" : "View More"} diff --git a/src/lib/pages/interact/component/common/FunctionAccordion.tsx b/src/lib/pages/interact/component/common/FunctionAccordion.tsx index 9d1095e21..e49f3656b 100644 --- a/src/lib/pages/interact/component/common/FunctionAccordion.tsx +++ b/src/lib/pages/interact/component/common/FunctionAccordion.tsx @@ -7,6 +7,7 @@ import { Text, } from "@chakra-ui/react"; +import { AmpEvent, track, trackUseExpand } from "lib/amplitude"; import { CountBadge, FunctionCard } from "lib/components/module"; import type { ExposedFunction, Option } from "lib/types"; @@ -18,6 +19,7 @@ interface FunctionAccordionProps { triggerText: string; selectedFn: Option; setSelectedFn: (fn: ExposedFunction) => void; + amptrackTab: string; } export const FunctionAccordion = ({ @@ -26,36 +28,55 @@ export const FunctionAccordion = ({ triggerText, selectedFn, setSelectedFn, + amptrackTab, }: FunctionAccordionProps) => ( -
- - - - {triggerText} - - - - - -
- - - {filteredFns.length ? ( - filteredFns.map((fn) => ( - ( + <> +
+ { + trackUseExpand({ + action: isExpanded ? "collapse" : "expand", + component: "module_interaction_function_accordion", + info: { Tab: amptrackTab }, + section: triggerText, + }); + }} + > + + + {triggerText} + + + + + +
+ + {filteredFns.length ? ( + + {filteredFns.map((fn) => ( + { + track(AmpEvent.USE_FUNCTION_SELECTION, { + functionType: fn.is_view ? "view" : "Execute", + }); + setSelectedFn(fn); + }} + /> + ))} + + ) : ( + - )) - ) : ( - - )} -
-
+ )} + + + )}
); diff --git a/src/lib/pages/interact/component/common/InteractionTypeSwitch.tsx b/src/lib/pages/interact/component/common/InteractionTypeSwitch.tsx index e6361ba97..4a1bf2dbd 100644 --- a/src/lib/pages/interact/component/common/InteractionTypeSwitch.tsx +++ b/src/lib/pages/interact/component/common/InteractionTypeSwitch.tsx @@ -2,6 +2,7 @@ import type { FlexProps } from "@chakra-ui/react"; import { Flex, Heading } from "@chakra-ui/react"; import type { Dispatch, SetStateAction } from "react"; +import { AmpEvent, track } from "lib/amplitude"; import { MotionBox } from "lib/components/MotionBox"; import type { Option } from "lib/types"; @@ -54,7 +55,10 @@ export const InteractionTypeSwitch = ({ }} initial="inactive" animate={currentTab === tab ? "active" : "inactive"} - onClick={() => onTabChange(tab)} + onClick={() => { + track(AmpEvent.USE_SUBTAB, { currentTab: tab }); + onTabChange(tab); + }} zIndex={1} textAlign="center" > diff --git a/src/lib/pages/interact/component/drawer/ModuleSelectDrawerTrigger.tsx b/src/lib/pages/interact/component/drawer/ModuleSelectDrawerTrigger.tsx index 8066ae8f0..adcd4437a 100644 --- a/src/lib/pages/interact/component/drawer/ModuleSelectDrawerTrigger.tsx +++ b/src/lib/pages/interact/component/drawer/ModuleSelectDrawerTrigger.tsx @@ -1,6 +1,7 @@ import type { ButtonProps } from "@chakra-ui/react"; import { Button } from "@chakra-ui/react"; +import { AmpEvent, track } from "lib/amplitude"; import { CustomIcon } from "lib/components/icon"; type TriggerVariant = "select-module" | "change-module"; @@ -26,7 +27,16 @@ export const ModuleSelectDrawerTrigger = ({ buttonText = "Select Module", onOpen, }: ModuleSelectDrawerTriggerProps) => ( - ); diff --git a/src/lib/pages/interact/component/drawer/body/ModuleFunctionBody.tsx b/src/lib/pages/interact/component/drawer/body/ModuleFunctionBody.tsx index af0d89832..e0cb6c251 100644 --- a/src/lib/pages/interact/component/drawer/body/ModuleFunctionBody.tsx +++ b/src/lib/pages/interact/component/drawer/body/ModuleFunctionBody.tsx @@ -4,6 +4,7 @@ import { useCallback, useMemo, useState } from "react"; import { ModuleEmptyState, NoImageEmptyState } from "../../common"; import type { ModuleSelectFunction } from "../types"; +import { AmpEvent, track } from "lib/amplitude"; import InputWithIcon from "lib/components/InputWithIcon"; import { CountBadge } from "lib/components/module/CountBadge"; import { FunctionCard } from "lib/components/module/FunctionCard"; @@ -73,6 +74,9 @@ export const ModuleFunctionBody = ({ const onFunctionSelect = useCallback( (fn: ExposedFunction) => { if (module) { + track(AmpEvent.USE_MODULE_SELECTION_FUNCTION, { + functionType: fn.is_view ? "view" : "Execute", + }); handleModuleSelect(module, fn); closeModal(); } @@ -95,6 +99,7 @@ export const ModuleFunctionBody = ({ {module.moduleName} setKeyword(e.target.value)} diff --git a/src/lib/pages/interact/component/drawer/body/ModuleSelectMainBody.tsx b/src/lib/pages/interact/component/drawer/body/ModuleSelectMainBody.tsx index 048d81545..8c34df508 100644 --- a/src/lib/pages/interact/component/drawer/body/ModuleSelectMainBody.tsx +++ b/src/lib/pages/interact/component/drawer/body/ModuleSelectMainBody.tsx @@ -8,6 +8,7 @@ import type { ModuleSelectFunction, SelectedAddress, } from "../types"; +import { AmpEvent, track } from "lib/amplitude"; import InputWithIcon from "lib/components/InputWithIcon"; import { CountBadge } from "lib/components/module/CountBadge"; import { ModuleCard } from "lib/components/module/ModuleCard"; @@ -95,6 +96,7 @@ export const ModuleSelectMainBody = ({ )} setKeyword(e.target.value)} @@ -134,6 +136,7 @@ export const ModuleSelectMainBody = ({ size="md" onClick={() => { if (selectedModule) { + track(AmpEvent.USE_MODULE_SELECTION_MODULE); handleModuleSelect(selectedModule); closeModal(); } diff --git a/src/lib/pages/interact/component/drawer/selector/ModuleSelectorInput.tsx b/src/lib/pages/interact/component/drawer/selector/ModuleSelectorInput.tsx index f03c91757..6d062a7a0 100644 --- a/src/lib/pages/interact/component/drawer/selector/ModuleSelectorInput.tsx +++ b/src/lib/pages/interact/component/drawer/selector/ModuleSelectorInput.tsx @@ -7,6 +7,7 @@ import type { DisplayMode, ModuleSelectFunction, } from "../types"; +import { trackUseModuleSelectionInputFill } from "lib/amplitude"; import { useExampleAddresses } from "lib/app-provider"; import { TextInput } from "lib/components/forms"; import { useFormatAddresses } from "lib/hooks/useFormatAddresses"; @@ -68,18 +69,23 @@ export const ModuleSelectorInput = ({ }); const handleSubmit = useCallback(() => { + trackUseModuleSelectionInputFill(addr, !!moduleName, !!functionName); + if (keyword === selectedAddress.address || keyword === selectedAddress.hex) return setMode("display"); const err = validateModuleInput(keyword); return err ? setError(err) : refetch(); }, [ + addr, + moduleName, + functionName, + keyword, selectedAddress.address, selectedAddress.hex, - keyword, - refetch, - validateModuleInput, setMode, + validateModuleInput, + refetch, ]); const handleKeydown = useCallback( diff --git a/src/lib/pages/interact/component/form/ExecuteArea.tsx b/src/lib/pages/interact/component/form/ExecuteArea.tsx index 4f23fc2b0..38fbebe94 100644 --- a/src/lib/pages/interact/component/form/ExecuteArea.tsx +++ b/src/lib/pages/interact/component/form/ExecuteArea.tsx @@ -5,6 +5,7 @@ import { MsgExecute as MsgExecuteModule } from "@initia/initia.js"; import dynamic from "next/dynamic"; import { useCallback, useEffect, useMemo, useState } from "react"; +import { AmpEvent, track } from "lib/amplitude"; import { useFabricateFee, useSimulateFeeQuery, @@ -185,7 +186,10 @@ export const ExecuteArea = ({ { + track(AmpEvent.ACTION_MOVE_EXECUTE); + proceed(); + }} isDisabled={isButtonDisabled} /> diff --git a/src/lib/pages/interact/component/form/ViewArea.tsx b/src/lib/pages/interact/component/form/ViewArea.tsx index 4f26f7a69..9f9ae6971 100644 --- a/src/lib/pages/interact/component/form/ViewArea.tsx +++ b/src/lib/pages/interact/component/form/ViewArea.tsx @@ -11,6 +11,7 @@ import { import dynamic from "next/dynamic"; import { useState } from "react"; +import { AmpEvent, track } from "lib/amplitude"; import { AbiForm } from "lib/components/abi"; import { SubmitButton } from "lib/components/button"; import { CustomIcon } from "lib/components/icon"; @@ -94,7 +95,10 @@ export const ViewArea = ({ { + track(AmpEvent.ACTION_MOVE_VIEW); + handleQuery(); + }} isDisabled={isButtonDisabled} /> @@ -133,7 +137,11 @@ export const ViewArea = ({ ) : ( - + )} diff --git a/src/lib/pages/interact/index.tsx b/src/lib/pages/interact/index.tsx index c678812ef..0810b1261 100644 --- a/src/lib/pages/interact/index.tsx +++ b/src/lib/pages/interact/index.tsx @@ -10,6 +10,7 @@ import { import { useRouter } from "next/router"; import { useCallback, useEffect, useState } from "react"; +import { AmpEvent, track, trackToModuleInteraction } from "lib/amplitude"; import { useInternalNavigate } from "lib/app-provider"; import { CustomIcon } from "lib/components/icon"; import { LabelText } from "lib/components/LabelText"; @@ -134,6 +135,18 @@ export const Interact = () => { } }, [addressParam, moduleNameParam, refetch]); + useEffect(() => { + if (router.isReady) + trackToModuleInteraction( + !!addressParam, + !!moduleNameParam, + !!verificationData, + !!functionNameParam, + functionTypeParam + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [router.isReady]); + return ( <> { } onClick={() => { + track(AmpEvent.USE_SEE_MODULE_BUTTON, { + isVerify: !!verificationData, + }); openNewTab({ pathname: `/modules/${module.address.toString()}/${ module.moduleName