diff --git a/src/components/CloudAccountList.tsx b/src/components/CloudAccountList.tsx index 46e1389..906a45b 100644 --- a/src/components/CloudAccountList.tsx +++ b/src/components/CloudAccountList.tsx @@ -40,7 +40,7 @@ import { } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { getLocalizedErrorMessage } from '@/utils/errorMessages'; @@ -48,7 +48,7 @@ import { getLocalizedErrorMessage } from '@/utils/errorMessages'; export function CloudAccountList() { const { t } = useTranslation(); - const { data: accounts, isLoading, isError, error } = useCloudAccounts(); + const { data: accounts, isLoading, isError, error, errorUpdatedAt, refetch } = useCloudAccounts(); const refreshMutation = useRefreshQuota(); const deleteMutation = useDeleteCloudAccount(); const addMutation = useAddGoogleAccount(); @@ -60,6 +60,7 @@ export function CloudAccountList() { const forcePollMutation = useForcePollCloudMonitor(); const { toast } = useToast(); + const lastCloudLoadErrorToastAt = useRef(0); const [isAddDialogOpen, setIsAddDialogOpen] = useState(false); const [authCode, setAuthCode] = useState(''); @@ -116,6 +117,19 @@ export function CloudAccountList() { // Batch Operations State const [selectedIds, setSelectedIds] = useState>(new Set()); + useEffect(() => { + if (!isError || !errorUpdatedAt || errorUpdatedAt === lastCloudLoadErrorToastAt.current) { + return; + } + + toast({ + title: t('cloud.error.loadFailed'), + description: getLocalizedErrorMessage(error, t), + variant: 'destructive', + }); + lastCloudLoadErrorToastAt.current = errorUpdatedAt; + }, [error, errorUpdatedAt, isError, t, toast]); + // ... (existing code: handleRefresh, handleSwitch, handleDelete) const handleRefresh = (id: string) => { @@ -291,11 +305,15 @@ export function CloudAccountList() { } if (isError) { - const errorMessage = getLocalizedErrorMessage(error, t); return ( -
-
{t('cloud.error.loadFailed')}
-
{errorMessage}
+
+ +
{t('cloud.error.loadFailed')}
+
{t('action.retry')}
+
); } diff --git a/src/routes/index.tsx b/src/routes/index.tsx index a651025..661415b 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,5 +1,11 @@ import { createFileRoute } from '@tanstack/react-router'; import { CloudAccountList } from '@/components/CloudAccountList'; +import { Button } from '@/components/ui/button'; +import { useToast } from '@/components/ui/use-toast'; +import { getLocalizedErrorMessage } from '@/utils/errorMessages'; +import { RefreshCw } from 'lucide-react'; +import { useEffect, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; function HomePage() { return ( @@ -9,6 +15,45 @@ function HomePage() { ); } +function HomePageErrorBoundary({ + error, + reset, +}: { + error: unknown; + reset: () => void; +}) { + const { t } = useTranslation(); + const { toast } = useToast(); + const hasShownToastRef = useRef(false); + + useEffect(() => { + if (hasShownToastRef.current) { + return; + } + + toast({ + title: t('error.generic'), + description: getLocalizedErrorMessage(error, t), + variant: 'destructive', + }); + hasShownToastRef.current = true; + }, [error, t, toast]); + + return ( +
+
+
{t('error.generic')}
+
{t('action.retry')}
+ +
+
+ ); +} + export const Route = createFileRoute('/')({ component: HomePage, + errorComponent: HomePageErrorBoundary, });