Skip to content

Commit f621020

Browse files
feat: unify loading states across the application
Standardizes the application's loading experience by replacing scattered, duplicated loading UI implementations with a single, reusable Loader component. Replaces manual loading blocks and plain text messages in instrumentation and collector pages, and unifies Suspense fallbacks. Resolved conflicts with upstream/main incorporating latest retry logic and SPA fallback protections.
1 parent 3291034 commit f621020

12 files changed

Lines changed: 103 additions & 98 deletions

File tree

ecosystem-explorer/src/LegacyApp.tsx

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { Header } from "@/components/layout/header";
1919
import { Footer } from "@/components/layout/footer";
2020
import { isEnabled } from "@/lib/feature-flags";
2121
import { ErrorBoundary } from "@/components/ui/error-boundary";
22+
import { Loader } from "@/components/ui/Loader";
2223

2324
const HomePage = lazy(() =>
2425
import("@/features/home/home-page").then((m) => ({ default: m.HomePage }))
@@ -82,17 +83,7 @@ export function LegacyApp() {
8283
<Header />
8384
<main className="flex-1 pt-16">
8485
<ErrorBoundary>
85-
<Suspense
86-
fallback={
87-
<div
88-
className="flex min-h-[400px] items-center justify-center"
89-
role="status"
90-
aria-live="polite"
91-
>
92-
<div className="text-muted-foreground text-sm font-medium">Loading…</div>
93-
</div>
94-
}
95-
>
86+
<Suspense fallback={<Loader label="Loading…" />}>
9687
<Routes>
9788
<Route path="/" element={<HomePage />} />
9889
<Route path="/java-agent" element={<JavaAgentPage />} />
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { Loader2 } from "lucide-react";
17+
18+
type LoaderProps = {
19+
/**
20+
* Size of the loader.
21+
* "lg" is for full-page states and Suspense fallbacks.
22+
* "sm" is for inline/compact states.
23+
*/
24+
size?: "sm" | "lg";
25+
/** Optional additional CSS classes */
26+
className?: string;
27+
/** Optional text to display alongside the loader */
28+
label?: string;
29+
};
30+
31+
/**
32+
* A unified Loader component for consistent loading states across the application.
33+
*/
34+
export function Loader({ size = "lg", className = "", label }: LoaderProps) {
35+
if (size === "sm") {
36+
return (
37+
<div
38+
className={`flex items-center gap-3 py-2 ${className}`}
39+
role="status"
40+
aria-live="polite"
41+
aria-busy="true"
42+
>
43+
<Loader2 className="text-primary h-4 w-4 animate-spin" aria-hidden="true" />
44+
{label && <span className="text-muted-foreground text-sm font-medium">{label}</span>}
45+
</div>
46+
);
47+
}
48+
49+
return (
50+
<div
51+
className={`flex min-h-[400px] flex-col items-center justify-center space-y-6 px-4 py-12 text-center ${className}`}
52+
role="status"
53+
aria-live="polite"
54+
aria-busy="true"
55+
>
56+
<div className="relative">
57+
{/* Glow effect */}
58+
<div className="bg-primary/20 absolute inset-0 animate-pulse rounded-full blur-2xl" />
59+
60+
<div className="bg-card border-primary/10 relative inline-flex animate-pulse rounded-full border p-6 shadow-[0_0_50px_hsl(var(--primary-hsl)/0.15)]">
61+
<Loader2 className="text-primary h-12 w-12 animate-spin" aria-hidden="true" />
62+
</div>
63+
</div>
64+
65+
{label && (
66+
<div className="space-y-2">
67+
<p className="text-foreground text-lg font-semibold tracking-tight">{label}</p>
68+
<p className="text-muted-foreground text-sm">This may take a moment</p>
69+
</div>
70+
)}
71+
</div>
72+
);
73+
}

ecosystem-explorer/src/features/collector/collector-detail-page.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
*/
1616
import { useState } from "react";
1717
import { useParams, useSearchParams, useNavigate } from "react-router-dom";
18-
import { Info, ExternalLink, AlertCircle, Loader2, Check, Users } from "lucide-react";
18+
import { Info, ExternalLink, AlertCircle, Check, Users } from "lucide-react";
19+
import { Loader } from "@/components/ui/Loader";
1920
import { GitHubIcon } from "@/components/icons/github-icon";
2021

2122
import { BackButton } from "@/components/ui/back-button";
@@ -70,17 +71,7 @@ export function CollectorDetailPage() {
7071
if (loading || versionLoading) {
7172
return (
7273
<PageContainer>
73-
<div className="flex min-h-[400px] items-center justify-center">
74-
<div className="text-center">
75-
<div className="inline-flex animate-pulse rounded-full p-4 shadow-[0_0_60px_hsl(var(--otel-orange-hsl)/0.2)]">
76-
<Loader2 className="text-secondary h-12 w-12 animate-spin" aria-hidden="true" />
77-
</div>
78-
<div className="mt-6 space-y-2">
79-
<div className="text-foreground text-lg font-medium">Loading component...</div>
80-
<div className="text-muted-foreground text-sm">This may take a moment</div>
81-
</div>
82-
</div>
83-
</div>
74+
<Loader label="Loading component..." />
8475
</PageContainer>
8576
);
8677
}

ecosystem-explorer/src/features/collector/collector-page.tsx

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,8 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import {
17-
Box,
18-
ChevronDown,
19-
ChevronRight,
20-
Layers,
21-
Loader2,
22-
Plug,
23-
Search,
24-
Send,
25-
Workflow,
26-
} from "lucide-react";
16+
import { Box, ChevronDown, ChevronRight, Layers, Plug, Search, Send, Workflow } from "lucide-react";
17+
import { Loader } from "@/components/ui/Loader";
2718
import { useMemo, useState } from "react";
2819
import { Link, useSearchParams, useParams } from "react-router-dom";
2920

@@ -182,12 +173,7 @@ function CollectorPageInner() {
182173
</div>
183174

184175
{componentsLoading ? (
185-
<div className="flex flex-col items-center justify-center space-y-4 py-32">
186-
<div className="inline-flex animate-pulse rounded-full p-4 shadow-[0_0_60px_hsl(var(--primary-hsl)/0.2)]">
187-
<Loader2 className="text-primary h-10 w-10 animate-spin" aria-hidden="true" />
188-
</div>
189-
<p className="text-muted-foreground text-sm font-medium">Loading components...</p>
190-
</div>
176+
<Loader label="Loading components..." />
191177
) : hasError ? (
192178
<div className="flex flex-col items-center justify-center space-y-4 py-32">
193179
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-red-100/10 text-red-500">

ecosystem-explorer/src/features/java-agent/components/telemetry-comparison/telemetry-comparison-section.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { AlertCircle, Loader2 } from "lucide-react";
17+
import { AlertCircle } from "lucide-react";
18+
import { Loader } from "@/components/ui/Loader";
1819
import type { VersionInfo } from "@/types/javaagent";
1920
import { useTelemetryComparison } from "../../hooks/use-telemetry-comparison";
2021
import { VersionSelectorPanel } from "./version-selector-panel";
@@ -70,10 +71,7 @@ export function TelemetryComparisonSection({
7071
{/* Loading state */}
7172
{loading && (
7273
<div className="flex min-h-[300px] items-center justify-center">
73-
<div className="flex items-center gap-3">
74-
<Loader2 className="text-primary h-6 w-6 animate-spin" />
75-
<p className="text-muted-foreground text-sm">Loading comparison data...</p>
76-
</div>
74+
<Loader size="sm" label="Loading comparison data..." />
7775
</div>
7876
)}
7977

ecosystem-explorer/src/features/java-agent/configuration/components/instrumentation-browser.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
import { useState, useCallback, useMemo, type JSX } from "react";
1717
import type { InstrumentationData, InstrumentationModule } from "@/types/javaagent";
18+
import { Loader } from "@/components/ui/Loader";
1819
import { useConfigurationBuilder } from "@/hooks/use-configuration-builder";
1920
import {
2021
useCustomizationStatusMap,
@@ -110,7 +111,7 @@ export function InstrumentationBrowser({
110111
</header>
111112

112113
{loading ? (
113-
<p className="text-muted-foreground text-sm">Loading instrumentations…</p>
114+
<Loader size="sm" label="Loading instrumentations…" />
114115
) : error ? (
115116
<p className="text-sm text-red-400">Failed to load instrumentations.</p>
116117
) : (

ecosystem-explorer/src/features/java-agent/configuration/configuration-builder-page.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616
import { useEffect, useMemo, useRef, useState } from "react";
17+
import { Loader } from "@/components/ui/Loader";
1718
import { BackButton } from "@/components/ui/back-button";
1819
import { BetaBadge } from "@/components/ui/beta-badge";
1920
import { PageContainer } from "@/components/layout/page-container";
@@ -305,8 +306,8 @@ export function ConfigurationBuilderPage() {
305306
<Tabs value={activeTab} onValueChange={setActiveTab}>
306307
<TabsContent value="sdk">
307308
{!version || schema.loading || starter.loading ? (
308-
<p className="text-muted-foreground mt-4 text-sm">Loading schema…</p>
309-
) : schema.error ? (
309+
<Loader size="sm" label="Loading schema…" className="mt-4" />
310+
) : schema.error || !root ? (
310311
<p className="mt-4 text-sm text-red-400">Failed to load schema.</p>
311312
) : starter.error ? (
312313
<p className="mt-4 text-sm text-red-400">Failed to load starter template.</p>
@@ -321,8 +322,8 @@ export function ConfigurationBuilderPage() {
321322
</TabsContent>
322323
<TabsContent value="instrumentation">
323324
{!version || schema.loading || starter.loading ? (
324-
<p className="text-muted-foreground mt-4 text-sm">Loading schema…</p>
325-
) : schema.error ? (
325+
<Loader size="sm" label="Loading schema…" className="mt-4" />
326+
) : schema.error || !root ? (
326327
<p className="mt-4 text-sm text-red-400">Failed to load schema.</p>
327328
) : starter.error ? (
328329
<p className="mt-4 text-sm text-red-400">Failed to load starter template.</p>

ecosystem-explorer/src/features/java-agent/instrumentation-detail-page.tsx

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ import {
2323
Code,
2424
Check,
2525
AlertCircle,
26-
Loader2,
2726
HelpCircle,
2827
} from "lucide-react";
2928

29+
import { Loader } from "@/components/ui/Loader";
30+
3031
import { BackButton } from "@/components/ui/back-button";
3132
import { Tabs, TabsContent } from "@/components/ui/tabs";
3233
import { SegmentedTabList } from "@/components/ui/segmented-tabs";
@@ -112,22 +113,7 @@ export function InstrumentationDetailPage() {
112113
if (loading) {
113114
return (
114115
<PageContainer>
115-
<div className="flex min-h-[400px] items-center justify-center">
116-
<div className="text-center">
117-
<div
118-
className="inline-flex animate-pulse rounded-full p-4"
119-
style={{
120-
boxShadow: "0 0 60px hsl(var(--otel-orange-hsl) / 0.2)",
121-
}}
122-
>
123-
<Loader2 className="text-secondary h-12 w-12 animate-spin" aria-hidden="true" />
124-
</div>
125-
<div className="mt-6 space-y-2">
126-
<div className="text-lg font-medium">Loading instrumentation...</div>
127-
<div className="text-muted-foreground text-sm">This may take a moment</div>
128-
</div>
129-
</div>
130-
</div>
116+
<Loader label="Loading instrumentation..." />
131117
</PageContainer>
132118
);
133119
}

ecosystem-explorer/src/features/java-agent/java-configuration-list-page.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
* limitations under the License.
1515
*/
1616
import { useState, useMemo, useEffect } from "react";
17-
import { Search, Settings, Loader2 } from "lucide-react";
17+
import { Search, Settings } from "lucide-react";
18+
import { Loader } from "@/components/ui/Loader";
1819
import { BackButton } from "@/components/ui/back-button";
1920
import { PageContainer } from "@/components/layout/page-container";
2021
import { Tabs } from "@/components/ui/tabs";
@@ -127,22 +128,14 @@ export function JavaConfigurationListPage() {
127128
<div className="mt-6">
128129
<div className="text-muted-foreground mb-4 text-sm">
129130
{isLoading
130-
? "Loading..."
131+
? "..."
131132
: error
132133
? "Unable to load configurations"
133134
: `Found ${filteredConfigs.length} configurations`}
134135
</div>
135136

136137
{isLoading ? (
137-
<div className="flex min-h-[300px] items-center justify-center rounded-lg border border-dashed">
138-
<div className="text-center">
139-
<Loader2
140-
className="text-primary mx-auto h-12 w-12 animate-spin"
141-
aria-hidden="true"
142-
/>
143-
<p className="text-muted-foreground mt-4 text-sm">Loading configurations...</p>
144-
</div>
145-
</div>
138+
<Loader label="Loading configurations..." />
146139
) : error ? (
147140
<div className="flex min-h-[300px] items-center justify-center rounded-lg border border-dashed">
148141
<div className="mx-auto max-w-md px-4 text-center">

ecosystem-explorer/src/features/java-agent/java-instrumentation-list-page.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { AlertCircle, Loader2 } from "lucide-react";
16+
import { AlertCircle } from "lucide-react";
17+
import { Loader } from "@/components/ui/Loader";
1718
import { BackButton } from "@/components/ui/back-button";
1819
import { useVersions, useInstrumentations } from "@/hooks/use-javaagent-data";
1920
import {
@@ -215,12 +216,7 @@ export function JavaInstrumentationListPage() {
215216
<p className="text-muted-foreground">Please try refreshing the page.</p>
216217
</div>
217218
) : versionsLoading || instrumentationsLoading || (!resolvedVersion && !versionsError) ? (
218-
<div className="flex flex-col items-center justify-center space-y-4 py-32">
219-
<div className="inline-flex animate-pulse rounded-full p-4 shadow-[0_0_60px_hsl(var(--primary-hsl)/0.2)]">
220-
<Loader2 className="text-primary h-10 w-10 animate-spin" aria-hidden="true" />
221-
</div>
222-
<p className="text-muted-foreground text-sm font-medium">Loading instrumentations...</p>
223-
</div>
219+
<Loader label="Loading instrumentations..." />
224220
) : (
225221
<>
226222
<div className="border-border/50 flex items-center justify-between border-b pb-4">

0 commit comments

Comments
 (0)