From 91e7ead5f17460171667169bc63e8bd6fe77726e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 05:09:24 +0000 Subject: [PATCH] feat(frontend): add privacy-respecting error and product analytics Co-authored-by: LSUDOKO <173903994+LSUDOKO@users.noreply.github.com> --- frontend/.env.example | 9 + frontend/app/app/page.tsx | 38 +- frontend/app/components/PostHogProvider.tsx | 41 + frontend/app/layout.tsx | 9 +- frontend/lib/analytics.ts | 72 + frontend/lib/scrub.test.ts | 52 + frontend/lib/scrub.ts | 37 + frontend/next-env.d.ts | 4 +- frontend/next.config.mjs | 8 +- frontend/package-lock.json | 3375 +++++++++++++++++-- frontend/package.json | 2 + frontend/sentry.client.config.ts | 11 + frontend/sentry.edge.config.ts | 11 + frontend/sentry.server.config.ts | 11 + frontend/tsconfig.tsbuildinfo | 2 +- 15 files changed, 3383 insertions(+), 299 deletions(-) create mode 100644 frontend/app/components/PostHogProvider.tsx create mode 100644 frontend/lib/analytics.ts create mode 100644 frontend/lib/scrub.test.ts create mode 100644 frontend/lib/scrub.ts create mode 100644 frontend/sentry.client.config.ts create mode 100644 frontend/sentry.edge.config.ts create mode 100644 frontend/sentry.server.config.ts diff --git a/frontend/.env.example b/frontend/.env.example index b0a87d5..e3dd09a 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -14,3 +14,12 @@ NEXT_PUBLIC_TOKEN_OUT=0xC67DCE33D7A8efA5FfEB961899C73fe01bCe9273 # FTSO feed the trigger watches. Default is FLR/USD. NEXT_PUBLIC_FEED_ID=0x01464c522f55534400000000000000000000000000 + +# Sentry error tracking +# Leave empty in local dev and CI to disable. +NEXT_PUBLIC_SENTRY_DSN= + +# PostHog funnel analytics +# Leave empty in local dev and CI to disable. +NEXT_PUBLIC_POSTHOG_KEY= +NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com diff --git a/frontend/app/app/page.tsx b/frontend/app/app/page.tsx index 6b7b372..1c4ad92 100644 --- a/frontend/app/app/page.tsx +++ b/frontend/app/app/page.tsx @@ -25,6 +25,7 @@ import { } from "@/lib/wraith"; import { Ticker } from "@/app/components/Ticker"; import { ActivityLog } from "@/app/components/ActivityLog"; +import { trackEvent, setPersonProperties, trackError } from "@/lib/analytics"; const WRAITH_ADDRESS = (process.env.NEXT_PUBLIC_WRAITH_ADDRESS ?? "") as Address; const FXRP_ADDRESS = (process.env.NEXT_PUBLIC_FXRP_ADDRESS ?? "") as Address; @@ -76,6 +77,14 @@ export default function Home() { const [minOut, setMinOut] = useState("150"); const [xrplAddress, setXrplAddress] = useState(""); const [days, setDays] = useState("7"); + const [composeStarted, setComposeStarted] = useState(false); + + const startCompose = useCallback(() => { + if (!composeStarted) { + setComposeStarted(true); + trackEvent("order_compose_started", { wallet_connected: Boolean(account) }); + } + }, [composeStarted, account]); const say = (message: string, kind: "info" | "error" = "info") => { setStatus(message); @@ -134,6 +143,16 @@ export default function Home() { return () => clearInterval(interval); }, [loadOrders]); + useEffect(() => { + setPersonProperties({ wallet_connected: Boolean(account) }); + }, [account]); + + useEffect(() => { + if (wrongNetwork) { + trackEvent("wrong_network_shown", { wallet_connected: Boolean(account) }); + } + }, [wrongNetwork, account]); + const stats = useMemo(() => { const escrowed = orders .filter((o) => o.state === "sealed") @@ -165,6 +184,7 @@ export default function Home() { setWrongNetwork(chainId !== coston2.id); say(chainId === coston2.id ? "Wallet connected." : "Wallet connected, but it is on the wrong network."); } catch (error) { + trackError(error); say(error instanceof Error ? error.message.split("\n")[0] : "Could not connect.", "error"); } } @@ -243,8 +263,10 @@ export default function Home() { setLastTx(hash); say("Sealed. Your trigger never touched the chain in the clear."); + trackEvent("order_sealed", { wallet_connected: true }); await loadOrders(); } catch (error) { + trackError(error); const message = error instanceof Error ? error.message : String(error); say(message.split("\n")[0], "error"); } finally { @@ -270,8 +292,10 @@ export default function Home() { setLastTx(hash); say(`Order ${orderId} cancelled. Escrow refunded.`); + trackEvent("order_cancelled", { wallet_connected: true }); await loadOrders(); } catch (error) { + trackError(error); const message = error instanceof Error ? error.message : String(error); say(message.split("\n")[0], "error"); } finally { @@ -334,19 +358,19 @@ export default function Home() {