Files
cc-switch/src/hooks/useDragSort.ts
Jason 9370054911 fix(error-handling): isolate tray menu update failures from main operations
Previously, if updateTrayMenu() failed after a successful main operation
(like sorting, adding, or updating providers), the entire operation would
appear to fail with a misleading error message, even though the core
functionality had already succeeded.

This resulted in false negative feedback where:
- Backend data was successfully updated
- Frontend UI was successfully refreshed
- Tray menu failed to update
- User saw "operation failed" message (incorrect)

Changes:
- Wrap updateTrayMenu() calls in nested try-catch blocks
- Log tray menu failures separately with descriptive messages
- Ensure main operation success is reported accurately
- Prevent tray menu failures from triggering main operation error handlers

Files modified:
- src/hooks/useDragSort.ts (drag-and-drop sorting)
- src/lib/query/mutations.ts (add/delete/switch mutations)
- src/hooks/useProviderActions.ts (update provider)

This fixes the bug introduced in PR #179 and prevents similar issues
across all provider operations.
2025-11-08 22:07:12 +08:00

109 lines
3.1 KiB
TypeScript

import { useCallback, useMemo } from "react";
import {
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
type DragEndEvent,
} from "@dnd-kit/core";
import { arrayMove, sortableKeyboardCoordinates } from "@dnd-kit/sortable";
import { useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { useTranslation } from "react-i18next";
import type { Provider } from "@/types";
import { providersApi, type AppId } from "@/lib/api";
export function useDragSort(providers: Record<string, Provider>, appId: AppId) {
const queryClient = useQueryClient();
const { t, i18n } = useTranslation();
const sortedProviders = useMemo(() => {
const locale = i18n.language === "zh" ? "zh-CN" : "en-US";
return Object.values(providers).sort((a, b) => {
if (a.sortIndex !== undefined && b.sortIndex !== undefined) {
return a.sortIndex - b.sortIndex;
}
if (a.sortIndex !== undefined) return -1;
if (b.sortIndex !== undefined) return 1;
const timeA = a.createdAt ?? 0;
const timeB = b.createdAt ?? 0;
if (timeA && timeB && timeA !== timeB) {
return timeA - timeB;
}
return a.name.localeCompare(b.name, locale);
});
}, [providers, i18n.language]);
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: { distance: 8 },
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
);
const handleDragEnd = useCallback(
async (event: DragEndEvent) => {
const { active, over } = event;
if (!over || active.id === over.id) {
return;
}
const oldIndex = sortedProviders.findIndex(
(provider) => provider.id === active.id,
);
const newIndex = sortedProviders.findIndex(
(provider) => provider.id === over.id,
);
if (oldIndex === -1 || newIndex === -1) {
return;
}
const reordered = arrayMove(sortedProviders, oldIndex, newIndex);
const updates = reordered.map((provider, index) => ({
id: provider.id,
sortIndex: index,
}));
try {
await providersApi.updateSortOrder(updates, appId);
await queryClient.invalidateQueries({
queryKey: ["providers", appId],
});
// 更新托盘菜单以反映新的排序(失败不影响主操作)
try {
await providersApi.updateTrayMenu();
} catch (trayError) {
console.error("Failed to update tray menu after sort", trayError);
// 托盘菜单更新失败不影响排序成功
}
toast.success(
t("provider.sortUpdated", {
defaultValue: "排序已更新",
}),
);
} catch (error) {
console.error("Failed to update provider sort order", error);
toast.error(
t("provider.sortUpdateFailed", {
defaultValue: "排序更新失败",
}),
);
}
},
[sortedProviders, appId, queryClient, t],
);
return {
sortedProviders,
sensors,
handleDragEnd,
};
}