Add skeleton loading state for Search Performance page (#355)
This commit is contained in:
parent
7bcd8497a0
commit
053ac4c4cf
@ -0,0 +1,47 @@
|
||||
// Skeleton loading state for the Search Performance (GSC) page. Mirrors the
|
||||
// loaded layout — four totals cards over a tabbed table panel — so the shell
|
||||
// stays put and only the data fills in, matching the other pages' loaders
|
||||
// (e.g. DomainOverviewLoadingState, KeywordResearchLoadingState).
|
||||
export function SearchPerformanceLoadingState() {
|
||||
return (
|
||||
<div className="space-y-4" aria-busy>
|
||||
<div className="grid grid-cols-2 gap-3 lg:grid-cols-4">
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="rounded-lg border border-base-300 bg-base-100 p-4 space-y-2"
|
||||
>
|
||||
<div className="skeleton h-3 w-20" />
|
||||
<div className="skeleton h-7 w-24" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="overflow-hidden rounded-xl border border-base-300 bg-base-100">
|
||||
<div className="flex flex-col gap-3 border-b border-base-300 px-4 py-3 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="skeleton h-8 w-40" />
|
||||
<div className="skeleton h-8 w-20" />
|
||||
<div className="skeleton h-8 w-16" />
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="skeleton h-8 w-36" />
|
||||
<div className="skeleton h-8 w-36" />
|
||||
<div className="skeleton h-8 w-36" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 p-4">
|
||||
{Array.from({ length: 8 }).map((_, index) => (
|
||||
<div key={index} className="grid grid-cols-5 gap-3">
|
||||
<div className="skeleton col-span-2 h-4" />
|
||||
<div className="skeleton h-4" />
|
||||
<div className="skeleton h-4" />
|
||||
<div className="skeleton h-4" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -10,6 +10,7 @@ import { toast } from "sonner";
|
||||
import { TableExportMenu } from "@/client/components/table/TableBulkActionBar";
|
||||
import { TablePagination } from "@/client/components/table/TablePagination";
|
||||
import { SearchConsoleConnectionCard } from "@/client/features/gsc/SearchConsoleConnectionCard";
|
||||
import { SearchPerformanceLoadingState } from "@/client/features/search-performance/SearchPerformanceLoadingState";
|
||||
import {
|
||||
DimensionTable,
|
||||
exportDimensionRows,
|
||||
@ -198,10 +199,7 @@ export function SearchPerformancePage({ projectId }: { projectId: string }) {
|
||||
</div>
|
||||
|
||||
{reportQuery.isPending ? (
|
||||
<div className="flex items-center gap-2 p-8 text-sm text-base-content/60">
|
||||
<Loader2 className="size-4 animate-spin" /> Loading Search Console
|
||||
data…
|
||||
</div>
|
||||
<SearchPerformanceLoadingState />
|
||||
) : reportQuery.isError ? (
|
||||
<div className="alert alert-error">
|
||||
<span className="text-sm">
|
||||
|
||||
@ -32,25 +32,30 @@ export function AuthenticatedAppLayout({
|
||||
React.useState(false);
|
||||
// On non-project pages (e.g. /settings) there's no projectId in the URL, so
|
||||
// derive one for the nav/switcher: prefer the last-visited project, else the
|
||||
// most recent. Reading localStorage in an effect keeps SSR/first render stable.
|
||||
// most recent. The whole app tree is client-only (see root ClientOnly), so we
|
||||
// can read localStorage synchronously during the first render — this lets the
|
||||
// sidebar show the full project nav on the very first paint instead of briefly
|
||||
// flashing only the always-visible Connect group while projects load.
|
||||
const projectsQuery = useQuery({
|
||||
queryKey: ["projects"],
|
||||
queryFn: () => getProjects(),
|
||||
enabled: !projectId,
|
||||
});
|
||||
const [rememberedProjectId, setRememberedProjectId] = React.useState<
|
||||
string | null
|
||||
>(null);
|
||||
React.useEffect(() => {
|
||||
setRememberedProjectId(getLastProjectId());
|
||||
}, []);
|
||||
const [rememberedProjectId] = React.useState<string | null>(() =>
|
||||
getLastProjectId(),
|
||||
);
|
||||
const fallbackProjects = projectsQuery.data ?? [];
|
||||
const fallbackProjectId =
|
||||
fallbackProjects.find((project) => project.id === rememberedProjectId)
|
||||
?.id ??
|
||||
fallbackProjects[0]?.id ??
|
||||
null;
|
||||
const sidebarProjectId = projectId ?? fallbackProjectId;
|
||||
// Once the projects list loads, fallbackProjectId is the validated choice
|
||||
// (remembered-if-valid, else most recent). Before it loads, fall back to the
|
||||
// remembered id so the project nav renders immediately; a stale id here only
|
||||
// builds links that self-correct via the route guard once data arrives.
|
||||
const sidebarProjectId =
|
||||
projectId ?? fallbackProjectId ?? rememberedProjectId;
|
||||
const shouldCheckSeoApiKeyStatus = location.pathname !== BILLING_ROUTE;
|
||||
const seoApiKeyStatusQuery = useQuery({
|
||||
queryKey: ["seoApiKeyStatus"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user