Better loading indicators

This commit is contained in:
Konstantin Wohlwend 2025-10-20 14:03:41 -07:00
parent 5e4f0592c9
commit ba33533fd3
6 changed files with 35 additions and 8 deletions

View File

@ -4,6 +4,7 @@ import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
import { KnownErrors } from "@stackframe/stack-shared";
import { UsersCrud } from "@stackframe/stack-shared/dist/interface/crud/users";
import { adaptSchema, adminAuthTypeSchema, yupArray, yupMixed, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { wait } from "@stackframe/stack-shared/dist/utils/promises";
import yup from 'yup';
import { usersCrudHandlers } from "../../users/crud";
@ -207,6 +208,8 @@ export const GET = createSmartRouteHandler({
}).defined(),
}),
handler: async (req) => {
await wait(5_000);
const now = new Date();
const includeAnonymous = req.query.include_anonymous === "true";

View File

@ -0,0 +1,5 @@
import { SiteLoadingIndicator } from "@/components/site-loading-indicator";
export default function Loading() {
return <SiteLoadingIndicator />;
}

View File

@ -1,9 +1,10 @@
import { Typography } from "@stackframe/stack-ui";
import React from "react";
export function PageLayout(props: {
children?: React.ReactNode,
title?: string,
description?: string,
description?: string | React.ReactNode,
actions?: React.ReactNode,
fillWidth?: boolean,
} & ({
@ -26,7 +27,7 @@ export function PageLayout(props: {
{props.title}
</Typography>}
{props.description && (
<Typography type="p" variant="secondary">
<Typography type={typeof props.description === "string" ? "p" : "div"} variant="secondary">
{props.description}
</Typography>
)}

View File

@ -4,21 +4,33 @@ import { stackAppInternalsSymbol } from "@/app/(main)/integrations/transfer-conf
import { UserTable } from "@/components/data-table/user-table";
import { StyledLink } from "@/components/link";
import { UserDialog } from "@/components/user-dialog";
import { Alert, Button } from "@stackframe/stack-ui";
import { Alert, Button, Skeleton } from "@stackframe/stack-ui";
import { Suspense } from "react";
import { AppEnabledGuard } from "../app-enabled-guard";
import { PageLayout } from "../page-layout";
import { useAdminApp } from "../use-admin-app";
export default function PageClient() {
function TotalUsersDisplay() {
const stackAdminApp = useAdminApp();
const data = (stackAdminApp as any)[stackAppInternalsSymbol].useMetrics();
return <>{data.total_users}</>;
}
export default function PageClient() {
const stackAdminApp = useAdminApp();
const firstUser = stackAdminApp.useUsers({ limit: 1 });
return (
<AppEnabledGuard appId="authentication">
<PageLayout
title="Users"
description={`Total: ${data.total_users}`}
description={<>
Total:{" "}
<Suspense fallback={<Skeleton className="inline"><span>Calculating</span></Skeleton>}>
<TotalUsersDisplay />
</Suspense>
</>}
actions={<UserDialog
type="create"
trigger={<Button>Create User</Button>}

View File

@ -5,13 +5,18 @@ import { cn } from "../../lib/utils";
function Skeleton({
className,
children,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("stack-scope animate-pulse rounded-md bg-primary/10", className)}
{...props}
/>
>
<div className="invisible inline">
{children}
</div>
</div>
);
}

View File

@ -1,7 +1,7 @@
import { cn } from "../../lib/utils";
import { forwardRefIfNeeded } from "@stackframe/stack-shared/dist/utils/react";
import { cva, type VariantProps } from "class-variance-authority";
import React from "react";
import { forwardRefIfNeeded } from "@stackframe/stack-shared/dist/utils/react";
import { cn } from "../../lib/utils";
const typographyVariants = cva("stack-scope text-md", {
variants: {
@ -13,6 +13,7 @@ const typographyVariants = cva("stack-scope text-md", {
p: "text-md",
label: "text-sm",
footnote: "text-xs",
div: "text-md",
},
variant: {
primary: "text-black dark:text-white",