From d34357e0a92d052dabf705c2a27fecc1b8125f4b Mon Sep 17 00:00:00 2001 From: gregfromstl Date: Fri, 8 Nov 2024 00:32:04 +0000 Subject: [PATCH] [Dashboard] Fix: Cleanup dashboard UI (#5344) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CNCT-2282 --- ## PR-Codex overview This PR primarily focuses on refactoring components related to analytics and charts in the dashboard, removing unused components, improving data handling, and enhancing UI elements for better responsiveness and clarity. ### Detailed summary - Deleted `ConnectSDKCard.tsx` and `ConnectAnalyticsDashboard.tsx` files. - Removed the `Analytics` link from the sidebar. - Enhanced `PieChartCard` and `BarChart` components with better data mapping and UI adjustments. - Updated `RangeSelector` to handle local state for range and interval. - Removed unused API functions related to wallet usage. - Improved data filtering logic in `WalletDistributionCard` and `AuthMethodDistributionCard`. - Adjusted image rendering for better layout consistency in charts. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- .../src/@3rdweb-sdk/react/hooks/useApi.ts | 131 ------------------ .../[project_slug]/components/BarChart.tsx | 5 +- .../components/CombinedBarChartCard.tsx | 1 + .../[project_slug]/components/PieChart.tsx | 8 +- .../components/PieChartCard.tsx | 10 +- .../analytics/ConnectAnalyticsDashboard.tsx | 87 ------------ .../[project_slug]/connect/layout.tsx | 5 - .../[project_slug]/connect/page.tsx | 24 +--- .../team/[team_slug]/[project_slug]/page.tsx | 91 ++++++------ .../components/analytics/range-selector.tsx | 39 +++++- .../src/components/shared/ConnectSDKCard.tsx | 71 ---------- 11 files changed, 102 insertions(+), 370 deletions(-) delete mode 100644 apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/analytics/ConnectAnalyticsDashboard.tsx delete mode 100644 apps/dashboard/src/components/shared/ConnectSDKCard.tsx diff --git a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useApi.ts b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useApi.ts index caadf46dfe1..2ca4f9236cc 100644 --- a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useApi.ts +++ b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useApi.ts @@ -380,43 +380,6 @@ export function useAccountCredits() { }); } -async function getWalletUsage(args: { - clientId: string; - from?: Date; - to?: Date; - period?: "day" | "week" | "month" | "year" | "all"; -}) { - const { clientId, from, to, period } = args; - - const searchParams = new URLSearchParams(); - searchParams.append("clientId", clientId); - if (from) { - searchParams.append("from", from.toISOString()); - } - if (to) { - searchParams.append("to", to.toISOString()); - } - if (period) { - searchParams.append("period", period); - } - const res = await fetch( - `${THIRDWEB_ANALYTICS_API_HOST}/v1/wallets?${searchParams.toString()}`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }, - ); - const json = await res.json(); - - if (res.status !== 200) { - throw new Error(json.message); - } - - return json.data; -} - async function getUserOpUsage(args: { clientId: string; from?: Date; @@ -454,43 +417,6 @@ async function getUserOpUsage(args: { return json.data; } -export async function getInAppWalletUsage(args: { - clientId: string; - from?: Date; - to?: Date; - period?: "day" | "week" | "month" | "year" | "all"; -}) { - const { clientId, from, to, period } = args; - - const searchParams = new URLSearchParams(); - searchParams.append("clientId", clientId); - if (from) { - searchParams.append("from", from.toISOString()); - } - if (to) { - searchParams.append("to", to.toISOString()); - } - if (period) { - searchParams.append("period", period); - } - const res = await fetch( - `${THIRDWEB_ANALYTICS_API_HOST}/v1/wallets/in-app?${searchParams.toString()}`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }, - ); - const json = await res?.json(); - - if (!res || res.status !== 200) { - throw new Error(json.message); - } - - return json.data; -} - export function useUserOpUsageAggregate(args: { clientId: string; from?: Date; @@ -574,63 +500,6 @@ export function useUserOpUsagePeriod(args: { }); } -export function useWalletUsageAggregate(args: { - clientId: string; - from?: Date; - to?: Date; -}) { - const { clientId, from, to } = args; - const { user, isLoggedIn } = useLoggedInUser(); - - return useQuery({ - queryKey: accountKeys.walletStats( - user?.address as string, - clientId as string, - from?.toISOString() || "", - to?.toISOString() || "", - "all", - ), - queryFn: async () => { - return getWalletUsage({ - clientId, - from, - to, - period: "all", - }); - }, - enabled: !!clientId && !!user?.address && isLoggedIn, - }); -} - -export function useWalletUsagePeriod(args: { - clientId: string; - from?: Date; - to?: Date; - period: "day" | "week" | "month" | "year"; -}) { - const { clientId, from, to, period } = args; - const { user, isLoggedIn } = useLoggedInUser(); - - return useQuery({ - queryKey: accountKeys.walletStats( - user?.address as string, - clientId as string, - from?.toISOString() || "", - to?.toISOString() || "", - period, - ), - queryFn: async () => { - return getWalletUsage({ - clientId, - from, - to, - period, - }); - }, - enabled: !!clientId && !!user?.address && isLoggedIn, - }); -} - export function useUpdateAccount() { const { user } = useLoggedInUser(); const queryClient = useQueryClient(); diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/BarChart.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/BarChart.tsx index 69375c6017a..a2979962263 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/BarChart.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/BarChart.tsx @@ -59,7 +59,7 @@ export function BarChart({ }} /> { return new Date(value).toLocaleDateString("en-US", { @@ -77,6 +77,7 @@ export function BarChart({ year: "numeric", }); }} + valueFormatter={(v: unknown) => formatTickerNumber(v as number)} /> } /> diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/CombinedBarChartCard.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/CombinedBarChartCard.tsx index f904ad08c84..b7602d9666c 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/CombinedBarChartCard.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/CombinedBarChartCard.tsx @@ -53,6 +53,7 @@ export function CombinedBarChartCard< usersChart: key, }, }} + prefetch scroll={false} key={chart} data-active={activeChart === chart} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/PieChart.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/PieChart.tsx index ccce7e4028f..395b182a0dd 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/PieChart.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/PieChart.tsx @@ -6,6 +6,7 @@ import { ChartTooltip, ChartTooltipContent, } from "@/components/ui/chart"; +import { formatTickerNumber } from "lib/format-utils"; import { Pie, PieChart as RechartsPieChart } from "recharts"; export function PieChart({ @@ -39,7 +40,12 @@ export function PieChart({ } + content={ + formatTickerNumber(v as number)} + /> + } /> diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/PieChartCard.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/PieChartCard.tsx index fa2a9032545..2bcd002d94c 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/PieChartCard.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/PieChartCard.tsx @@ -28,7 +28,9 @@ export function PieChartCard({ const sorted = [...data].sort((a, b) => b.value - a.value); // Take top 9 - const top10 = sorted.slice(0, 9); + const top10 = sorted.slice(0, 9).map((item) => ({ + ...item, + })); // Aggregate the rest const otherValue = sorted @@ -44,7 +46,11 @@ export function PieChartCard({ } return top10; - })(); + })().map((item, index) => ({ + ...item, + fill: item.fill || `hsl(var(--chart-${index + 1}))`, + })); + return ( diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/analytics/ConnectAnalyticsDashboard.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/analytics/ConnectAnalyticsDashboard.tsx deleted file mode 100644 index 73b9c24ae76..00000000000 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/analytics/ConnectAnalyticsDashboard.tsx +++ /dev/null @@ -1,87 +0,0 @@ -"use client"; -import { - getInAppWalletUsage, - useUserOpUsageAggregate, - useWalletUsageAggregate, - useWalletUsagePeriod, -} from "@3rdweb-sdk/react/hooks/useApi"; -import { useQuery } from "@tanstack/react-query"; -import { - type Range, - getLastNDaysRange, -} from "components/analytics/date-range-selector"; -import { differenceInDays } from "date-fns"; -import { useState } from "react"; -import { ConnectAnalyticsDashboardUI } from "./ConnectAnalyticsDashboardUI"; - -export function ConnectAnalyticsDashboard(props: { - clientId: string; - connectLayoutSlug: string; -}) { - const [range, setRange] = useState(() => - getLastNDaysRange("last-120"), - ); - - // use date-fns to calculate the number of days in the range - const daysInRange = differenceInDays(range.to, range.from); - const [intervalType, setIntervalType] = useState<"day" | "week">( - daysInRange > 30 ? "week" : "day", - ); - - const walletUsageQuery = useWalletUsagePeriod({ - clientId: props.clientId, - from: range.from, - to: range.to, - period: intervalType, - }); - - const walletUsageAggregateQuery = useWalletUsageAggregate({ - clientId: props.clientId, - from: range.from, - to: range.to, - }); - - const userOpAggregateQuery = useUserOpUsageAggregate({ - clientId: props.clientId, - }); - - const inAppAggregateQuery = useQuery({ - queryKey: ["in-app-usage-aggregate", props.clientId], - queryFn: async () => { - const [allTimeStats, monthlyStats] = await Promise.all([ - getInAppWalletUsage({ - clientId: props.clientId, - from: new Date(2022, 0, 1), - to: new Date(), - period: "all", - }), - getInAppWalletUsage({ - clientId: props.clientId, - from: new Date(new Date().getFullYear(), new Date().getMonth(), 1), - to: new Date(), - period: "month", - }), - ]); - return { allTimeStats, monthlyStats }; - }, - }); - - return ( -
- -
- ); -} diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/layout.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/layout.tsx index 7b233823438..81bf85e75eb 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/layout.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/layout.tsx @@ -12,11 +12,6 @@ export default async function Layout(props: { const { team_slug, project_slug } = await props.params; const links: SidebarLink[] = [ - { - label: "Analytics", - href: `/team/${team_slug}/${project_slug}/connect`, - exactMatch: true, - }, { label: "In-App Wallets", href: `/team/${team_slug}/${project_slug}/connect/in-app-wallets`, diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/page.tsx index 67a91ba597d..e8c8a734c52 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/page.tsx @@ -1,7 +1,5 @@ import { getProject } from "@/api/projects"; -import { ConnectSDKCard } from "components/shared/ConnectSDKCard"; -import { notFound } from "next/navigation"; -import { ConnectAnalyticsDashboard } from "./analytics/ConnectAnalyticsDashboard"; +import { notFound, redirect } from "next/navigation"; export default async function Page(props: { params: Promise<{ @@ -16,23 +14,7 @@ export default async function Page(props: { notFound(); } - return ( -
-
-

- Connect Analytics -

-

- Visualize how your users are connecting to your app -

-
-
- -
- -
+ redirect( + `/team/${params.team_slug}/${params.project_slug}/connect/in-app-wallets`, ); } diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/page.tsx index 79f129bdcd9..e25257be171 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/page.tsx @@ -181,7 +181,7 @@ function processTimeSeriesData( let cumulativeUsers = 0; for (const stat of userStats) { - cumulativeUsers += stat.totalUsers ?? 0; + cumulativeUsers += stat.newUsers ?? 0; metrics.push({ date: stat.date, activeUsers: stat.totalUsers ?? 0, @@ -224,6 +224,14 @@ function UsersChartCard({ aggregateFn={(_data, key) => timeSeriesData[timeSeriesData.length - 1]?.[key] } + // Get the trend from the last two COMPLETE periods + trendFn={(data, key) => + data.filter((d) => (d[key] as number) > 0).length >= 3 + ? ((data[data.length - 2]?.[key] as number) ?? 0) / + ((data[data.length - 3]?.[key] as number) ?? 0) - + 1 + : undefined + } existingQueryParams={searchParams} /> ); @@ -232,12 +240,7 @@ function UsersChartCard({ async function WalletDistributionCard({ data }: { data: WalletStats[] }) { const formattedData = await Promise.all( data - .filter( - (w) => - w.walletType !== "smart" && - w.walletType !== "unknown" && - w.walletType !== "inApp", - ) + .filter((w) => w.walletType !== "smart" && w.walletType !== "smartWallet") .map(async (w) => { const wallet = await getWalletInfo(w.walletType as WalletId).catch( () => ({ name: w.walletType }), @@ -254,16 +257,12 @@ async function WalletDistributionCard({ data }: { data: WalletStats[] }) { return ( { - return { - value: uniqueWalletsConnected, - label: walletName, - // Multiply by 2 to avoid colors used by authentication methods - fill: `hsl(var(--chart-${(index + 1) * 2}))`, - }; - }, - )} + data={formattedData.map(({ walletName, uniqueWalletsConnected }) => { + return { + value: uniqueWalletsConnected, + label: walletName, + }; + })} /> ); } @@ -272,14 +271,10 @@ function AuthMethodDistributionCard({ data }: { data: InAppWalletStats[] }) { return ( ({ - value: uniqueWalletsConnected, - label: authenticationMethod, - // Multiply by 2 and add 1 to avoid colors used by wallets connected - fill: `hsl(var(--chart-${index * 2 + 1}))`, - }), - )} + data={data.map(({ authenticationMethod, uniqueWalletsConnected }) => ({ + value: uniqueWalletsConnected, + label: authenticationMethod, + }))} /> ); } @@ -305,17 +300,18 @@ async function TotalSponsoredCard({ data }: { data: UserOpStatsByChain[] }) { label: chain?.name || item.chainId || "Unknown", value: item.sponsoredUsd, icon: chain?.icon?.url ? ( - // eslint-disable-next-line @next/next/no-img-element - +
+ +
) : undefined, fill: `hsl(var(--chart-${index + 1}))`, }; @@ -345,17 +341,18 @@ async function UserOpUsageCard({ data }: { data: UserOpStatsByChain[] }) { label: chain?.name || item.chainId || "Unknown", value: item.successful + item.failed, icon: chain?.icon?.url ? ( - // eslint-disable-next-line @next/next/no-img-element - +
+ +
) : undefined, fill: `hsl(var(--chart-${index + 1}))`, }; diff --git a/apps/dashboard/src/components/analytics/range-selector.tsx b/apps/dashboard/src/components/analytics/range-selector.tsx index 018166b5f84..1840210d2bc 100644 --- a/apps/dashboard/src/components/analytics/range-selector.tsx +++ b/apps/dashboard/src/components/analytics/range-selector.tsx @@ -12,6 +12,7 @@ import type { import { IntervalSelector } from "components/analytics/interval-selector"; import { differenceInDays } from "date-fns"; import { usePathname, useSearchParams } from "next/navigation"; +import { useState } from "react"; export function RangeSelector({ range, @@ -20,11 +21,16 @@ export function RangeSelector({ const pathname = usePathname(); const searchParams = useSearchParams(); const router = useDashboardRouter(); + const [localRange, setRange] = useState( + range || getLastNDaysRange("last-120"), + ); + const [localInterval, setInterval] = useState<"day" | "week">(interval); - const { data: computedRange } = useQuery({ + useQuery({ queryKey: ["analytics-range", searchParams?.toString(), range], queryFn: async () => { if (range) { + setRange(range); return range; } if (searchParams) { @@ -35,17 +41,43 @@ export function RangeSelector({ const toStringified = searchParams.get("to"); const to = toStringified ? new Date(toStringified) : new Date(); const type = (searchParams.get("type") as DurationId) || "last-120"; + setRange({ from, to, type }); return { from, to, type } satisfies Range; } return getLastNDaysRange("last-120"); }, }); + // prefetch for each interval and default range + useQuery({ + queryKey: ["analytics-range", searchParams?.toString()], + queryFn: async () => { + const newSearchParams = new URLSearchParams(searchParams || {}); + for (const interval of ["day", "week"] as const) { + newSearchParams.set("interval", interval); + for (const type of [ + "last-120", + "last-60", + "last-30", + "last-7", + ] as const) { + const newRange = getLastNDaysRange(type); + newSearchParams.set("from", newRange.from.toISOString()); + newSearchParams.set("to", newRange.to.toISOString()); + newSearchParams.set("type", newRange.type); + router.prefetch(`${pathname}?${newSearchParams.toString()}`); + } + } + return true; + }, + }); + return (
{ + setRange(newRange); const days = differenceInDays(newRange.to, newRange.from); const interval = days > 30 ? "week" : "day"; const newSearchParams = new URLSearchParams(searchParams || {}); @@ -57,8 +89,9 @@ export function RangeSelector({ }} /> { + setInterval(newInterval); const newSearchParams = new URLSearchParams(searchParams || {}); newSearchParams.set("interval", newInterval); router.push(`${pathname}?${newSearchParams.toString()}`); diff --git a/apps/dashboard/src/components/shared/ConnectSDKCard.tsx b/apps/dashboard/src/components/shared/ConnectSDKCard.tsx deleted file mode 100644 index 49b2582ba3d..00000000000 --- a/apps/dashboard/src/components/shared/ConnectSDKCard.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { DotNetIcon } from "components/icons/brand-icons/DotNetIcon"; -import { ReactIcon } from "components/icons/brand-icons/ReactIcon"; -import { TypeScriptIcon } from "components/icons/brand-icons/TypeScriptIcon"; -import { UnityIcon } from "components/icons/brand-icons/UnityIcon"; -import { UnrealIcon } from "components/icons/brand-icons/UnrealIcon"; -import { DocLink } from "./DocLink"; - -export function ConnectSDKCard({ - title, - description, -}: { title?: string; description?: string }) { - return ( -
-

- {title || "Connect SDK"} -

-

- {description || "Add the Connect SDK to your app."} -

- -
- - - - - - -
- - -
- ); -} - -function BackgroundPattern() { - const color = "hsl(var(--foreground)/50%)"; - return ( -
- ); -}