Skip to content

Commit f8fd88e

Browse files
Add Google analytics
1 parent b16f13d commit f8fd88e

File tree

19 files changed

+343
-51
lines changed

19 files changed

+343
-51
lines changed

apps/web/app/(authenticated)/vault/[vaultId]/(dashboard)/layout.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { Dialog, DialogTrigger } from "@/components/ui/dialog";
1616
import ReceiveModal from "@/components/modals/ReceiveModal";
1717
import PageVaultHeader from "@/components/PageVaultHeader";
1818
import SendCoinsModal from "@/components/modals/SendCoinsModal";
19+
import useAnalytics from "@/hooks/useAnalytics";
1920

2021
const tabs = [
2122
{
@@ -31,6 +32,7 @@ const tabs = [
3132
] as const;
3233

3334
export default function VaultLayout({ children }: PropsWithChildren) {
35+
const trackEvent = useAnalytics();
3436
const [isSendCoinsModalOpen, setIsSendCoinsModalOpen] = useState(false);
3537

3638
const pathname = usePathname();
@@ -83,7 +85,10 @@ export default function VaultLayout({ children }: PropsWithChildren) {
8385
<Button
8486
size="lg"
8587
className="px-6"
86-
onClick={() => setIsSendCoinsModalOpen(true)}
88+
onClick={() => {
89+
trackEvent("send_coins_review_draft", {});
90+
setIsSendCoinsModalOpen(true);
91+
}}
8792
disabled={!isOwner}
8893
data-testid="send-coins-button"
8994
>

apps/web/app/(authenticated)/vault/[vaultId]/proposal/create/page.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,11 @@ import { Separator } from "@/components/ui/separator";
4040
import Callout from "@/components/Callout";
4141
import { getSimulationQueryErrors } from "@/lib/simulations/shared";
4242
import { jsonStringify } from "@/lib/storage";
43+
import useAnalytics from "@/hooks/useAnalytics";
4344

4445
export default function CreateProposalPage() {
46+
const trackEvent = useAnalytics();
47+
4548
const router = useRouter();
4649

4750
const [page, setPage] = useState<"set-details" | "confirm">("set-details");
@@ -55,10 +58,20 @@ export default function CreateProposalPage() {
5558
hash,
5659
signAndSubmitTransaction,
5760
isPending: isSigningAndSubmitting,
58-
} = useSignAndSubmitTransaction();
61+
} = useSignAndSubmitTransaction({
62+
onSuccess: (data) => {
63+
trackEvent("create_proposal", {
64+
entry_function_id: entryFunction.value,
65+
hash: data.hash,
66+
});
67+
},
68+
});
5969

60-
const { isSuccess, isLoading: isWaitingForTransaction } =
61-
useWaitForTransaction({ hash });
70+
const {
71+
isSuccess,
72+
isError,
73+
isLoading: isWaitingForTransaction,
74+
} = useWaitForTransaction({ hash });
6275

6376
const { transactionPayload, innerPayload } = useMemo(() => {
6477
if (!abi.value || !isFormValid.value) {
@@ -105,20 +118,23 @@ export default function CreateProposalPage() {
105118
});
106119

107120
const createProposal = useCallback(() => {
108-
if (!transactionPayload) {
121+
if (!transactionPayload || !entryFunction.value) {
109122
toast.error(`There was an error creating your proposal`);
110123
return;
111124
}
112125

113126
signAndSubmitTransaction({ data: transactionPayload });
114-
}, [signAndSubmitTransaction, transactionPayload]);
127+
}, [signAndSubmitTransaction, transactionPayload, entryFunction.value]);
115128

116129
useEffect(() => {
117130
if (isSuccess) {
118131
toast.success("Proposal created");
119132
router.push(`/vault/${id}/transactions`);
133+
} else if (isError) {
134+
toast.error("Proposal creation failed");
120135
}
121-
}, [isSuccess, router, vaultAddress, hash, id]);
136+
// eslint-disable-next-line react-hooks/exhaustive-deps
137+
}, [isSuccess, isError]);
122138

123139
const balanceChanges =
124140
simulation.data &&

apps/web/app/(authenticated)/vault/[vaultId]/proposal/pending/[sequenceNumber]/page.tsx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,11 @@ import { PendingTransactionRow } from "@/components/PendingTransactionRow";
4040
import { useRouter } from "next/navigation";
4141
import { jsonStringify } from "@/lib/storage";
4242
import Link from "next/link";
43+
import useAnalytics from "@/hooks/useAnalytics";
4344

4445
export default function ProposalPage() {
46+
const trackEvent = useAnalytics();
47+
4548
const queryClient = useQueryClient();
4649
const router = useRouter();
4750

@@ -78,7 +81,20 @@ export default function ProposalPage() {
7881
hash: secondaryActionHash,
7982
signAndSubmitTransaction: signAndSubmitSecondaryAction,
8083
isPending: isSignAndSubmitSecondaryActionPending,
81-
} = useSignAndSubmitTransaction();
84+
} = useSignAndSubmitTransaction({
85+
onSuccess(data, variables) {
86+
trackEvent("vote_proposal", {
87+
hash: data.hash,
88+
action:
89+
"data" in variables &&
90+
"function" in variables.data &&
91+
variables.data.function ===
92+
"0x1::multisig_account::approve_transaction"
93+
? "approve"
94+
: "reject",
95+
});
96+
},
97+
});
8298

8399
const {
84100
isSuccess: isSecondaryActionSuccess,
@@ -111,7 +127,25 @@ export default function ProposalPage() {
111127
hash: primaryActionHash,
112128
signAndSubmitTransaction: signAndSubmitPrimaryAction,
113129
isPending: isSignAndSubmitPrimaryActionPending,
114-
} = useSignAndSubmitTransaction();
130+
} = useSignAndSubmitTransaction({
131+
onSuccess(data, variables) {
132+
if ("data" in variables && "function" in variables.data) {
133+
if (
134+
variables.data.function ===
135+
"0x1::multisig_account::execute_transaction"
136+
) {
137+
trackEvent("execute_proposal", {
138+
hash: data.hash,
139+
});
140+
} else if (
141+
variables.data.function ===
142+
"0x1::multisig_account::execute_rejected_transaction"
143+
) {
144+
trackEvent("remove_proposal", { hash: data.hash });
145+
}
146+
}
147+
},
148+
});
115149

116150
const {
117151
isSuccess: isPrimaryActionSuccess,

apps/web/app/(authenticated)/vault/[vaultId]/proposal/publish-contract/page.tsx

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import { useRouter } from "next/navigation";
4343
import { Abis } from "@/lib/abis";
4444
import { getSimulationQueryErrors } from "@/lib/simulations/shared";
4545
import { jsonStringify } from "@/lib/storage";
46-
46+
import useAnalytics from "@/hooks/useAnalytics";
4747
const publishModuleJsonSchema = z.object({
4848
function_id: z.string(),
4949
type_args: z.array(z.object({ type: z.string(), value: z.string() })),
@@ -54,6 +54,7 @@ const publishModuleJsonSchema = z.object({
5454
});
5555

5656
export default function PublishContractPage() {
57+
const trackEvent = useAnalytics();
5758
const queryClient = useQueryClient();
5859
const router = useRouter();
5960

@@ -63,15 +64,6 @@ export default function PublishContractPage() {
6364

6465
const [page, setPage] = useState<"draft" | "confirm">("draft");
6566

66-
const {
67-
hash,
68-
signAndSubmitTransaction,
69-
isPending: isSigningAndSubmitting,
70-
} = useSignAndSubmitTransaction();
71-
72-
const { isSuccess, isLoading: isWaitingForTransaction } =
73-
useWaitForTransaction({ hash });
74-
7567
const { data: jsonData, isError: isJsonError } = useQuery({
7668
queryKey: ["publish-module", file?.lastModified],
7769
queryFn: async () => {
@@ -114,6 +106,19 @@ export default function PublishContractPage() {
114106
}
115107
}, [jsonData, vaultAddress]);
116108

109+
const {
110+
hash,
111+
signAndSubmitTransaction,
112+
isPending: isSigningAndSubmitting,
113+
} = useSignAndSubmitTransaction({
114+
onSuccess: (data) => {
115+
trackEvent("create_publish_contract_proposal", { hash: data.hash });
116+
},
117+
});
118+
119+
const { isSuccess, isLoading: isWaitingForTransaction } =
120+
useWaitForTransaction({ hash });
121+
117122
const createProposal = useCallback(() => {
118123
if (!transactionPayload) {
119124
toast.error(`There was an error creating your proposal`);

apps/web/app/(authenticated)/vault/[vaultId]/settings/export/page.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import { DownloadIcon } from "@radix-ui/react-icons";
1414
import CodeBlock from "@/components/CodeBlock";
1515
import { jsonStringify } from "@/lib/storage";
1616
import CopyButton from "@/components/CopyButton";
17-
17+
import useAnalytics from "@/hooks/useAnalytics";
1818
export default function ExportSettingsPage() {
19+
const trackEvent = useAnalytics();
1920
const { vaults } = useVaults();
2021

2122
const exportVaultsJSON = jsonStringify(vaults);
@@ -45,7 +46,13 @@ export default function ExportSettingsPage() {
4546
</div>
4647
<br />
4748
<div className="flex gap-2">
48-
<Button asChild data-testid="export-vaults-button">
49+
<Button
50+
asChild
51+
data-testid="export-vaults-button"
52+
onClick={() => {
53+
trackEvent("download_backup_file", {});
54+
}}
55+
>
4956
<a
5057
href={`data:text/json;charset=utf-8,${exportVaultsJSON}`}
5158
download="petra-vaults-export.json"

apps/web/app/(authenticated)/vault/[vaultId]/settings/page.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@ import {
3434
AlertDialogDescription,
3535
} from "@/components/ui/alert-dialog";
3636
import { Dialog, DialogTrigger } from "@/components/ui/dialog";
37+
import useAnalytics from "@/hooks/useAnalytics";
3738

3839
export default function VaultSettingsPage() {
40+
const trackEvent = useAnalytics();
41+
3942
const router = useRouter();
4043

4144
// This is a patch to reset the modal's state when it is closed.
@@ -202,6 +205,7 @@ export default function VaultSettingsPage() {
202205
<Button
203206
variant="destructive"
204207
data-testid="delete-vault-button"
208+
onClick={() => trackEvent("delete_vault_attempt", {})}
205209
>
206210
<TrashIcon />
207211
Remove Vault
@@ -226,6 +230,7 @@ export default function VaultSettingsPage() {
226230
variant="destructive"
227231
onClick={() => {
228232
deleteVault(AccountAddress.from(vaultAddress));
233+
trackEvent("delete_vault_success", {});
229234
router.push("/");
230235
}}
231236
data-testid="confirm-delete-vault-button"

apps/web/app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Toaster } from "@/components/ui/sonner";
55
import AppProviders from "@/context/AppProviders";
66
import { ThemeProvider } from "@/context/ThemeProvider";
77
import { ReactScan } from "@/components/ReactScan";
8+
import Analytics from "@/components/background/Analytics";
89

910
const workSans = Work_Sans({
1011
display: "swap",
@@ -48,6 +49,7 @@ export default function RootLayout({
4849
<Toaster richColors closeButton />
4950
</ThemeProvider>
5051
</body>
52+
<Analytics />
5153
</AppProviders>
5254
</html>
5355
);

apps/web/components/OnboardingAddOrImport.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ import UploadImportJSONModal from "./modals/UploadImportJSONModal";
2525
import { Vault } from "@/lib/types/vaults";
2626
import { useRouter } from "next/navigation";
2727
import { AccountAddress } from "@aptos-labs/ts-sdk";
28+
import useAnalytics from "@/hooks/useAnalytics";
2829

2930
export default function OnboardingAddOrImport() {
31+
const trackEvent = useAnalytics();
3032
const router = useRouter();
3133
const { page, vaultName, importVaultAddress } = useOnboarding();
3234
const { vaults, importVaults } = useVaults();
@@ -80,13 +82,15 @@ export default function OnboardingAddOrImport() {
8082
"Your connected account is not an owner of this Multisig account."
8183
);
8284
}
85+
trackEvent("manual_import_vault", { owners: owners.length });
8386
} catch {
8487
toast.error("This account is not a Multisig account");
8588
}
8689
},
8790
});
8891

8992
const handleImportVaults = (vaults: Vault[]) => {
93+
trackEvent("backup_import_vault", { vault_count: vaults.length });
9094
importVaults(vaults);
9195
router.push(`/`);
9296
toast.success(
@@ -109,6 +113,7 @@ export default function OnboardingAddOrImport() {
109113
vaultName.set(values.name);
110114
importVaultAddress.set("");
111115
page.set("set-config");
116+
trackEvent("set_vault_name", {});
112117
}}
113118
/>
114119
</CardContent>
@@ -222,6 +227,7 @@ export default function OnboardingAddOrImport() {
222227
variant="link"
223228
size="sm"
224229
onClick={() => {
230+
trackEvent("select_discovered_vault", {});
225231
importVaultAddress.set(e.toString());
226232
handleLookUpAndImportAccount(e.toString());
227233
}}

apps/web/components/OnboardingImportSetName.tsx

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ import useMultisigSignaturesRequired from "@/hooks/useMultisigSignaturesRequired
1313
import { toast } from "sonner";
1414
import { AccountAddress } from "@aptos-labs/ts-sdk";
1515
import { useNetwork } from "@aptos-labs/react";
16+
import useAnalytics from "@/hooks/useAnalytics";
1617

1718
export default function OnboardingImportSetName() {
19+
const trackEvent = useAnalytics();
1820
const { importVaultAddress, importVault } = useOnboarding();
1921
const network = useNetwork();
2022

@@ -26,6 +28,30 @@ export default function OnboardingImportSetName() {
2628
address: importVaultAddress.current,
2729
});
2830

31+
const handleImportVault = (name: string) => {
32+
if (!owners || !signaturesRequired) {
33+
toast.error("Failed to fetch owners or signatures required");
34+
return;
35+
}
36+
37+
trackEvent("create_imported_vault", {
38+
signatures_required: signaturesRequired,
39+
owners: owners.length,
40+
});
41+
42+
importVault({
43+
type: "framework",
44+
name: name,
45+
signers: owners.map((e, i) => ({
46+
address: AccountAddress.from(e),
47+
name: `Owner ${i + 1}`,
48+
})),
49+
signaturesRequired,
50+
address: AccountAddress.from(importVaultAddress.current),
51+
network: network.network,
52+
});
53+
};
54+
2955
if (!importVaultAddress.current) {
3056
return <div>No vault address found</div>;
3157
}
@@ -47,24 +73,7 @@ export default function OnboardingImportSetName() {
4773
<CardContent>
4874
<VaultImportNameForm
4975
address={importVaultAddress.current}
50-
onSubmit={(e) => {
51-
if (!owners || !signaturesRequired) {
52-
toast.error("Failed to fetch owners or signatures required");
53-
return;
54-
}
55-
56-
importVault({
57-
type: "framework",
58-
name: e.name,
59-
signers: owners.map((e, i) => ({
60-
address: AccountAddress.from(e),
61-
name: `Owner ${i + 1}`,
62-
})),
63-
signaturesRequired,
64-
address: AccountAddress.from(importVaultAddress.current),
65-
network: network.network,
66-
});
67-
}}
76+
onSubmit={(e) => handleImportVault(e.name)}
6877
>
6978
<div>
7079
<Label>Owners</Label>

0 commit comments

Comments
 (0)