add better page loading indicator

This commit is contained in:
miloschwartz
2026-09-11 14:09:56 -04:00
parent 8db205352f
commit d776478350
6 changed files with 112 additions and 12 deletions
+2 -6
View File
@@ -1,9 +1,5 @@
import { Loader2 } from "lucide-react";
import OrgRouteLoading from "@app/components/OrgRouteLoading";
export default function OrgPageLoading() {
return (
<div className="flex items-center justify-center py-16">
<Loader2 className="size-6 animate-spin text-muted-foreground" />
</div>
);
return <OrgRouteLoading />;
}
+56
View File
@@ -0,0 +1,56 @@
import { cn } from "@app/lib/cn";
type LoadingDotsProps = {
className?: string;
size?: "sm" | "md";
};
const sizeClasses = {
sm: {
gap: "gap-1.5",
dot: "h-1.5 w-1.5"
},
md: {
gap: "gap-2.5",
dot: "h-2.5 w-2.5"
}
} as const;
export default function LoadingDots({
className,
size = "md"
}: LoadingDotsProps) {
const classes = sizeClasses[size];
return (
<span
className={cn(
"flex items-center text-muted-foreground",
classes.gap,
className
)}
>
<span
className={cn(
"rounded-full bg-current animate-dot-pulse",
classes.dot
)}
style={{ animationDelay: "0ms" }}
/>
<span
className={cn(
"rounded-full bg-current animate-dot-pulse",
classes.dot
)}
style={{ animationDelay: "200ms" }}
/>
<span
className={cn(
"rounded-full bg-current animate-dot-pulse",
classes.dot
)}
style={{ animationDelay: "400ms" }}
/>
</span>
);
}
+45
View File
@@ -0,0 +1,45 @@
"use client";
import Link from "next/link";
import BrandingLogo from "@app/components/BrandingLogo";
import LoadingDots from "@app/components/LoadingDots";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
export default function OrgRouteLoading() {
const { env } = useEnvContext();
const { isUnlocked } = useLicenseStatusContext();
const logoWidth = isUnlocked()
? env.branding.logo?.navbar?.width || 98
: 98;
const logoHeight = isUnlocked()
? env.branding.logo?.navbar?.height || 32
: 32;
return (
<div className="relative h-screen-safe overflow-hidden">
<div className="absolute top-0 left-0 right-0 z-50">
<div className="px-6 py-2">
<div className="container mx-auto max-w-12xl">
<div className="flex h-16 items-center">
<Link
href="/"
className="flex shrink-0 items-center"
>
<BrandingLogo
width={logoWidth}
height={logoHeight}
/>
</Link>
</div>
</div>
</div>
</div>
<div className="absolute inset-0 flex items-center justify-center">
<LoadingDots size="md" />
</div>
</div>
);
}
@@ -9,6 +9,7 @@ import type {
} from "@server/routers/launcher/types";
import { LAUNCHER_FLAT_GROUP_KEY } from "@server/routers/launcher/types";
import { useInfiniteQuery } from "@tanstack/react-query";
import LoadingDots from "@app/components/LoadingDots";
import { Loader2 } from "lucide-react";
import { useEffect, useMemo, useRef } from "react";
import { LauncherEmptyState } from "./LauncherEmptyState";
@@ -81,8 +82,8 @@ export function LauncherFlatResourceList({
if (resources.length === 0) {
if (isFetching) {
return (
<div className="flex items-center justify-center py-16 text-muted-foreground">
<Loader2 className="size-6 animate-spin" />
<div className="flex items-center justify-center py-16">
<LoadingDots size="sm" />
</div>
);
}
@@ -8,6 +8,7 @@ import type {
LauncherViewConfig
} from "@server/routers/launcher/types";
import { useInfiniteQuery } from "@tanstack/react-query";
import LoadingDots from "@app/components/LoadingDots";
import { Loader2 } from "lucide-react";
import { useEffect, useMemo, useRef } from "react";
import { LauncherEmptyState } from "./LauncherEmptyState";
@@ -109,8 +110,8 @@ export function LauncherGroupList({
if (groups.length === 0) {
if (isFetching) {
return (
<div className="flex items-center justify-center py-16 text-muted-foreground">
<Loader2 className="size-6 animate-spin" />
<div className="flex items-center justify-center py-16">
<LoadingDots size="sm" />
</div>
);
}
@@ -22,6 +22,7 @@ import {
LAUNCHER_UNLABELED_GROUP_KEY
} from "@server/routers/launcher/types";
import { useInfiniteQuery } from "@tanstack/react-query";
import LoadingDots from "@app/components/LoadingDots";
import { Loader2 } from "lucide-react";
import { useTranslations } from "next-intl";
import { useEffect, useRef, useState } from "react";
@@ -169,8 +170,8 @@ export function LauncherGroupSection({
<CollapsibleContent className="w-full">
{showInitialLoader ? (
<div className="flex items-center justify-center py-10 text-muted-foreground">
<Loader2 className="size-5 animate-spin" />
<div className="flex items-center justify-center py-10">
<LoadingDots size="sm" />
</div>
) : resources.length === 0 ? (
<p className="py-4 text-sm text-muted-foreground">