diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx
deleted file mode 100644
index ff0e78c69..000000000
--- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx
+++ /dev/null
@@ -1,193 +0,0 @@
-'use client';
-
-import { useRouter } from "@/components/router";
-import { UserAvatar } from '@stackframe/stack';
-import { fromNow } from '@stackframe/stack-shared/dist/utils/dates';
-import {
- Card,
- CardContent,
- CardDescription,
- CardHeader,
- CardTitle,
- Typography,
-} from "@/components/ui";
-import { stackAppInternalsSymbol } from "@/lib/stack-app-internals";
-import { useAdminApp, useProjectId } from '../use-admin-app';
-import { DonutChartDisplay, LineChartDisplay, LineChartDisplayConfig } from './line-chart';
-
-const dailySignUpsConfig = {
- name: 'Daily Sign-ups',
- description: 'User registration over the last 30 days',
- chart: {
- activity: {
- label: "Activity",
- theme: {
- light: "hsl(221, 83%, 53%)", // Bright blue for light mode
- dark: "hsl(217, 91%, 60%)", // Lighter blue for dark mode
- },
- },
- }
-} satisfies LineChartDisplayConfig;
-
-const dauConfig = {
- name: 'Daily Active Users',
- description: 'Number of unique users that were active over the last 30 days',
- chart: {
- activity: {
- label: "Activity",
- theme: {
- light: "hsl(142, 76%, 36%)", // Bright green for light mode
- dark: "hsl(142, 71%, 45%)", // Lighter green for dark mode
- },
- },
- }
-} satisfies LineChartDisplayConfig;
-
-export function ChartsSectionWithData({ includeAnonymous }: { includeAnonymous: boolean }) {
- const adminApp = useAdminApp();
- const projectId = useProjectId();
- const router = useRouter();
- const data = (adminApp as any)[stackAppInternalsSymbol].useMetrics(includeAnonymous);
-
- return (
-
- {/* Charts Grid */}
-
-
-
-
-
- {/* Activity Grid */}
-
- {/* Recent Sign Ups - 2/3 width */}
-
-
-
-
- Activity
-
-
- Recent Sign Ups
-
-
- Users who signed up most recently.
-
-
-
-
- {data.recently_registered.length === 0 ? (
-
-
- No recent sign ups
-
-
- ) : (
-
- {data.recently_registered.map((user: any) => (
-
- ))}
-
- )}
-
-
-
- {/* Auth Methods Donut */}
-
-
-
- {/* Recently Active - Full Width Grid */}
-
-
-
-
- Activity
-
-
- Recently Active
-
-
- Users who were active most recently.
-
-
-
-
- {data.recently_active.length === 0 ? (
-
-
- No recent activity
-
-
- ) : (
-
- {data.recently_active.map((user: any) => (
-
- ))}
-
- )}
-
-
-
- );
-}
diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe-section-with-data.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe-section-with-data.tsx
index a7b867b29..5f488badb 100644
--- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe-section-with-data.tsx
+++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe-section-with-data.tsx
@@ -1,17 +1,37 @@
'use client';
-import { ErrorBoundary } from '@sentry/nextjs';
import { stackAppInternalsSymbol } from "@/lib/stack-app-internals";
+import { captureError } from "@stackframe/stack-shared/dist/utils/errors";
+import { ErrorBoundary } from "next/dist/client/components/error-boundary";
import { useAdminApp } from '../use-admin-app';
import { GlobeSection } from './globe';
-export function GlobeSectionWithData({ includeAnonymous }: { includeAnonymous: boolean }) {
- const adminApp = useAdminApp();
- const data = (adminApp as any)[stackAppInternalsSymbol].useMetrics(includeAnonymous);
+const capturedGlobeErrors = new WeakSet();
+function captureGlobeErrorOnce(error: Error) {
+ if (capturedGlobeErrors.has(error)) {
+ return;
+ }
+ capturedGlobeErrors.add(error);
+ captureError("metrics-globe-error-boundary", error);
+}
+
+export function GlobeSectionWithData({ includeAnonymous }: { includeAnonymous: boolean }) {
return (
- Error initializing globe visualization. Please try updating your browser or enabling WebGL.}>
-
+
+
);
}
+
+function GlobeErrorComponent(props: { error: Error }) {
+ captureGlobeErrorOnce(props.error);
+ return Error initializing globe visualization. Please try updating your browser or enabling WebGL.
;
+}
+
+function GlobeSectionWithMetrics({ includeAnonymous }: { includeAnonymous: boolean }) {
+ const adminApp = useAdminApp();
+ const data = (adminApp as any)[stackAppInternalsSymbol].useMetrics(includeAnonymous);
+
+ return ;
+}
diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-loading.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-loading.tsx
index 21d31b434..960f91a45 100644
--- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-loading.tsx
+++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-loading.tsx
@@ -1,7 +1,18 @@
'use client';
-import { Card, CardContent, cn } from '@/components/ui';
-import { CircleNotchIcon } from '@phosphor-icons/react';
+import { Button, Card, CardContent, cn } from '@/components/ui';
+import { captureError } from '@stackframe/stack-shared/dist/utils/errors';
+import { ArrowClockwiseIcon, CircleNotchIcon, WarningCircleIcon } from '@phosphor-icons/react';
+
+const capturedMetricsErrors = new WeakSet();
+
+function captureMetricsErrorOnce(error: Error) {
+ if (capturedMetricsErrors.has(error)) {
+ return;
+ }
+ capturedMetricsErrors.add(error);
+ captureError("metrics-page-error-boundary", error);
+}
export function MetricsLoadingFallback({ className }: { className?: string }) {
return (
@@ -16,3 +27,37 @@ export function MetricsLoadingFallback({ className }: { className?: string }) {
);
}
+
+export function MetricsErrorFallback({
+ error,
+ onRetryAction,
+ className,
+}: {
+ error: Error,
+ onRetryAction?: () => void,
+ className?: string,
+}) {
+ captureMetricsErrorOnce(error);
+
+ const errorMessage = error.message.trim().length > 0
+ ? error.message
+ : "An unexpected error occurred while loading project metrics.";
+
+ return (
+
+
+
+
+
Failed to load metrics
+
{errorMessage}
+
+ {onRetryAction != null ? (
+
+ ) : null}
+
+
+ );
+}
diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
index 50f6be81b..6dd654de5 100644
--- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
+++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
@@ -18,6 +18,7 @@ import { useUser } from "@stackframe/stack";
import { ALL_APPS } from "@stackframe/stack-shared/dist/apps/apps-config";
import { typedEntries } from "@stackframe/stack-shared/dist/utils/objects";
import { stringCompare } from "@stackframe/stack-shared/dist/utils/strings";
+import { ErrorBoundary } from "next/dist/client/components/error-boundary";
import { Suspense, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type ElementType } from "react";
import { PageLayout } from "../page-layout";
import { useAdminApp, useProjectId } from "../use-admin-app";
@@ -42,7 +43,7 @@ import {
VisitorsHoverChart,
VisitorsHoverDataPoint,
} from "./line-chart";
-import { MetricsLoadingFallback } from "./metrics-loading";
+import { MetricsErrorFallback, MetricsLoadingFallback } from "./metrics-loading";
const dailySignUpsConfig: LineChartDisplayConfig = {
name: 'Daily Sign-Ups',
@@ -932,13 +933,19 @@ export default function MetricsPage(props: { toSetup: () => void }) {
fullBleed
wrapHeaderInCard
>
- }>
-
-
+
+ }>
+
+
+
);
}
+function MetricsErrorComponent(props: { error: Error, reset?: () => void }) {
+ return ;
+}
+
function MetricsContent({
includeAnonymous,
timeRange,
diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx
index ced4e6e87..cce06df4e 100644
--- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx
+++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx
@@ -6,12 +6,24 @@ import { StyledLink } from "@/components/link";
import { Alert, Button, SimpleTooltip, Skeleton } from "@/components/ui";
import { UserDialog } from "@/components/user-dialog";
import { stackAppInternalsSymbol } from "@/lib/stack-app-internals";
+import { captureError } from "@stackframe/stack-shared/dist/utils/errors";
import { ArrowsClockwiseIcon, DownloadSimpleIcon } from "@phosphor-icons/react";
+import { ErrorBoundary } from "next/dist/client/components/error-boundary";
import { Suspense, useState } from "react";
import { AppEnabledGuard } from "../app-enabled-guard";
import { PageLayout } from "../page-layout";
import { useAdminApp } from "../use-admin-app";
+const capturedUsersMetricsErrors = new WeakSet();
+
+function captureUsersMetricsErrorOnce(error: Error) {
+ if (capturedUsersMetricsErrors.has(error)) {
+ return;
+ }
+ capturedUsersMetricsErrors.add(error);
+ captureError("users-total-metrics-error-boundary", error);
+}
+
function TotalUsersDisplay() {
const stackAdminApp = useAdminApp();
const metrics = (stackAdminApp as any)[stackAppInternalsSymbol].useMetrics(false);
@@ -38,6 +50,11 @@ function TotalUsersDisplay() {
);
}
+function TotalUsersErrorComponent(props: { error: Error }) {
+ captureUsersMetricsErrorOnce(props.error);
+ return <>Unavailable>;
+}
+
export default function PageClient() {
const stackAdminApp = useAdminApp();
const firstUser = (stackAdminApp as any).useUsers({ limit: 1 });
@@ -60,9 +77,11 @@ export default function PageClient() {
title="Users"
description={<>
Total:{" "}
- Calculating}>
-
-
+
+ Calculating}>
+
+
+
>}
actions={