feat: implement LoadingSpinner component and replace existing loaders across the application

This commit is contained in:
Junyan Qin
2026-01-29 15:24:23 +08:00
parent 13f42857f5
commit b89a240250
7 changed files with 111 additions and 50 deletions

View File

@@ -19,6 +19,7 @@ import {
CardDescription,
} from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { LoadingSpinner } from '@/components/ui/loading-spinner';
import langbotIcon from '@/app/assets/langbot-logo.webp';
function SpaceOAuthCallbackContent() {
@@ -174,9 +175,7 @@ function SpaceOAuthCallbackContent() {
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col items-center space-y-4">
{status === 'loading' && (
<Loader2 className="h-12 w-12 animate-spin text-primary" />
)}
{status === 'loading' && <LoadingSpinner size="lg" text="" />}
{status === 'confirm' && (
<>
<AlertTriangle className="h-12 w-12 text-yellow-500" />
@@ -232,7 +231,7 @@ function LoadingFallback() {
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-neutral-900">
<Card className="w-[400px] shadow-lg dark:shadow-white/10">
<CardContent className="flex flex-col items-center py-12">
<Loader2 className="h-12 w-12 animate-spin text-primary" />
<LoadingSpinner size="lg" text="" />
</CardContent>
</Card>
</div>

View File

@@ -1,7 +1,7 @@
'use client';
import styles from './HomeSidebar.module.css';
import { useEffect, useState, Suspense } from 'react';
import { useEffect, useState } from 'react';
import {
SidebarChild,
SidebarChildVO,
@@ -20,7 +20,6 @@ import {
Lightbulb,
LogOut,
KeyRound,
Loader2,
} from 'lucide-react';
import { useTheme } from 'next-themes';
@@ -59,7 +58,7 @@ function compareVersions(v1: string, v2: string): boolean {
}
// TODO 侧边导航栏要加动画
function HomeSidebarContent({
export default function HomeSidebar({
onSelectedChangeAction,
}: {
onSelectedChangeAction: (sidebarChild: SidebarChildVO) => void;
@@ -484,25 +483,3 @@ function HomeSidebarContent({
</div>
);
}
function SidebarLoadingFallback() {
return (
<div className={`${styles.sidebarContainer}`}>
<div className="flex items-center justify-center h-full">
<Loader2 className="h-6 w-6 animate-spin" />
</div>
</div>
);
}
export default function HomeSidebar({
onSelectedChangeAction,
}: {
onSelectedChangeAction: (sidebarChild: SidebarChildVO) => void;
}) {
return (
<Suspense fallback={<SidebarLoadingFallback />}>
<HomeSidebarContent onSelectedChangeAction={onSelectedChangeAction} />
</Suspense>
);
}

View File

@@ -0,0 +1,5 @@
import { LoadingPage } from '@/components/ui/loading-spinner';
export default function Loading() {
return <LoadingPage />;
}

View File

@@ -13,6 +13,7 @@ import { MessageDetailsCard } from './components/MessageDetailsCard';
import { MessageContentRenderer } from './components/MessageContentRenderer';
import { MessageDetails } from './types/monitoring';
import { httpClient } from '@/app/infra/http/HttpClient';
import { LoadingSpinner, LoadingPage } from '@/components/ui/loading-spinner';
interface RawMessageData {
id: string;
@@ -262,11 +263,10 @@ function MonitoringPageContent() {
<TabsContent value="messages" className="p-6 m-0">
<div>
{loading && (
<div className="text-center text-gray-500 dark:text-gray-400 py-12">
<div className="inline-block animate-spin rounded-full h-10 w-10 border-b-2 border-blue-600 dark:border-blue-400 mb-4"></div>
<p className="text-sm font-medium">
{t('monitoring.messageList.loading')}
</p>
<div className="py-12 flex justify-center">
<LoadingSpinner
text={t('monitoring.messageList.loading')}
/>
</div>
)}
@@ -363,8 +363,8 @@ function MonitoringPageContent() {
{expandedMessageId === msg.id && (
<div className="border-t border-gray-200 dark:border-gray-700 p-4 bg-gray-50 dark:bg-gray-900">
{loadingDetails[msg.id] && (
<div className="text-center text-gray-500 dark:text-gray-400 py-4">
<div className="inline-block animate-spin rounded-full h-6 w-6 border-b-2 border-gray-900 dark:border-white"></div>
<div className="py-4 flex justify-center">
<LoadingSpinner size="sm" text="" />
</div>
)}
{!loadingDetails[msg.id] &&
@@ -410,9 +410,8 @@ function MonitoringPageContent() {
<TabsContent value="modelCalls" className="p-6 m-0">
<div>
{loading && (
<div className="text-center text-gray-500 dark:text-gray-400 py-12">
<div className="inline-block animate-spin rounded-full h-10 w-10 border-b-2 border-blue-600 dark:border-blue-400 mb-4"></div>
<p className="text-sm font-medium">{t('common.loading')}</p>
<div className="py-12 flex justify-center">
<LoadingSpinner text={t('common.loading')} />
</div>
)}
@@ -629,9 +628,8 @@ function MonitoringPageContent() {
<TabsContent value="errors" className="p-6 m-0">
<div>
{loading && (
<div className="text-center text-gray-500 dark:text-gray-400 py-12">
<div className="inline-block animate-spin rounded-full h-10 w-10 border-b-2 border-blue-600 dark:border-blue-400 mb-4"></div>
<p className="text-sm font-medium">{t('common.loading')}</p>
<div className="py-12 flex justify-center">
<LoadingSpinner text={t('common.loading')} />
</div>
)}
@@ -810,7 +808,7 @@ function MonitoringPageContent() {
export default function MonitoringPage() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Suspense fallback={<LoadingPage />}>
<MonitoringPageContent />
</Suspense>
);

View File

@@ -27,6 +27,7 @@ import { PluginV4 } from '@/app/infra/entities/plugin';
import { extractI18nObject } from '@/i18n/I18nProvider';
import { toast } from 'sonner';
import { ApiRespMarketplacePlugins } from '@/app/infra/entities/api';
import { LoadingSpinner } from '@/components/ui/loading-spinner';
interface SortOption {
value: string;
@@ -460,8 +461,7 @@ function MarketPageContent({
>
{isLoading ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-8 w-8 animate-spin" />
<span className="ml-2">{t('market.loading')}</span>
<LoadingSpinner text={t('market.loading')} />
</div>
) : plugins.length === 0 ? (
<div className="flex items-center justify-center py-12">
@@ -484,8 +484,7 @@ function MarketPageContent({
{/* Loading more indicator */}
{isLoadingMore && (
<div className="flex items-center justify-center py-6">
<Loader2 className="h-6 w-6 animate-spin" />
<span className="ml-2">{t('market.loadingMore')}</span>
<LoadingSpinner size="sm" text={t('market.loadingMore')} />
</div>
)}
@@ -522,8 +521,7 @@ export default function MarketPage({
fallback={
<div className="container mx-auto px-4 py-6">
<div className="flex items-center justify-center py-12">
<Loader2 className="h-8 w-8 animate-spin" />
<span className="ml-2">...</span>
<LoadingSpinner text="加载中..." />
</div>
</div>
}

View File

@@ -29,6 +29,7 @@ import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
import Link from 'next/link';
import { ThemeToggle } from '@/components/ui/theme-toggle';
import { LoadingSpinner } from '@/components/ui/loading-spinner';
const formSchema = (t: (key: string) => string) =>
z.object({
@@ -123,7 +124,7 @@ export default function Login() {
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-neutral-900">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
<LoadingSpinner />
</div>
);
}