feat(telemetry): product interaction events

- h2h_viewed from match card, dock, profile, stats, predictions
- reaction_sent with emoji and surface; score_report_opened from all
  three report entry points
- stats_filtered and stats_sorted on stats route and profile
- rules/teams/players/bracket/tournament view events with sources
- match_history_viewed, badge_viewed, player_searched (count only)
This commit is contained in:
2026-08-26 23:04:36 -07:00
parent fc65fdb451
commit f593b2c7b8
25 changed files with 747 additions and 629 deletions
@@ -1,4 +1,4 @@
import { useMemo } from "react";
import { useEffect, useMemo, useRef } from "react";
import { Box } from "@mantine/core";
import Header from "./header";
import TeamList from "@/features/teams/components/team-list";
@@ -7,6 +7,7 @@ import { useTournament } from "../../queries";
import MatchList from "@/features/matches/components/match-list";
import { TournamentStats } from "../tournament-stats";
import { useLingui } from "@lingui/react/macro";
import { track } from "@/lib/telemetry/client";
interface ProfileProps {
id: string;
@@ -15,6 +16,14 @@ interface ProfileProps {
const Profile = ({ id }: ProfileProps) => {
const { t } = useLingui();
const { data: tournament } = useTournament(id);
const trackedId = useRef<string | null>(null);
useEffect(() => {
if (!tournament || trackedId.current === tournament.id) return;
trackedId.current = tournament.id;
track("tournament_viewed", { tournament: tournament.name });
}, [tournament]);
if (!tournament) return null;
const tabs = useMemo(() => [
@@ -24,6 +24,7 @@ import { useSheet } from "@/hooks/use-sheet";
import TeamList from "@/features/teams/components/team-list";
import WizardOrbIcon from "@/components/wizard-orb-icon";
import { Tournament } from "../../../types";
import { track } from "@/lib/telemetry/client";
import classes from "./nav-grid.module.css";
const RulesContent = lazy(
@@ -86,9 +87,10 @@ const NavGrid = ({
<NavTile
label={t`Bracket`}
Icon={TreeStructureIcon}
onClick={() =>
navigate({ to: `/tournaments/${tournament.id}/bracket` })
}
onClick={() => {
track("bracket_viewed", { source: "nav_grid" });
navigate({ to: `/tournaments/${tournament.id}/bracket` });
}}
/>
{isPredictable && !predictionsLocked && (
<NavTile
@@ -118,9 +120,19 @@ const NavGrid = ({
: t`Teams`
}
Icon={UsersIcon}
onClick={teamsSheet.open}
onClick={() => {
track("teams_viewed", { source: "nav_grid" });
teamsSheet.open();
}}
/>
<NavTile
label={t`Rules`}
Icon={ListIcon}
onClick={() => {
track("rules_viewed", { source: "nav_grid" });
rulesSheet.open();
}}
/>
<NavTile label={t`Rules`} Icon={ListIcon} onClick={rulesSheet.open} />
</SimpleGrid>
<Sheet title={t`Enrolled Teams`} {...teamsSheet.props}>
@@ -4,6 +4,7 @@ import { useNavigate } from "@tanstack/react-router";
import { Trans, useLingui } from "@lingui/react/macro";
import MatchCard from "@/features/matches/components/match-card";
import { Match } from "@/features/matches/types";
import { track } from "@/lib/telemetry/client";
import SectionHeading from "./section-heading";
interface RecentResultsProps {
@@ -39,9 +40,10 @@ const RecentResults = ({ matches, tournamentId, max = 5 }: RecentResultsProps) =
type="button"
size="xs"
fw={600}
onClick={() =>
navigate({ to: `/tournaments/${tournamentId}/bracket` })
}
onClick={() => {
track("bracket_viewed", { source: "recent_results" });
navigate({ to: `/tournaments/${tournamentId}/bracket` });
}}
>
<Group gap={2} align="center" wrap="nowrap">
<Trans>View all results</Trans>
@@ -77,7 +77,7 @@ const ScoreReportCta = ({ matches }: ScoreReportCtaProps) => {
)}
</Group>
</Group>
<MatchReport match={match} />
<MatchReport match={match} source="score_cta" />
</Stack>
</Paper>
))}
@@ -9,6 +9,7 @@ import { Text } from "@mantine/core";
import PlayerList from "@/features/players/components/player-list";
import { Player } from "@/features/players/types";
import { NavTile } from "../started-tournament/sections/nav-grid";
import { track } from "@/lib/telemetry/client";
interface EnrolledPlayersListButtonProps {
tournamentId: string;
@@ -33,7 +34,10 @@ const EnrolledPlayersListButton: React.FC<EnrolledPlayersListButtonProps> = ({ t
values: { count },
})}
Icon={UserListIcon}
onClick={open}
onClick={() => {
track("players_viewed", { source: "upcoming" });
open();
}}
/>
<Sheet title={t`Enrolled Players`} opened={isOpen} onChange={toggle}>
@@ -6,6 +6,7 @@ import { useTournament } from "../../queries"
import { Button, Flex, Loader, Stack } from "@mantine/core"
import { lazy, Suspense } from "react"
import { NavTile } from "../started-tournament/sections/nav-grid"
import { track } from "@/lib/telemetry/client"
const RulesContent = lazy(() => import("./rules-content"));
@@ -20,7 +21,14 @@ const RulesListButton: React.FC<RulesListButtonProps> = ({ tournamentId }) => {
return (
<>
<NavTile label={t`Rules`} Icon={ListIcon} onClick={open} />
<NavTile
label={t`Rules`}
Icon={ListIcon}
onClick={() => {
track("rules_viewed", { source: "upcoming" });
open();
}}
/>
<Sheet title={t`Tournament Rules`} opened={isOpen} onChange={toggle}>
<Stack gap="xs">
@@ -7,6 +7,7 @@ import { useMemo } from "react"
import { useLingui } from "@lingui/react/macro"
import { msg } from "@lingui/core/macro"
import { NavTile } from "../started-tournament/sections/nav-grid"
import { track } from "@/lib/telemetry/client"
interface TeamListButtonProps {
teams: TeamInfo[]
@@ -25,7 +26,10 @@ const TeamListButton: React.FC<TeamListButtonProps> = ({ teams, isRegional }) =>
values: { count },
})}
Icon={UsersIcon}
onClick={open}
onClick={() => {
track("teams_viewed", { source: "upcoming" });
open();
}}
/>
<Sheet title={t`Enrolled Teams`} opened={isOpen} onChange={toggle}>