diff --git a/web/src/app/auth/space/callback/page.tsx b/web/src/app/auth/space/callback/page.tsx index f237f37e..bf9ec024 100644 --- a/web/src/app/auth/space/callback/page.tsx +++ b/web/src/app/auth/space/callback/page.tsx @@ -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() { - {status === 'loading' && ( - - )} + {status === 'loading' && } {status === 'confirm' && ( <> @@ -232,7 +231,7 @@ function LoadingFallback() {
- +
diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx index 5a6d3a4f..f8901614 100644 --- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx +++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx @@ -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({ ); } - -function SidebarLoadingFallback() { - return ( -
-
- -
-
- ); -} - -export default function HomeSidebar({ - onSelectedChangeAction, -}: { - onSelectedChangeAction: (sidebarChild: SidebarChildVO) => void; -}) { - return ( - }> - - - ); -} diff --git a/web/src/app/home/loading.tsx b/web/src/app/home/loading.tsx new file mode 100644 index 00000000..8d61d2d6 --- /dev/null +++ b/web/src/app/home/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingPage } from '@/components/ui/loading-spinner'; + +export default function Loading() { + return ; +} diff --git a/web/src/app/home/monitoring/page.tsx b/web/src/app/home/monitoring/page.tsx index c6d83336..d7ebc486 100644 --- a/web/src/app/home/monitoring/page.tsx +++ b/web/src/app/home/monitoring/page.tsx @@ -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() {
{loading && ( -
-
-

- {t('monitoring.messageList.loading')} -

+
+
)} @@ -363,8 +363,8 @@ function MonitoringPageContent() { {expandedMessageId === msg.id && (
{loadingDetails[msg.id] && ( -
-
+
+
)} {!loadingDetails[msg.id] && @@ -410,9 +410,8 @@ function MonitoringPageContent() {
{loading && ( -
-
-

{t('common.loading')}

+
+
)} @@ -629,9 +628,8 @@ function MonitoringPageContent() {
{loading && ( -
-
-

{t('common.loading')}

+
+
)} @@ -810,7 +808,7 @@ function MonitoringPageContent() { export default function MonitoringPage() { return ( - Loading...
}> + }> ); diff --git a/web/src/app/home/plugins/components/plugin-market/PluginMarketComponent.tsx b/web/src/app/home/plugins/components/plugin-market/PluginMarketComponent.tsx index 4c896127..321a8713 100644 --- a/web/src/app/home/plugins/components/plugin-market/PluginMarketComponent.tsx +++ b/web/src/app/home/plugins/components/plugin-market/PluginMarketComponent.tsx @@ -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 ? (
- - {t('market.loading')} +
) : plugins.length === 0 ? (
@@ -484,8 +484,7 @@ function MarketPageContent({ {/* Loading more indicator */} {isLoadingMore && (
- - {t('market.loadingMore')} +
)} @@ -522,8 +521,7 @@ export default function MarketPage({ fallback={
- - 加载中... +
} diff --git a/web/src/app/login/page.tsx b/web/src/app/login/page.tsx index 39d5272a..05ab6e34 100644 --- a/web/src/app/login/page.tsx +++ b/web/src/app/login/page.tsx @@ -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 (
- +
); } diff --git a/web/src/components/ui/loading-spinner.tsx b/web/src/components/ui/loading-spinner.tsx new file mode 100644 index 00000000..42c34840 --- /dev/null +++ b/web/src/components/ui/loading-spinner.tsx @@ -0,0 +1,83 @@ +import { Loader2 } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +interface LoadingSpinnerProps { + /** + * Size variant of the spinner + * @default 'default' + */ + size?: 'sm' | 'default' | 'lg'; + /** + * Additional CSS classes + */ + className?: string; + /** + * Loading text to display below the spinner + */ + text?: string; + /** + * Whether to display as full page overlay + * @default false + */ + fullPage?: boolean; +} + +const sizeMap = { + sm: 'h-4 w-4', + default: 'h-8 w-8', + lg: 'h-12 w-12', +}; + +const textSizeMap = { + sm: 'text-xs', + default: 'text-sm', + lg: 'text-base', +}; + +export function LoadingSpinner({ + size = 'default', + className, + text = '加载中...', + fullPage = false, +}: LoadingSpinnerProps) { + const spinner = ( +
+ + {text && ( +

{text}

+ )} +
+ ); + + if (fullPage) { + return ( +
+ {spinner} +
+ ); + } + + return spinner; +} + +/** + * Full page loading component for use in page.tsx or layout.tsx + */ +export function LoadingPage({ text }: { text?: string }) { + return ; +} + +/** + * Inline loading component for use within components + */ +export function LoadingInline({ + size, + text, +}: { + size?: 'sm' | 'default' | 'lg'; + text?: string; +}) { + return ; +}