fix: prevent page crash on 500 and add toast-based fallback (#90)
This commit is contained in:
2 files changed
+69
-6
No files matched your search
@@ -40,7 +40,7 @@ import {
|
|||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { getLocalizedErrorMessage } from '@/utils/errorMessages';
|
import { getLocalizedErrorMessage } from '@/utils/errorMessages';
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ import { getLocalizedErrorMessage } from '@/utils/errorMessages';
|
|||||||
|
|
||||||
export function CloudAccountList() {
|
export function CloudAccountList() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { data: accounts, isLoading, isError, error } = useCloudAccounts();
|
const { data: accounts, isLoading, isError, error, errorUpdatedAt, refetch } = useCloudAccounts();
|
||||||
const refreshMutation = useRefreshQuota();
|
const refreshMutation = useRefreshQuota();
|
||||||
const deleteMutation = useDeleteCloudAccount();
|
const deleteMutation = useDeleteCloudAccount();
|
||||||
const addMutation = useAddGoogleAccount();
|
const addMutation = useAddGoogleAccount();
|
||||||
@@ -60,6 +60,7 @@ export function CloudAccountList() {
|
|||||||
const forcePollMutation = useForcePollCloudMonitor();
|
const forcePollMutation = useForcePollCloudMonitor();
|
||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const lastCloudLoadErrorToastAt = useRef<number>(0);
|
||||||
|
|
||||||
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
|
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
|
||||||
const [authCode, setAuthCode] = useState('');
|
const [authCode, setAuthCode] = useState('');
|
||||||
@@ -116,6 +117,19 @@ export function CloudAccountList() {
|
|||||||
// Batch Operations State
|
// Batch Operations State
|
||||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(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)
|
// ... (existing code: handleRefresh, handleSwitch, handleDelete)
|
||||||
|
|
||||||
const handleRefresh = (id: string) => {
|
const handleRefresh = (id: string) => {
|
||||||
@@ -291,11 +305,15 @@ export function CloudAccountList() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
const errorMessage = getLocalizedErrorMessage(error, t);
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 text-red-500">
|
<div className="col-span-full rounded-lg border border-dashed p-8 text-center">
|
||||||
<div>{t('cloud.error.loadFailed')}</div>
|
<Cloud className="text-muted-foreground mx-auto mb-3 h-10 w-10 opacity-40" />
|
||||||
<div className="mt-1 text-sm text-red-400">{errorMessage}</div>
|
<div className="text-sm font-medium">{t('cloud.error.loadFailed')}</div>
|
||||||
|
<div className="text-muted-foreground mt-2 text-xs">{t('action.retry')}</div>
|
||||||
|
<Button className="mt-4" variant="outline" onClick={() => void refetch()}>
|
||||||
|
<RefreshCw className="mr-2 h-4 w-4" />
|
||||||
|
{t('action.retry')}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router';
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
import { CloudAccountList } from '@/components/CloudAccountList';
|
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() {
|
function HomePage() {
|
||||||
return (
|
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 (
|
||||||
|
<div className="container mx-auto max-w-4xl p-6">
|
||||||
|
<div className="rounded-lg border border-dashed p-8 text-center">
|
||||||
|
<div className="text-lg font-semibold">{t('error.generic')}</div>
|
||||||
|
<div className="text-muted-foreground mt-2 text-sm">{t('action.retry')}</div>
|
||||||
|
<Button className="mt-4" variant="outline" onClick={() => reset()}>
|
||||||
|
<RefreshCw className="mr-2 h-4 w-4" />
|
||||||
|
{t('action.retry')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const Route = createFileRoute('/')({
|
export const Route = createFileRoute('/')({
|
||||||
component: HomePage,
|
component: HomePage,
|
||||||
|
errorComponent: HomePageErrorBoundary,
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user