mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-11 21:41:41 +02:00
add better page loading indicator
This commit is contained in:
@@ -1,9 +1,5 @@
|
|||||||
import { Loader2 } from "lucide-react";
|
import OrgRouteLoading from "@app/components/OrgRouteLoading";
|
||||||
|
|
||||||
export default function OrgPageLoading() {
|
export default function OrgPageLoading() {
|
||||||
return (
|
return <OrgRouteLoading />;
|
||||||
<div className="flex items-center justify-center py-16">
|
|
||||||
<Loader2 className="size-6 animate-spin text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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";
|
} from "@server/routers/launcher/types";
|
||||||
import { LAUNCHER_FLAT_GROUP_KEY } from "@server/routers/launcher/types";
|
import { LAUNCHER_FLAT_GROUP_KEY } from "@server/routers/launcher/types";
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
|
import LoadingDots from "@app/components/LoadingDots";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
import { useEffect, useMemo, useRef } from "react";
|
||||||
import { LauncherEmptyState } from "./LauncherEmptyState";
|
import { LauncherEmptyState } from "./LauncherEmptyState";
|
||||||
@@ -81,8 +82,8 @@ export function LauncherFlatResourceList({
|
|||||||
if (resources.length === 0) {
|
if (resources.length === 0) {
|
||||||
if (isFetching) {
|
if (isFetching) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-16 text-muted-foreground">
|
<div className="flex items-center justify-center py-16">
|
||||||
<Loader2 className="size-6 animate-spin" />
|
<LoadingDots size="sm" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type {
|
|||||||
LauncherViewConfig
|
LauncherViewConfig
|
||||||
} from "@server/routers/launcher/types";
|
} from "@server/routers/launcher/types";
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
|
import LoadingDots from "@app/components/LoadingDots";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
import { useEffect, useMemo, useRef } from "react";
|
||||||
import { LauncherEmptyState } from "./LauncherEmptyState";
|
import { LauncherEmptyState } from "./LauncherEmptyState";
|
||||||
@@ -109,8 +110,8 @@ export function LauncherGroupList({
|
|||||||
if (groups.length === 0) {
|
if (groups.length === 0) {
|
||||||
if (isFetching) {
|
if (isFetching) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-16 text-muted-foreground">
|
<div className="flex items-center justify-center py-16">
|
||||||
<Loader2 className="size-6 animate-spin" />
|
<LoadingDots size="sm" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
LAUNCHER_UNLABELED_GROUP_KEY
|
LAUNCHER_UNLABELED_GROUP_KEY
|
||||||
} from "@server/routers/launcher/types";
|
} from "@server/routers/launcher/types";
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
|
import LoadingDots from "@app/components/LoadingDots";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
@@ -169,8 +170,8 @@ export function LauncherGroupSection({
|
|||||||
|
|
||||||
<CollapsibleContent className="w-full">
|
<CollapsibleContent className="w-full">
|
||||||
{showInitialLoader ? (
|
{showInitialLoader ? (
|
||||||
<div className="flex items-center justify-center py-10 text-muted-foreground">
|
<div className="flex items-center justify-center py-10">
|
||||||
<Loader2 className="size-5 animate-spin" />
|
<LoadingDots size="sm" />
|
||||||
</div>
|
</div>
|
||||||
) : resources.length === 0 ? (
|
) : resources.length === 0 ? (
|
||||||
<p className="py-4 text-sm text-muted-foreground">
|
<p className="py-4 text-sm text-muted-foreground">
|
||||||
|
|||||||
Reference in New Issue
Block a user