Add Connect analytics page in Teams layout (#4384)
## Problem solved
Short description of the bug fixed or feature added
<!-- start pr-codex -->
---
## PR-Codex overview
This PR focuses on updating the analytics feature for team projects.
### Detailed summary
- Updated links and routes related to analytics feature
- Renamed functions for clarity
- Refactored components for analytics visualization
- Added new UI components for analytics dashboard
> The following files were skipped due to too many changes: `apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/analytics/_components/ConnectSDKCard.tsx`, `apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/analytics/ConnectAnalyticsDashboard.tsx`, `apps/dashboard/src/pages/dashboard/connect/analytics.tsx`
> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`
<!-- end pr-codex -->
This commit is contained in:
@@ -110,7 +110,7 @@ function ProjectCard(props: {
|
||||
<Link
|
||||
className="static group before:absolute before:top-0 before:bottom-0 before:left-0 before:right-0 before:z-0"
|
||||
// remove /connect when we have overview page
|
||||
href={`/team/${team_slug}/${project.slug}/connect/in-app-wallets`}
|
||||
href={`/team/${team_slug}/${project.slug}/connect/analytics`}
|
||||
>
|
||||
<h2 className="text-base font-medium">{project.name}</h2>
|
||||
</Link>
|
||||
|
||||
+14
-65
@@ -1,72 +1,21 @@
|
||||
import type { WalletStats } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { CableIcon, WalletCardsIcon } from "lucide-react";
|
||||
import type React from "react";
|
||||
import { useMemo } from "react";
|
||||
import { DailyConnectionsChartCard } from "./_components/DailyConnectionsChartCard";
|
||||
import { WalletConnectorsChartCard } from "./_components/WalletConnectorsChartCard";
|
||||
import { WalletDistributionChartCard } from "./_components/WalletDistributionChartCard";
|
||||
"use client";
|
||||
|
||||
import { useWalletStats } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { ConnectAnalyticsDashboardUI } from "./ConnectAnalyticsDashboardUI";
|
||||
|
||||
export function ConnectAnalyticsDashboard(props: {
|
||||
walletStats: WalletStats;
|
||||
isLoading: boolean;
|
||||
clientId: string;
|
||||
}) {
|
||||
const { totalWallets, uniqueWallets } = useMemo(() => {
|
||||
return props.walletStats.timeSeries.reduce(
|
||||
(acc, curr) => {
|
||||
acc.totalWallets += curr.totalWallets;
|
||||
acc.uniqueWallets += curr.uniqueWallets;
|
||||
return acc;
|
||||
},
|
||||
{ uniqueWallets: 0, totalWallets: 0 },
|
||||
);
|
||||
}, [props.walletStats]);
|
||||
const statsQuery = useWalletStats(props.clientId);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 lg:gap-6">
|
||||
{/* Summary Stat Cards */}
|
||||
<div className="grid grid-cols-2 gap-4 lg:gap-6">
|
||||
<Stat label="Connections" value={totalWallets} icon={CableIcon} />
|
||||
<Stat
|
||||
label="Unique Wallets"
|
||||
value={uniqueWallets}
|
||||
icon={WalletCardsIcon}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DailyConnectionsChartCard
|
||||
walletStats={props.walletStats}
|
||||
isLoading={props.isLoading}
|
||||
/>
|
||||
|
||||
<WalletConnectorsChartCard
|
||||
walletStats={props.walletStats}
|
||||
isLoading={props.isLoading}
|
||||
/>
|
||||
|
||||
<WalletDistributionChartCard
|
||||
walletStats={props.walletStats}
|
||||
isLoading={props.isLoading}
|
||||
/>
|
||||
</div>
|
||||
<ConnectAnalyticsDashboardUI
|
||||
walletStats={
|
||||
statsQuery.data || {
|
||||
timeSeries: [],
|
||||
}
|
||||
}
|
||||
isLoading={statsQuery.isLoading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const Stat: React.FC<{
|
||||
label: string;
|
||||
value?: number;
|
||||
icon: React.FC<{ className?: string }>;
|
||||
}> = ({ label, value, icon: Icon }) => {
|
||||
return (
|
||||
<dl className="bg-muted/50 rounded-lg border border-border p-4 lg:p-6 flex items-center gap-4 justify-between">
|
||||
<div>
|
||||
<dd className="text-3xl lg:text-5xl font-semibold tracking-tight">
|
||||
{value}
|
||||
</dd>
|
||||
<dt className="text-sm lg:text-lg font-medium text-muted-foreground tracking-tight">
|
||||
{label}
|
||||
</dt>
|
||||
</div>
|
||||
<Icon className="text-muted-foreground size-12 opacity-50 hidden lg:block" />
|
||||
</dl>
|
||||
);
|
||||
};
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
import type { WalletStats } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { CableIcon, WalletCardsIcon } from "lucide-react";
|
||||
import type React from "react";
|
||||
import { useMemo } from "react";
|
||||
import { DailyConnectionsChartCard } from "./_components/DailyConnectionsChartCard";
|
||||
import { WalletConnectorsChartCard } from "./_components/WalletConnectorsChartCard";
|
||||
import { WalletDistributionChartCard } from "./_components/WalletDistributionChartCard";
|
||||
|
||||
export function ConnectAnalyticsDashboardUI(props: {
|
||||
walletStats: WalletStats;
|
||||
isLoading: boolean;
|
||||
}) {
|
||||
const { totalWallets, uniqueWallets } = useMemo(() => {
|
||||
return props.walletStats.timeSeries.reduce(
|
||||
(acc, curr) => {
|
||||
acc.totalWallets += curr.totalWallets;
|
||||
acc.uniqueWallets += curr.uniqueWallets;
|
||||
return acc;
|
||||
},
|
||||
{ uniqueWallets: 0, totalWallets: 0 },
|
||||
);
|
||||
}, [props.walletStats]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 lg:gap-6">
|
||||
{/* Summary Stat Cards */}
|
||||
<div className="grid grid-cols-2 gap-4 lg:gap-6">
|
||||
<Stat label="Connections" value={totalWallets} icon={CableIcon} />
|
||||
<Stat
|
||||
label="Unique Wallets"
|
||||
value={uniqueWallets}
|
||||
icon={WalletCardsIcon}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DailyConnectionsChartCard
|
||||
walletStats={props.walletStats}
|
||||
isLoading={props.isLoading}
|
||||
/>
|
||||
|
||||
<WalletConnectorsChartCard
|
||||
walletStats={props.walletStats}
|
||||
isLoading={props.isLoading}
|
||||
/>
|
||||
|
||||
<WalletDistributionChartCard
|
||||
walletStats={props.walletStats}
|
||||
isLoading={props.isLoading}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Stat: React.FC<{
|
||||
label: string;
|
||||
value?: number;
|
||||
icon: React.FC<{ className?: string }>;
|
||||
}> = ({ label, value, icon: Icon }) => {
|
||||
return (
|
||||
<dl className="bg-muted/50 rounded-lg border border-border p-4 lg:p-6 flex items-center gap-4 justify-between">
|
||||
<div>
|
||||
<dd className="text-3xl lg:text-5xl font-semibold tracking-tight">
|
||||
{value}
|
||||
</dd>
|
||||
<dt className="text-sm lg:text-lg font-medium text-muted-foreground tracking-tight">
|
||||
{label}
|
||||
</dt>
|
||||
</div>
|
||||
<Icon className="text-muted-foreground size-12 opacity-50 hidden lg:block" />
|
||||
</dl>
|
||||
);
|
||||
};
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { mobileViewport } from "../../../../../../../stories/utils";
|
||||
import { ConnectAnalyticsDashboard } from "../ConnectAnalyticsDashboard";
|
||||
import { createWalletStatusStub } from "./storyUtils";
|
||||
import { ConnectAnalyticsDashboardUI } from "../ConnectAnalyticsDashboardUI";
|
||||
import { createWalletStatsStub } from "./storyUtils";
|
||||
|
||||
const meta = {
|
||||
title: "Charts/Connect/Analytics Dashboard",
|
||||
@@ -28,8 +28,8 @@ export const Mobile: Story = {
|
||||
function Component() {
|
||||
return (
|
||||
<div className="min-h-screen bg-background p-4 text-foreground gap-6 max-w-[1000px] flex flex-col mx-auto py-10">
|
||||
<ConnectAnalyticsDashboard
|
||||
walletStats={createWalletStatusStub(30)}
|
||||
<ConnectAnalyticsDashboardUI
|
||||
walletStats={createWalletStatsStub(30)}
|
||||
isLoading={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
import { SiReact } from "@react-icons/all-files/si/SiReact";
|
||||
import { SiTypescript } from "@react-icons/all-files/si/SiTypescript";
|
||||
import { SiUnity } from "@react-icons/all-files/si/SiUnity";
|
||||
import Link from "next/link";
|
||||
import { SiUnrealengine } from "react-icons/si";
|
||||
import { SiDotnet } from "react-icons/si";
|
||||
|
||||
export function ConnectSDKCard() {
|
||||
return (
|
||||
<div className="border border-border bg-muted/50 rounded-lg p-6 relative">
|
||||
<h3 className="text-2xl font-semibold tracking-tight mb-1">
|
||||
Connect SDK
|
||||
</h3>
|
||||
<p className="mb-8 text-muted-foreground text-sm">
|
||||
Add the Connect SDK to your app to start collecting analytics.
|
||||
</p>
|
||||
|
||||
<div className="grid gap-6 md:gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 max-w-[500px]">
|
||||
<DocLink
|
||||
link="https://portal.thirdweb.com/typescript/v5/getting-started"
|
||||
icon={SiTypescript}
|
||||
label="TypeScript SDK"
|
||||
/>
|
||||
<DocLink
|
||||
link="https://portal.thirdweb.com/react/v5/getting-started"
|
||||
icon={SiReact}
|
||||
label="React SDK"
|
||||
/>
|
||||
<DocLink
|
||||
link="https://portal.thirdweb.com/react-native/v5/getting-started"
|
||||
icon={SiReact}
|
||||
label="React Native SDK"
|
||||
/>
|
||||
<DocLink
|
||||
link="https://portal.thirdweb.com/unity/v4/getting-started"
|
||||
icon={SiUnity}
|
||||
label="Unity SDK"
|
||||
/>
|
||||
<DocLink
|
||||
link="https://portal.thirdweb.com/unreal/getting-started"
|
||||
icon={SiUnrealengine}
|
||||
label="Unreal SDK"
|
||||
/>
|
||||
<DocLink
|
||||
link="https://portal.thirdweb.com/dotnet/getting-started"
|
||||
icon={SiDotnet}
|
||||
label=".NET SDK"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<BackgroundPattern />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BackgroundPattern() {
|
||||
const color = "hsl(var(--foreground)/50%)";
|
||||
return (
|
||||
<div
|
||||
className="hidden xl:block absolute w-[50%] right-2 top-4 bottom-4 z-[1]"
|
||||
style={{
|
||||
backgroundImage: `radial-gradient(${color} 1px, transparent 1px)`,
|
||||
backgroundSize: "20px 20px",
|
||||
maskImage: "linear-gradient(to left, black, transparent)",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DocLink(props: {
|
||||
link: string;
|
||||
label: string;
|
||||
icon: React.FC<{ className?: string }>;
|
||||
}) {
|
||||
return (
|
||||
<Link
|
||||
href={props.link}
|
||||
target="_blank"
|
||||
className="text-muted-foreground hover:text-foreground flex items-center gap-2 text-sm"
|
||||
>
|
||||
<props.icon className="size-4" />
|
||||
{props.label}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
+6
-6
@@ -4,7 +4,7 @@ import {
|
||||
mobileViewport,
|
||||
} from "../../../../../../../stories/utils";
|
||||
import { DailyConnectionsChartCard } from "./DailyConnectionsChartCard";
|
||||
import { createWalletStatusStub } from "./storyUtils";
|
||||
import { createWalletStatsStub } from "./storyUtils";
|
||||
|
||||
const meta = {
|
||||
title: "Charts/Connect/DailyConnections",
|
||||
@@ -33,35 +33,35 @@ function Component() {
|
||||
<div className="min-h-screen bg-background p-4 text-foreground gap-6 max-w-[1000px] flex flex-col mx-auto">
|
||||
<BadgeContainer label="30 days">
|
||||
<DailyConnectionsChartCard
|
||||
walletStats={createWalletStatusStub(30)}
|
||||
walletStats={createWalletStatsStub(30)}
|
||||
isLoading={false}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="60 days">
|
||||
<DailyConnectionsChartCard
|
||||
walletStats={createWalletStatusStub(60)}
|
||||
walletStats={createWalletStatsStub(60)}
|
||||
isLoading={false}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="10 days">
|
||||
<DailyConnectionsChartCard
|
||||
walletStats={createWalletStatusStub(10)}
|
||||
walletStats={createWalletStatsStub(10)}
|
||||
isLoading={false}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="0 days">
|
||||
<DailyConnectionsChartCard
|
||||
walletStats={createWalletStatusStub(0)}
|
||||
walletStats={createWalletStatsStub(0)}
|
||||
isLoading={false}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="Loading">
|
||||
<DailyConnectionsChartCard
|
||||
walletStats={createWalletStatusStub(0)}
|
||||
walletStats={createWalletStatsStub(0)}
|
||||
isLoading={true}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
type ChartConfig,
|
||||
ChartContainer,
|
||||
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { Spinner } from "@/components/ui/Spinner/Spinner";
|
||||
import { type ChartConfig, ChartContainer } from "@/components/ui/chart";
|
||||
import { useMemo } from "react";
|
||||
|
||||
+6
-6
@@ -4,7 +4,7 @@ import {
|
||||
mobileViewport,
|
||||
} from "../../../../../../../stories/utils";
|
||||
import { WalletConnectorsChartCard } from "./WalletConnectorsChartCard";
|
||||
import { createWalletStatusStub } from "./storyUtils";
|
||||
import { createWalletStatsStub } from "./storyUtils";
|
||||
|
||||
const meta = {
|
||||
title: "Charts/Connect/Wallet Connectors",
|
||||
@@ -33,35 +33,35 @@ function Component() {
|
||||
<div className="min-h-screen bg-background p-4 text-foreground gap-6 max-w-[1000px] flex flex-col mx-auto">
|
||||
<BadgeContainer label="30 days">
|
||||
<WalletConnectorsChartCard
|
||||
walletStats={createWalletStatusStub(30)}
|
||||
walletStats={createWalletStatsStub(30)}
|
||||
isLoading={false}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="60 days">
|
||||
<WalletConnectorsChartCard
|
||||
walletStats={createWalletStatusStub(60)}
|
||||
walletStats={createWalletStatsStub(60)}
|
||||
isLoading={false}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="10 days">
|
||||
<WalletConnectorsChartCard
|
||||
walletStats={createWalletStatusStub(10)}
|
||||
walletStats={createWalletStatsStub(10)}
|
||||
isLoading={false}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="0 days">
|
||||
<WalletConnectorsChartCard
|
||||
walletStats={createWalletStatusStub(0)}
|
||||
walletStats={createWalletStatsStub(0)}
|
||||
isLoading={false}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="Loading">
|
||||
<WalletConnectorsChartCard
|
||||
walletStats={createWalletStatusStub(0)}
|
||||
walletStats={createWalletStatsStub(0)}
|
||||
isLoading={true}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
+6
-6
@@ -4,7 +4,7 @@ import {
|
||||
mobileViewport,
|
||||
} from "../../../../../../../stories/utils";
|
||||
import { WalletDistributionChartCard } from "./WalletDistributionChartCard";
|
||||
import { createWalletStatusStub } from "./storyUtils";
|
||||
import { createWalletStatsStub } from "./storyUtils";
|
||||
|
||||
const meta = {
|
||||
title: "Charts/Connect/Wallet Distribution",
|
||||
@@ -33,35 +33,35 @@ function Component() {
|
||||
<div className="min-h-screen bg-background p-4 text-foreground gap-6 max-w-[1000px] flex flex-col mx-auto">
|
||||
<BadgeContainer label="30 days">
|
||||
<WalletDistributionChartCard
|
||||
walletStats={createWalletStatusStub(30)}
|
||||
walletStats={createWalletStatsStub(30)}
|
||||
isLoading={false}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="60 days">
|
||||
<WalletDistributionChartCard
|
||||
walletStats={createWalletStatusStub(60)}
|
||||
walletStats={createWalletStatsStub(60)}
|
||||
isLoading={false}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="10 days">
|
||||
<WalletDistributionChartCard
|
||||
walletStats={createWalletStatusStub(10)}
|
||||
walletStats={createWalletStatsStub(10)}
|
||||
isLoading={false}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="0 days">
|
||||
<WalletDistributionChartCard
|
||||
walletStats={createWalletStatusStub(0)}
|
||||
walletStats={createWalletStatsStub(0)}
|
||||
isLoading={false}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="Loading">
|
||||
<WalletDistributionChartCard
|
||||
walletStats={createWalletStatusStub(0)}
|
||||
walletStats={createWalletStatsStub(0)}
|
||||
isLoading={true}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ const pickRandomWallet = () => {
|
||||
];
|
||||
};
|
||||
|
||||
export function createWalletStatusStub(days: number): WalletStats {
|
||||
export function createWalletStatsStub(days: number): WalletStats {
|
||||
const timeSeries: WalletStats["timeSeries"] = [];
|
||||
|
||||
let d = days;
|
||||
|
||||
@@ -1,9 +1,38 @@
|
||||
export default function Page() {
|
||||
import { getProject } from "@/api/projects";
|
||||
import { notFound } from "next/navigation";
|
||||
import { ConnectAnalyticsDashboard } from "./ConnectAnalyticsDashboard";
|
||||
import { ConnectSDKCard } from "./_components/ConnectSDKCard";
|
||||
|
||||
export default async function Page(props: {
|
||||
params: {
|
||||
team_slug: string;
|
||||
project_slug: string;
|
||||
};
|
||||
}) {
|
||||
const project = await getProject(
|
||||
props.params.team_slug,
|
||||
props.params.project_slug,
|
||||
);
|
||||
|
||||
if (!project) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full py-6 container flex items-center justify-center">
|
||||
<h1 className="text-4xl tracking-tighter text-muted-foreground">
|
||||
Analytics
|
||||
</h1>
|
||||
<div className="pb-10 max-sm:pt-6">
|
||||
<div>
|
||||
<h1 className="text-2xl md:text-3xl font-semibold tracking-tight mb-1">
|
||||
Connect Analytics
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm md:text-base">
|
||||
Visualize how your users are connecting to your app
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="h-6 lg:h-8" />
|
||||
<ConnectAnalyticsDashboard clientId={project.publishableKey} />
|
||||
<div className="h-4 lg:h-8" />
|
||||
<ConnectSDKCard />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,14 +11,14 @@ export default function Layout(props: {
|
||||
const { team_slug, project_slug } = props.params;
|
||||
|
||||
const links: { label: string; href: string }[] = [
|
||||
{
|
||||
label: "In-App Wallets",
|
||||
href: `/team/${team_slug}/${project_slug}/connect/in-app-wallets`,
|
||||
},
|
||||
{
|
||||
label: "Analytics",
|
||||
href: `/team/${team_slug}/${project_slug}/connect/analytics`,
|
||||
},
|
||||
{
|
||||
label: "In-App Wallets",
|
||||
href: `/team/${team_slug}/${project_slug}/connect/in-app-wallets`,
|
||||
},
|
||||
{
|
||||
label: "Ecosystem Wallets",
|
||||
href: `/team/${team_slug}/${project_slug}/connect/ecosystem`,
|
||||
|
||||
@@ -7,5 +7,5 @@ export default function Page(props: {
|
||||
};
|
||||
}) {
|
||||
const { team_slug, project_slug } = props.params;
|
||||
redirect(`/team/${team_slug}/${project_slug}/connect/in-app-wallets`);
|
||||
redirect(`/team/${team_slug}/${project_slug}/connect/analytics`);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export default async function TeamLayout(props: {
|
||||
<TeamTabs
|
||||
links={[
|
||||
{
|
||||
href: `/team/${props.params.team_slug}/${props.params.project_slug}/connect`,
|
||||
href: `/team/${props.params.team_slug}/${props.params.project_slug}/connect/analytics`,
|
||||
name: "Connect",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
import { useAccount } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { Box, Flex, HStack, Icon, useColorModeValue } from "@chakra-ui/react";
|
||||
import Image from "next/image";
|
||||
import type { ReactNode } from "react";
|
||||
import { FiLock } from "react-icons/fi";
|
||||
import { Card, Heading, Text, TrackedLinkButton } from "tw-components";
|
||||
|
||||
interface GatedFeatureProps {
|
||||
children: ReactNode;
|
||||
title?: string;
|
||||
description?: ReactNode;
|
||||
imgSrc?: string;
|
||||
imgWidth?: number;
|
||||
imgHeight?: number;
|
||||
trackingLabel?: string;
|
||||
}
|
||||
|
||||
export const GatedFeature: React.FC<GatedFeatureProps> = ({
|
||||
children,
|
||||
title,
|
||||
description,
|
||||
imgSrc,
|
||||
imgWidth,
|
||||
imgHeight,
|
||||
trackingLabel,
|
||||
}) => {
|
||||
const meQuery = useAccount();
|
||||
const { data: account } = meQuery;
|
||||
|
||||
const bg = useColorModeValue("backgroundCardHighlight", "transparent");
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (account.advancedEnabled) {
|
||||
return children;
|
||||
}
|
||||
|
||||
if (!title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
pt={{ base: 4, lg: 8 }}
|
||||
pb={{ base: 8, lg: 8 }}
|
||||
px={{ base: 4, lg: 8, xl: 16 }}
|
||||
bg={bg}
|
||||
>
|
||||
<Flex
|
||||
flexDir={{ base: "column-reverse", lg: "row" }}
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
gap={8}
|
||||
>
|
||||
<Flex
|
||||
flexDir="column"
|
||||
gap={6}
|
||||
alignItems={{ base: "center", lg: "flex-start" }}
|
||||
>
|
||||
<Flex flexDir="column" gap={2}>
|
||||
<HStack
|
||||
gap={2}
|
||||
alignItems="center"
|
||||
justifyContent={{ base: "center", lg: "flex-start" }}
|
||||
>
|
||||
<Icon as={FiLock} opacity={0.3} boxSize={4} />
|
||||
<Text size="label.md" opacity={0.5}>
|
||||
Advanced feature
|
||||
</Text>
|
||||
</HStack>
|
||||
<Heading size="title.md" textAlign={{ base: "center", lg: "left" }}>
|
||||
{title}
|
||||
</Heading>
|
||||
</Flex>
|
||||
|
||||
<Text size="body.lg" textAlign={{ base: "center", lg: "left" }}>
|
||||
{description}
|
||||
</Text>
|
||||
|
||||
<Box>
|
||||
<TrackedLinkButton
|
||||
colorScheme="primary"
|
||||
category="advancedFeature"
|
||||
label={trackingLabel}
|
||||
href={"/dashboard/settings/billing"}
|
||||
borderRadius="lg"
|
||||
py={6}
|
||||
px={6}
|
||||
>
|
||||
Upgrade to Growth
|
||||
</TrackedLinkButton>
|
||||
</Box>
|
||||
</Flex>
|
||||
{imgSrc && (
|
||||
<Box overflow="hidden">
|
||||
<Image
|
||||
src={imgSrc}
|
||||
width={imgWidth}
|
||||
height={imgHeight}
|
||||
alt={trackingLabel ?? ""}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -1,13 +1,6 @@
|
||||
import { Spinner } from "@/components/ui/Spinner/Spinner";
|
||||
import {
|
||||
type ApiKey,
|
||||
useApiKeys,
|
||||
useWalletStats,
|
||||
} from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { type ApiKey, useApiKeys } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { useLoggedInUser } from "@3rdweb-sdk/react/hooks/useLoggedInUser";
|
||||
import { SiReact } from "@react-icons/all-files/si/SiReact";
|
||||
import { SiTypescript } from "@react-icons/all-files/si/SiTypescript";
|
||||
import { SiUnity } from "@react-icons/all-files/si/SiUnity";
|
||||
import { AppLayout } from "components/app-layouts/app";
|
||||
import { ApiKeysMenu } from "components/settings/ApiKeys/Menu";
|
||||
import { ConnectSidebar } from "core-ui/sidebar/connect";
|
||||
@@ -15,11 +8,10 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { PageId } from "page-id";
|
||||
import { useMemo, useState } from "react";
|
||||
import { SiUnrealengine } from "react-icons/si";
|
||||
import { SiDotnet } from "react-icons/si";
|
||||
import type { ThirdwebNextPage } from "utils/types";
|
||||
import { Button } from "../../../@/components/ui/button";
|
||||
import { ConnectAnalyticsDashboard } from "../../../app/team/[team_slug]/[project_slug]/connect/analytics/ConnectAnalyticsDashboard";
|
||||
import { ConnectSDKCard } from "../../../app/team/[team_slug]/[project_slug]/connect/analytics/_components/ConnectSDKCard";
|
||||
|
||||
const DashboardConnectAnalytics: ThirdwebNextPage = () => {
|
||||
const router = useRouter();
|
||||
@@ -46,7 +38,6 @@ const DashboardConnectAnalytics: ThirdwebNextPage = () => {
|
||||
return undefined;
|
||||
}, [apiKeys, defaultClientId, selectedKey_]);
|
||||
|
||||
const statsQuery = useWalletStats(selectedKey?.key);
|
||||
const showLoader = loggedInUser.isLoading || keysQuery.isLoading;
|
||||
|
||||
return (
|
||||
@@ -82,14 +73,7 @@ const DashboardConnectAnalytics: ThirdwebNextPage = () => {
|
||||
) : (
|
||||
<>
|
||||
{selectedKey ? (
|
||||
<ConnectAnalyticsDashboard
|
||||
walletStats={
|
||||
statsQuery.data || {
|
||||
timeSeries: [],
|
||||
}
|
||||
}
|
||||
isLoading={statsQuery.isLoading}
|
||||
/>
|
||||
<ConnectAnalyticsDashboard clientId={selectedKey.key} />
|
||||
) : (
|
||||
<NoAPIFoundCard />
|
||||
)}
|
||||
@@ -97,7 +81,7 @@ const DashboardConnectAnalytics: ThirdwebNextPage = () => {
|
||||
)}
|
||||
|
||||
<div className="h-4 lg:h-8" />
|
||||
<SDKCtaSection />
|
||||
<ConnectSDKCard />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -116,85 +100,6 @@ function NoAPIFoundCard() {
|
||||
);
|
||||
}
|
||||
|
||||
function SDKCtaSection() {
|
||||
return (
|
||||
<div className="border border-border bg-muted/50 rounded-lg p-6 relative">
|
||||
<h3 className="text-2xl font-semibold tracking-tight mb-1">
|
||||
Connect SDK
|
||||
</h3>
|
||||
<p className="mb-8 text-muted-foreground text-sm">
|
||||
Add the Connect SDK to your app to start collecting analytics.
|
||||
</p>
|
||||
|
||||
<div className="grid gap-6 md:gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 max-w-[500px]">
|
||||
<DocLink
|
||||
link="https://portal.thirdweb.com/typescript/v5/getting-started"
|
||||
icon={SiTypescript}
|
||||
label="TypeScript SDK"
|
||||
/>
|
||||
<DocLink
|
||||
link="https://portal.thirdweb.com/react/v5/getting-started"
|
||||
icon={SiReact}
|
||||
label="React SDK"
|
||||
/>
|
||||
<DocLink
|
||||
link="https://portal.thirdweb.com/react-native/v5/getting-started"
|
||||
icon={SiReact}
|
||||
label="React Native SDK"
|
||||
/>
|
||||
<DocLink
|
||||
link="https://portal.thirdweb.com/unity/v4/getting-started"
|
||||
icon={SiUnity}
|
||||
label="Unity SDK"
|
||||
/>
|
||||
<DocLink
|
||||
link="https://portal.thirdweb.com/unreal/getting-started"
|
||||
icon={SiUnrealengine}
|
||||
label="Unreal SDK"
|
||||
/>
|
||||
<DocLink
|
||||
link="https://portal.thirdweb.com/dotnet/getting-started"
|
||||
icon={SiDotnet}
|
||||
label=".NET SDK"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<BackgroundPattern />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BackgroundPattern() {
|
||||
const color = "hsl(var(--foreground)/50%)";
|
||||
return (
|
||||
<div
|
||||
className="hidden xl:block absolute w-[50%] right-2 top-4 bottom-4 z-[1]"
|
||||
style={{
|
||||
backgroundImage: `radial-gradient(${color} 1px, transparent 1px)`,
|
||||
backgroundSize: "20px 20px",
|
||||
maskImage: "linear-gradient(to left, black, transparent)",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DocLink(props: {
|
||||
link: string;
|
||||
label: string;
|
||||
icon: React.FC<{ className?: string }>;
|
||||
}) {
|
||||
return (
|
||||
<Link
|
||||
href={props.link}
|
||||
target="_blank"
|
||||
className="text-muted-foreground hover:text-foreground flex items-center gap-2 text-sm"
|
||||
>
|
||||
<props.icon className="size-4" />
|
||||
{props.label}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
DashboardConnectAnalytics.getLayout = (page, props) => (
|
||||
<AppLayout {...props} hasSidebar={true}>
|
||||
<ConnectSidebar activePage="analytics" />
|
||||
|
||||
Reference in New Issue
Block a user