diff --git a/apps/dashboard/src/@/api/analytics.ts b/apps/dashboard/src/@/api/analytics.ts
index a6f2819e1c6..39a89144148 100644
--- a/apps/dashboard/src/@/api/analytics.ts
+++ b/apps/dashboard/src/@/api/analytics.ts
@@ -17,6 +17,29 @@ import type {
import { getAuthToken } from "./auth-token";
import { getChains } from "./chain";
+export interface InsightChainStats {
+ date: string;
+ chainId: string;
+ totalRequests: number;
+}
+
+export interface InsightStatusCodeStats {
+ date: string;
+ httpStatusCode: number;
+ totalRequests: number;
+}
+
+export interface InsightEndpointStats {
+ date: string;
+ endpoint: string;
+ totalRequests: number;
+}
+
+interface InsightUsageStats {
+ date: string;
+ totalRequests: number;
+}
+
async function fetchAnalytics(
input: string | URL,
init?: RequestInit,
@@ -76,6 +99,9 @@ function buildSearchParams(params: AnalyticsQueryParams): URLSearchParams {
if (params.period) {
searchParams.append("period", params.period);
}
+ if (params.limit) {
+ searchParams.append("limit", params.limit.toString());
+ }
return searchParams;
}
@@ -424,3 +450,91 @@ export async function getEngineCloudMethodUsage(
const json = await res.json();
return json.data as EngineCloudStats[];
}
+
+export async function getInsightChainUsage(
+ params: AnalyticsQueryParams,
+): Promise<{ data: InsightChainStats[] } | { errorMessage: string }> {
+ const searchParams = buildSearchParams(params);
+ const res = await fetchAnalytics(
+ `v2/insight/usage/by-chain?${searchParams.toString()}`,
+ {
+ method: "GET",
+ },
+ );
+
+ if (res?.status !== 200) {
+ const reason = await res?.text();
+ const errMsg = `Failed to fetch Insight chain usage: ${res?.status} - ${res.statusText} - ${reason}`;
+ console.error(errMsg);
+ return { errorMessage: errMsg };
+ }
+
+ const json = await res.json();
+ return { data: json.data as InsightChainStats[] };
+}
+
+export async function getInsightStatusCodeUsage(
+ params: AnalyticsQueryParams,
+): Promise<{ data: InsightStatusCodeStats[] } | { errorMessage: string }> {
+ const searchParams = buildSearchParams(params);
+ const res = await fetchAnalytics(
+ `v2/insight/usage/by-status-code?${searchParams.toString()}`,
+ {
+ method: "GET",
+ },
+ );
+
+ if (res?.status !== 200) {
+ const reason = await res?.text();
+ const errMsg = `Failed to fetch Insight status code usage: ${res?.status} - ${res.statusText} - ${reason}`;
+ console.error(errMsg);
+ return { errorMessage: errMsg };
+ }
+
+ const json = await res.json();
+ return { data: json.data as InsightStatusCodeStats[] };
+}
+
+export async function getInsightEndpointUsage(
+ params: AnalyticsQueryParams,
+): Promise<{ data: InsightEndpointStats[] } | { errorMessage: string }> {
+ const searchParams = buildSearchParams(params);
+ const res = await fetchAnalytics(
+ `v2/insight/usage/by-endpoint?${searchParams.toString()}`,
+ {
+ method: "GET",
+ },
+ );
+
+ if (res?.status !== 200) {
+ const reason = await res?.text();
+ const errMsg = `Failed to fetch Insight endpoint usage: ${res?.status} - ${res.statusText} - ${reason}`;
+ console.error(errMsg);
+ return { errorMessage: errMsg };
+ }
+
+ const json = await res.json();
+ return { data: json.data as InsightEndpointStats[] };
+}
+
+export async function getInsightUsage(
+ params: AnalyticsQueryParams,
+): Promise<{ data: InsightUsageStats[] } | { errorMessage: string }> {
+ const searchParams = buildSearchParams(params);
+ const res = await fetchAnalytics(
+ `v2/insight/usage?${searchParams.toString()}`,
+ {
+ method: "GET",
+ },
+ );
+
+ if (res?.status !== 200) {
+ const reason = await res?.text();
+ const errMsg = `Failed to fetch Insight usage: ${res?.status} - ${res.statusText} - ${reason}`;
+ console.error(errMsg);
+ return { errorMessage: errMsg };
+ }
+
+ const json = await res.json();
+ return { data: json.data as InsightUsageStats[] };
+}
diff --git a/apps/dashboard/src/@/types/analytics.ts b/apps/dashboard/src/@/types/analytics.ts
index 895da7b95df..d294b537cde 100644
--- a/apps/dashboard/src/@/types/analytics.ts
+++ b/apps/dashboard/src/@/types/analytics.ts
@@ -78,4 +78,5 @@ export interface AnalyticsQueryParams {
from?: Date;
to?: Date;
period?: "day" | "week" | "month" | "year" | "all";
+ limit?: number;
}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/blueprint-card.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/blueprint-card.tsx
deleted file mode 100644
index 79d750da614..00000000000
--- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/blueprint-card.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import {
- Code2Icon,
- DatabaseIcon,
- ExternalLinkIcon,
- ZapIcon,
-} from "lucide-react";
-import Link from "next/link";
-import { Button } from "@/components/ui/button";
-
-export function BlueprintCard() {
- const features = [
- {
- description: "RESTful endpoints for any application",
- icon: Code2Icon,
- title: "Easy-to-Use API",
- },
- {
- description:
- "No need to index blockchains yourself or manage infrastructure and RPC costs.",
- icon: DatabaseIcon,
- title: "Managed Infrastructure",
- },
- {
- description: "Access any transaction, event or token API data",
- icon: ZapIcon,
- title: "Lightning-Fast Queries",
- },
- ];
-
- return (
-
- {/* header */}
-
-
-
Blueprints
-
-
-
-
- Docs
-
-
-
-
-
-
- {/* Content */}
-
-
- Simple endpoints for querying rich blockchain data
-
-
- A blueprint is an API that provides access to on-chain data in a
- user-friendly format. No need for ABIs, decoding, RPC, or web3
- knowledge required to fetch blockchain data.
-
-
-
-
- {/* Features */}
-
- {features.map((feature) => (
-
-
-
-
-
-
{feature.title}
-
- {feature.description}
-
-
-
- ))}
-
-
-
- {/* Playground link */}
-
-
-
- Try Insight blueprints in the playground
-
-
-
-
-
- );
-}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/InsightAnalytics.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/InsightAnalytics.tsx
new file mode 100644
index 00000000000..40f2b0b35cb
--- /dev/null
+++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/InsightAnalytics.tsx
@@ -0,0 +1,223 @@
+import "server-only";
+
+import { ActivityIcon, AlertCircleIcon, CloudAlertIcon } from "lucide-react";
+import { ResponsiveSuspense } from "responsive-rsc";
+import type { ThirdwebClient } from "thirdweb";
+import {
+ getInsightChainUsage,
+ getInsightEndpointUsage,
+ getInsightStatusCodeUsage,
+ getInsightUsage,
+} from "@/api/analytics";
+import type { Range } from "@/components/analytics/date-range-selector";
+import { StatCard } from "@/components/analytics/stat";
+import { Card, CardContent } from "@/components/ui/card";
+import { Skeleton } from "@/components/ui/skeleton";
+import { InsightAnalyticsFilter } from "./InsightAnalyticsFilter";
+import { InsightFTUX } from "./insight-ftux";
+import { RequestsByStatusGraph } from "./RequestsByStatusGraph";
+import { TopInsightChainsTable } from "./TopChainsTable";
+import { TopInsightEndpointsTable } from "./TopEndpointsTable";
+
+// Error state component for analytics
+function AnalyticsErrorState({
+ title,
+ message,
+ className,
+}: {
+ title: string;
+ message: string;
+ className?: string;
+}) {
+ return (
+
+
+
+
+
+
+ );
+}
+
+export async function InsightAnalytics(props: {
+ projectClientId: string;
+ client: ThirdwebClient;
+ projectId: string;
+ teamId: string;
+ range: Range;
+ interval: "day" | "week";
+}) {
+ const { projectId, teamId, range, interval } = props;
+
+ const allTimeRequestsPromise = getInsightUsage({
+ from: range.from,
+ period: "all",
+ projectId: projectId,
+ teamId: teamId,
+ to: range.to,
+ });
+ const chainsDataPromise = getInsightChainUsage({
+ from: range.from,
+ limit: 10,
+ period: "all",
+ projectId: projectId,
+ teamId: teamId,
+ to: range.to,
+ });
+ const statusCodesDataPromise = getInsightStatusCodeUsage({
+ from: range.from,
+ period: interval,
+ projectId: projectId,
+ teamId: teamId,
+ to: range.to,
+ });
+ const endpointsDataPromise = getInsightEndpointUsage({
+ from: range.from,
+ limit: 10,
+ period: "all",
+ projectId: projectId,
+ teamId: teamId,
+ to: range.to,
+ });
+
+ const [allTimeRequestsData, statusCodesData, endpointsData, chainsData] =
+ await Promise.all([
+ allTimeRequestsPromise,
+ statusCodesDataPromise,
+ endpointsDataPromise,
+ chainsDataPromise,
+ ]);
+
+ const hasVolume =
+ "data" in allTimeRequestsData &&
+ allTimeRequestsData.data?.some((d) => d.totalRequests > 0);
+
+ const allTimeRequests =
+ "data" in allTimeRequestsData
+ ? allTimeRequestsData.data?.reduce(
+ (acc, curr) => acc + curr.totalRequests,
+ 0,
+ )
+ : 0;
+
+ let requestsInPeriod = 0;
+ let errorsInPeriod = 0;
+ if ("data" in statusCodesData) {
+ for (const request of statusCodesData.data) {
+ requestsInPeriod += request.totalRequests;
+ if (request.httpStatusCode >= 400) {
+ errorsInPeriod += request.totalRequests;
+ }
+ }
+ }
+ const errorRate = Number(
+ ((errorsInPeriod / (requestsInPeriod || 1)) * 100).toFixed(2),
+ );
+
+ if (!hasVolume) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ }
+ searchParamsUsed={["from", "to", "interval"]}
+ >
+
+
+
+ `${value}%`}
+ icon={CloudAlertIcon}
+ isPending={false}
+ label="Error rate"
+ value={errorRate}
+ />
+
+
+ {"errorMessage" in statusCodesData ? (
+
+ ) : (
+
+ )}
+
+
+
+ {"errorMessage" in endpointsData ? (
+
+ ) : (
+
+ )}
+
+ {"errorMessage" in chainsData ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+}
+
+function GridWithSeparator(props: { children: React.ReactNode }) {
+ return (
+
+ {props.children}
+ {/* Desktop - horizontal middle */}
+
+
+ );
+}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/InsightAnalyticsFilter.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/InsightAnalyticsFilter.tsx
new file mode 100644
index 00000000000..f62e5d43f90
--- /dev/null
+++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/InsightAnalyticsFilter.tsx
@@ -0,0 +1,59 @@
+"use client";
+
+import {
+ useResponsiveSearchParams,
+ useSetResponsiveSearchParams,
+} from "responsive-rsc";
+import { DateRangeSelector } from "@/components/analytics/date-range-selector";
+import { IntervalSelector } from "@/components/analytics/interval-selector";
+import { getFiltersFromSearchParams, normalizeTimeISOString } from "@/lib/time";
+
+type SearchParams = {
+ from?: string;
+ to?: string;
+ interval?: "day" | "week";
+};
+
+export function InsightAnalyticsFilter() {
+ const responsiveSearchParams = useResponsiveSearchParams();
+ const setResponsiveSearchParams = useSetResponsiveSearchParams();
+
+ const { range, interval } = getFiltersFromSearchParams({
+ defaultRange: "last-30",
+ from: responsiveSearchParams.from,
+ interval: responsiveSearchParams.interval,
+ to: responsiveSearchParams.to,
+ });
+
+ return (
+
+ {
+ setResponsiveSearchParams((v: SearchParams) => {
+ const newParams = {
+ ...v,
+ from: normalizeTimeISOString(newRange.from),
+ to: normalizeTimeISOString(newRange.to),
+ };
+ return newParams;
+ });
+ }}
+ />
+
+ {
+ setResponsiveSearchParams((v: SearchParams) => {
+ const newParams = {
+ ...v,
+ interval: newInterval,
+ };
+ return newParams;
+ });
+ }}
+ />
+
+ );
+}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/RequestsByStatusGraph.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/RequestsByStatusGraph.tsx
new file mode 100644
index 00000000000..1418b07043a
--- /dev/null
+++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/RequestsByStatusGraph.tsx
@@ -0,0 +1,121 @@
+"use client";
+import { format } from "date-fns";
+import { useMemo } from "react";
+import { shortenLargeNumber } from "thirdweb/utils";
+import type { InsightStatusCodeStats } from "@/api/analytics";
+import { EmptyChartState } from "@/components/analytics/empty-chart-state";
+import { ThirdwebBarChart } from "@/components/blocks/charts/bar-chart";
+import type { ChartConfig } from "@/components/ui/chart";
+
+type ChartData = Record & {
+ time: string; // human readable date
+};
+const defaultLabel = 200;
+
+export function RequestsByStatusGraph(props: {
+ data: InsightStatusCodeStats[];
+ isPending: boolean;
+ title: string;
+ description: string;
+}) {
+ const topStatusCodesToShow = 10;
+
+ const { chartConfig, chartData } = useMemo(() => {
+ const _chartConfig: ChartConfig = {};
+ const _chartDataMap: Map = new Map();
+ const statusCodeToVolumeMap: Map = new Map();
+ // for each stat, add it in _chartDataMap
+ for (const stat of props.data) {
+ const chartData = _chartDataMap.get(stat.date);
+ const { httpStatusCode } = stat;
+
+ // if no data for current day - create new entry
+ if (!chartData && stat.totalRequests > 0) {
+ _chartDataMap.set(stat.date, {
+ time: stat.date,
+ [httpStatusCode || defaultLabel]: stat.totalRequests,
+ } as ChartData);
+ } else if (chartData) {
+ chartData[httpStatusCode || defaultLabel] =
+ (chartData[httpStatusCode || defaultLabel] || 0) + stat.totalRequests;
+ }
+
+ statusCodeToVolumeMap.set(
+ (httpStatusCode || defaultLabel).toString(),
+ stat.totalRequests +
+ (statusCodeToVolumeMap.get(
+ (httpStatusCode || defaultLabel).toString(),
+ ) || 0),
+ );
+ }
+
+ const statusCodesSorted = Array.from(statusCodeToVolumeMap.entries())
+ .sort((a, b) => b[1] - a[1])
+ .map((w) => w[0]);
+
+ const statusCodesToShow = statusCodesSorted.slice(0, topStatusCodesToShow);
+ const statusCodesAsOther = statusCodesSorted.slice(topStatusCodesToShow);
+
+ // replace chainIdsToTagAsOther chainId with "other"
+ for (const data of _chartDataMap.values()) {
+ for (const statusCode in data) {
+ if (statusCodesAsOther.includes(statusCode)) {
+ data.others = (data.others || 0) + (data[statusCode] || 0);
+ delete data[statusCode];
+ }
+ }
+ }
+
+ statusCodesToShow.forEach((statusCode, i) => {
+ _chartConfig[statusCode] = {
+ color: `hsl(var(--chart-${(i % 10) + 1}))`,
+ label: statusCodesToShow[i],
+ };
+ });
+
+ if (statusCodesAsOther.length > 0) {
+ _chartConfig.others = {
+ color: "hsl(var(--muted-foreground))",
+ label: "Others",
+ };
+ }
+
+ return {
+ chartConfig: _chartConfig,
+ chartData: Array.from(_chartDataMap.values()).sort(
+ (a, b) => new Date(a.time).getTime() - new Date(b.time).getTime(),
+ ),
+ };
+ }, [props.data]);
+
+ return (
+
+
+ {props.title}
+
+
+ {props.description}
+
+
+ }
+ data={chartData}
+ emptyChartState={ }
+ hideLabel={false}
+ isPending={props.isPending}
+ showLegend
+ toolTipLabelFormatter={(_v, item) => {
+ if (Array.isArray(item)) {
+ const time = item[0].payload.time as number;
+ return format(new Date(time), "MMM d, yyyy");
+ }
+ return undefined;
+ }}
+ toolTipValueFormatter={(v) => shortenLargeNumber(v as number)}
+ variant="stacked"
+ />
+ );
+}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/TopChainsTable.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/TopChainsTable.tsx
new file mode 100644
index 00000000000..79c661299ae
--- /dev/null
+++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/TopChainsTable.tsx
@@ -0,0 +1,106 @@
+"use client";
+import { useMemo } from "react";
+import type { ThirdwebClient } from "thirdweb";
+import { shortenLargeNumber } from "thirdweb/utils";
+import type { InsightChainStats } from "@/api/analytics";
+import { SkeletonContainer } from "@/components/ui/skeleton";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { CardHeading } from "../../universal-bridge/components/common";
+
+export function TopInsightChainsTable(props: {
+ data: InsightChainStats[];
+ client: ThirdwebClient;
+}) {
+ const tableData = useMemo(() => {
+ return props.data.sort((a, b) => b.totalRequests - a.totalRequests);
+ }, [props.data]);
+ const isEmpty = useMemo(() => tableData.length === 0, [tableData]);
+
+ return (
+
+ {/* header */}
+
+ Top Chains
+
+
+
+
+
+
+
+ Chain ID
+ Requests
+
+
+
+ {tableData.map((chain, i) => {
+ return (
+
+ );
+ })}
+
+
+ {isEmpty && (
+
+ No data available
+
+ )}
+
+
+ );
+}
+
+function ChainTableRow(props: {
+ chain?: {
+ chainId: string;
+ totalRequests: number;
+ };
+ client: ThirdwebClient;
+ rowIndex: number;
+}) {
+ const delayAnim = {
+ animationDelay: `${props.rowIndex * 100}ms`,
+ };
+
+ return (
+
+
+ (
+
+ {v === "0" ? "Multichain" : v}
+
+ )}
+ skeletonData="..."
+ style={delayAnim}
+ />
+
+
+ {
+ return {shortenLargeNumber(v)}
;
+ }}
+ skeletonData={0}
+ style={delayAnim}
+ />
+
+
+ );
+}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/TopEndpointsTable.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/TopEndpointsTable.tsx
new file mode 100644
index 00000000000..1ee0853f08b
--- /dev/null
+++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/TopEndpointsTable.tsx
@@ -0,0 +1,106 @@
+"use client";
+import { useMemo } from "react";
+import type { ThirdwebClient } from "thirdweb";
+import { shortenLargeNumber } from "thirdweb/utils";
+import type { InsightEndpointStats } from "@/api/analytics";
+import { SkeletonContainer } from "@/components/ui/skeleton";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { CardHeading } from "../../universal-bridge/components/common";
+
+export function TopInsightEndpointsTable(props: {
+ data: InsightEndpointStats[];
+ client: ThirdwebClient;
+}) {
+ const tableData = useMemo(() => {
+ return props.data?.sort((a, b) => b.totalRequests - a.totalRequests);
+ }, [props.data]);
+ const isEmpty = useMemo(() => tableData.length === 0, [tableData]);
+
+ return (
+
+ {/* header */}
+
+ Top Endpoints
+
+
+
+
+
+
+
+ Endpoint
+ Requests
+
+
+
+ {tableData.map((endpoint, i) => {
+ return (
+
+ );
+ })}
+
+
+ {isEmpty && (
+
+ No data available
+
+ )}
+
+
+ );
+}
+
+function EndpointTableRow(props: {
+ endpoint?: {
+ endpoint: string;
+ totalRequests: number;
+ };
+ client: ThirdwebClient;
+ rowIndex: number;
+}) {
+ const delayAnim = {
+ animationDelay: `${props.rowIndex * 100}ms`,
+ };
+
+ return (
+
+
+ (
+
+ {v}
+
+ )}
+ skeletonData="..."
+ style={delayAnim}
+ />
+
+
+ {
+ return {shortenLargeNumber(v)}
;
+ }}
+ skeletonData={0}
+ style={delayAnim}
+ />
+
+
+ );
+}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/insight-ftux.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/insight-ftux.tsx
similarity index 91%
rename from apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/insight-ftux.tsx
rename to apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/insight-ftux.tsx
index 12fc1cd34ce..22caabe269b 100644
--- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/insight-ftux.tsx
+++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/insight-ftux.tsx
@@ -1,7 +1,7 @@
import { CodeServer } from "@/components/ui/code/code.server";
import { isProd } from "@/constants/env-utils";
-import { ClientIDSection } from "../components/ProjectFTUX/ClientIDSection";
-import { WaitingForIntegrationCard } from "../components/WaitingForIntegrationCard/WaitingForIntegrationCard";
+import { ClientIDSection } from "../../components/ProjectFTUX/ClientIDSection";
+import { WaitingForIntegrationCard } from "../../components/WaitingForIntegrationCard/WaitingForIntegrationCard";
export function InsightFTUX(props: { clientId: string }) {
return (
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/layout.tsx
new file mode 100644
index 00000000000..c6bbdd86800
--- /dev/null
+++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/layout.tsx
@@ -0,0 +1,99 @@
+import { redirect } from "next/navigation";
+import { getProject } from "@/api/projects";
+import { UnderlineLink } from "@/components/ui/UnderlineLink";
+import { FooterLinksSection } from "../components/footer/FooterLinksSection";
+
+export default async function Layout(props: {
+ params: Promise<{
+ team_slug: string;
+ project_slug: string;
+ }>;
+ children: React.ReactNode;
+}) {
+ const params = await props.params;
+ const project = await getProject(params.team_slug, params.project_slug);
+
+ if (!project) {
+ redirect(`/team/${params.team_slug}`);
+ }
+
+ return (
+
+
+
+
+ Insight
+
+
+ APIs to retrieve blockchain data from any EVM chain, enrich it with
+ metadata, and transform it using custom logic.{" "}
+
+ Learn more
+
+
+
+
+
+
+
+
+
+ {props.children}
+
+
+
+
+
+ );
+}
+
+function InsightFooter() {
+ return (
+
+ );
+}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/page.tsx
index 3c55930a3d0..3e326952fa3 100644
--- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/page.tsx
+++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/page.tsx
@@ -1,109 +1,83 @@
-import { notFound } from "next/navigation";
-import { isProjectActive } from "@/api/analytics";
+import { loginRedirect } from "@app/login/loginRedirect";
+import { ArrowUpRightIcon } from "lucide-react";
+import { redirect } from "next/navigation";
+import { ResponsiveSearchParamsProvider } from "responsive-rsc";
+import { getAuthToken } from "@/api/auth-token";
import { getProject } from "@/api/projects";
-import { getTeamBySlug } from "@/api/team";
-import { FooterLinksSection } from "../components/footer/FooterLinksSection";
-import { BlueprintCard } from "./blueprint-card";
-import { InsightFTUX } from "./insight-ftux";
+import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
+import { getFiltersFromSearchParams } from "@/lib/time";
+import { InsightAnalytics } from "./components/InsightAnalytics";
export default async function Page(props: {
params: Promise<{
team_slug: string;
project_slug: string;
}>;
+ searchParams: Promise<{
+ from?: string | undefined | string[];
+ to?: string | undefined | string[];
+ interval?: string | undefined | string[];
+ }>;
}) {
- const params = await props.params;
+ const [params, authToken] = await Promise.all([props.params, getAuthToken()]);
+
+ const project = await getProject(params.team_slug, params.project_slug);
- const [team, project] = await Promise.all([
- getTeamBySlug(params.team_slug),
- getProject(params.team_slug, params.project_slug),
- ]);
+ if (!authToken) {
+ loginRedirect(`/team/${params.team_slug}/${params.project_slug}/insight`);
+ }
- if (!team || !project) {
- notFound();
+ if (!project) {
+ redirect(`/team/${params.team_slug}`);
}
- const activeResponse = await isProjectActive({
- projectId: project.id,
- teamId: team.id,
+ const searchParams = await props.searchParams;
+ const { range, interval } = getFiltersFromSearchParams({
+ defaultRange: "last-30",
+ from: searchParams.from,
+ interval: searchParams.interval,
+ to: searchParams.to,
});
- const showFTUX = !activeResponse.insight;
+ const client = getClientThirdwebClient({
+ jwt: authToken,
+ teamId: project.teamId,
+ });
return (
-
- {/* header */}
-
-
-
- Insight
-
-
- APIs to retrieve blockchain data from any EVM chain, enrich it with
- metadata, and transform it using custom logic
-
-
-
-
-
+
+
+
-
- {showFTUX ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
+
+
+
+
+
+
Get Started with Insight
+
+ A cross-chain API for historic blockchain data.
+
+
+
+ Learn More
+
+
+
-
- );
-}
-
-function InsightFooter() {
- return (
-
+
);
}