Files
cc-switch/src/App.tsx

227 lines
7.2 KiB
TypeScript
Raw Normal View History

import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
2025-10-16 10:49:56 +08:00
import { toast } from "sonner";
import { Plus, Settings } from "lucide-react";
import type { Provider } from "@/types";
import { useProvidersQuery } from "@/lib/query";
import { providersApi, settingsApi, type AppType, type ProviderSwitchEvent } from "@/lib/api";
import { useProviderActions } from "@/hooks/useProviderActions";
2025-10-16 10:49:56 +08:00
import { extractErrorMessage } from "@/utils/errorUtils";
import { AppSwitcher } from "@/components/AppSwitcher";
import { ModeToggle } from "@/components/mode-toggle";
import { ProviderList } from "@/components/providers/ProviderList";
import { AddProviderDialog } from "@/components/providers/AddProviderDialog";
import { EditProviderDialog } from "@/components/providers/EditProviderDialog";
import { ConfirmDialog } from "@/components/ConfirmDialog";
import { SettingsDialog } from "@/components/settings/SettingsDialog";
2025-10-16 10:49:56 +08:00
import { UpdateBadge } from "@/components/UpdateBadge";
import UsageScriptModal from "@/components/UsageScriptModal";
import McpPanel from "@/components/mcp/McpPanel";
import { Button } from "@/components/ui/button";
2025-08-04 22:16:26 +08:00
function App() {
const { t } = useTranslation();
2025-10-16 10:49:56 +08:00
const [activeApp, setActiveApp] = useState<AppType>("claude");
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
2025-10-16 10:49:56 +08:00
const [isAddOpen, setIsAddOpen] = useState(false);
const [isMcpOpen, setIsMcpOpen] = useState(false);
2025-10-16 10:49:56 +08:00
const [editingProvider, setEditingProvider] = useState<Provider | null>(null);
const [usageProvider, setUsageProvider] = useState<Provider | null>(null);
const [confirmDelete, setConfirmDelete] = useState<Provider | null>(null);
2025-10-16 10:49:56 +08:00
const { data, isLoading, refetch } = useProvidersQuery(activeApp);
const providers = useMemo(() => data?.providers ?? {}, [data]);
const currentProviderId = data?.currentProviderId ?? "";
2025-08-04 22:16:26 +08:00
// 🎯 使用 useProviderActions Hook 统一管理所有 Provider 操作
const {
addProvider,
updateProvider,
switchProvider,
deleteProvider,
saveUsageScript,
} = useProviderActions(activeApp);
2025-08-04 22:16:26 +08:00
// 监听来自托盘菜单的切换事件
useEffect(() => {
2025-10-16 10:49:56 +08:00
let unsubscribe: (() => void) | undefined;
const setupListener = async () => {
try {
feat: complete stage 4 cleanup and code formatting This commit completes stage 4 of the refactoring plan, focusing on cleanup and optimization of the modernized codebase. ## Key Changes ### Code Cleanup - Remove legacy `src/lib/styles.ts` (no longer needed) - Remove old modal components (`ImportProgressModal.tsx`, `ProviderList.tsx`) - Streamline `src/lib/tauri-api.ts` from 712 lines to 17 lines (-97.6%) - Remove global `window.api` pollution - Keep only event listeners (`tauriEvents.onProviderSwitched`) - All API calls now use modular `@/lib/api/*` layer ### Type System - Clean up `src/vite-env.d.ts` (remove 156 lines of outdated types) - Remove obsolete global type declarations - All TypeScript checks pass with zero errors ### Code Formatting - Format all source files with Prettier (82 files) - Fix formatting issues in 15 files: - App.tsx and core components - MCP management components - Settings module components - Provider management components - UI components ### Documentation Updates - Update `REFACTORING_CHECKLIST.md` with stage 4 progress - Mark completed tasks in `REFACTORING_MASTER_PLAN.md` ## Impact **Code Reduction:** - Total: -1,753 lines, +384 lines (net -1,369 lines) - tauri-api.ts: 712 → 17 lines (-97.6%) - Removed styles.ts: -82 lines - Removed vite-env.d.ts declarations: -156 lines **Quality Improvements:** - ✅ Zero TypeScript errors - ✅ Zero TODO/FIXME comments - ✅ 100% Prettier compliant - ✅ Zero `window.api` references - ✅ Fully modular API layer ## Testing - [x] TypeScript compilation passes - [x] Code formatting validated - [x] No linting errors Stage 4 completion: 100% Ready for stage 5 (testing and bug fixes)
2025-10-16 12:13:51 +08:00
unsubscribe = await providersApi.onSwitched(
2025-10-16 10:49:56 +08:00
async (event: ProviderSwitchEvent) => {
if (event.appType === activeApp) {
await refetch();
}
},
);
} catch (error) {
2025-10-16 10:49:56 +08:00
console.error("[App] Failed to subscribe provider switch event", error);
}
};
setupListener();
return () => {
2025-10-16 10:49:56 +08:00
unsubscribe?.();
};
2025-10-16 10:49:56 +08:00
}, [activeApp, refetch]);
// 打开网站链接
const handleOpenWebsite = async (url: string) => {
try {
await settingsApi.openExternal(url);
} catch (error) {
const detail =
extractErrorMessage(error) ||
t("notifications.openLinkFailed", {
defaultValue: "链接打开失败",
});
toast.error(detail);
}
};
// 编辑供应商
const handleEditProvider = async (provider: Provider) => {
await updateProvider(provider);
setEditingProvider(null);
};
// 确认删除供应商
const handleConfirmDelete = async () => {
2025-10-16 10:49:56 +08:00
if (!confirmDelete) return;
await deleteProvider(confirmDelete.id);
setConfirmDelete(null);
};
2025-08-04 22:16:26 +08:00
// 导入配置成功后刷新
const handleImportSuccess = async () => {
2025-10-16 10:49:56 +08:00
await refetch();
try {
2025-10-16 10:49:56 +08:00
await providersApi.updateTrayMenu();
} catch (error) {
2025-10-16 10:49:56 +08:00
console.error("[App] Failed to refresh tray menu", error);
}
};
2025-08-04 22:16:26 +08:00
return (
2025-10-16 10:49:56 +08:00
<div className="flex h-screen flex-col bg-gray-50 dark:bg-gray-950">
<header className="flex-shrink-0 border-b border-gray-200 bg-white px-6 py-4 dark:border-gray-800 dark:bg-gray-900">
<div className="flex flex-wrap items-center justify-between gap-4">
<div className="flex items-center gap-3">
<a
href="https://github.com/farion1231/cc-switch"
target="_blank"
2025-10-16 10:49:56 +08:00
rel="noreferrer"
className="text-xl font-semibold text-blue-500 transition-colors hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300"
>
CC Switch
</a>
2025-10-16 10:49:56 +08:00
<ModeToggle />
<Button
variant="ghost"
size="icon"
onClick={() => setIsSettingsOpen(true)}
>
2025-10-16 10:49:56 +08:00
<Settings className="h-4 w-4" />
</Button>
<UpdateBadge onClick={() => setIsSettingsOpen(true)} />
</div>
2025-10-16 10:49:56 +08:00
<div className="flex flex-wrap items-center gap-3">
<AppSwitcher activeApp={activeApp} onSwitch={setActiveApp} />
style: restore original color scheme to shadcn/ui components Restore the vibrant color palette from the pre-refactoring version while maintaining shadcn/ui component architecture and modern design patterns. ## Color Scheme Restoration ### Button Component - **default variant**: Blue primary (`bg-blue-500`) - matches old `primary` - **destructive variant**: Red (`bg-red-500`) - matches old `danger` - **secondary variant**: Gray text (`text-gray-500`) - matches old `secondary` - **ghost variant**: Transparent hover (`hover:bg-gray-100`) - matches old `ghost` - **mcp variant**: Emerald green (`bg-emerald-500`) - matches old `mcp` - Updated border-radius to `rounded-lg` for consistency ### CSS Variables - Set `--primary` to blue (`hsl(217 91% 60%)` ≈ `bg-blue-500`) - Added complete shadcn/ui theme variables for light/dark modes - Maintained semantic color tokens for maintainability ### Component-Specific Colors - **"Currently Using" badge**: Green (`bg-green-500/10 text-green-500`) - **Delete button hover**: Red (`hover:text-red-500 hover:bg-red-100`) - **MCP button**: Emerald green with minimum width (`min-w-[80px]`) - **Links/URLs**: Blue (`text-blue-500`) ## Benefits - ✅ Restored original vibrant UI (blue, green, red accents) - ✅ Maintained shadcn/ui component system (accessibility, animations) - ✅ Easy global theming via CSS variables - ✅ Consistent design language across all components - ✅ Code formatted with Prettier (shadcn/ui standards) ## Files Changed - `src/index.css`: Added shadcn/ui theme variables with blue primary - `src/components/ui/button.tsx`: Restored all original button color variants - `src/components/providers/ProviderCard.tsx`: Green badge for current provider - `src/components/providers/ProviderActions.tsx`: Red hover for delete button - `src/components/mcp/McpPanel.tsx`: Use `mcp` variant for consistency - `src/App.tsx`: MCP button with emerald color and wider width The UI now matches the original colorful design while leveraging modern shadcn/ui components for better maintainability and user experience.
2025-10-16 15:32:26 +08:00
<Button
variant="mcp"
onClick={() => setIsMcpOpen(true)}
className="min-w-[80px]"
>
MCP
2025-10-16 10:49:56 +08:00
</Button>
<Button onClick={() => setIsAddOpen(true)}>
<Plus className="h-4 w-4" />
{t("header.addProvider", { defaultValue: "添加供应商" })}
</Button>
</div>
2025-08-04 22:16:26 +08:00
</div>
</header>
2025-10-16 10:49:56 +08:00
<main className="flex-1 overflow-y-auto">
<div className="mx-auto max-w-4xl px-6 py-6">
<ProviderList
providers={providers}
currentProviderId={currentProviderId}
appType={activeApp}
isLoading={isLoading}
onSwitch={switchProvider}
2025-10-16 10:49:56 +08:00
onEdit={setEditingProvider}
onDelete={setConfirmDelete}
2025-10-16 10:49:56 +08:00
onConfigureUsage={setUsageProvider}
onOpenWebsite={handleOpenWebsite}
onCreate={() => setIsAddOpen(true)}
/>
</div>
2025-08-04 22:16:26 +08:00
</main>
2025-10-16 10:49:56 +08:00
<AddProviderDialog
open={isAddOpen}
onOpenChange={setIsAddOpen}
appType={activeApp}
onSubmit={addProvider}
2025-10-16 10:49:56 +08:00
/>
<EditProviderDialog
open={Boolean(editingProvider)}
provider={editingProvider}
onOpenChange={(open) => {
if (!open) {
setEditingProvider(null);
}
}}
onSubmit={handleEditProvider}
appType={activeApp}
2025-10-16 10:49:56 +08:00
/>
2025-10-16 10:49:56 +08:00
{usageProvider && (
<UsageScriptModal
provider={usageProvider}
appType={activeApp}
refactor: migrate UsageScriptModal to shadcn/ui Dialog component Migrate the usage script configuration modal from custom modal implementation to shadcn/ui Dialog component to maintain consistent styling across the entire application. ## Changes ### UsageScriptModal.tsx - Replace custom modal structure (fixed positioning, backdrop) with Dialog component - Remove X icon import (Dialog includes built-in close button) - Add Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter imports - Add Button component import for action buttons - Update props interface to include isOpen boolean prop - Restructure component layout: - Use DialogHeader with DialogTitle for header section - Apply -mx-6 px-6 pattern for full-width scrollable content - Use DialogFooter with flex-col sm:flex-row sm:justify-between layout - Convert custom buttons to Button components: - Test/Format buttons: variant="outline" size="sm" - Cancel button: variant="ghost" size="sm" - Save button: variant="default" size="sm" - Maintain all existing functionality (preset templates, JSON editor, validation, testing, formatting) ### App.tsx - Update UsageScriptModal usage to pass isOpen prop - Use Boolean(usageProvider) to control dialog open state ## Benefits - **Consistent styling**: All dialogs now use the same shadcn/ui Dialog component - **Better accessibility**: Automatic focus management, ESC key handling, ARIA attributes - **Code maintainability**: Reduced custom modal boilerplate, easier to update styling globally - **User experience**: Unified look and feel across settings, providers, MCP, and usage script dialogs All TypeScript type checks and Prettier formatting checks pass.
2025-10-16 16:32:50 +08:00
isOpen={Boolean(usageProvider)}
2025-10-16 10:49:56 +08:00
onClose={() => setUsageProvider(null)}
onSave={(script) => {
void saveUsageScript(usageProvider, script);
2025-10-16 10:49:56 +08:00
}}
/>
)}
2025-10-16 10:49:56 +08:00
<ConfirmDialog
isOpen={Boolean(confirmDelete)}
title={t("confirm.deleteProvider", { defaultValue: "删除供应商" })}
message={
confirmDelete
? t("confirm.deleteProviderMessage", {
name: confirmDelete.name,
defaultValue: `确定删除 ${confirmDelete.name} 吗?`,
})
: ""
}
onConfirm={() => void handleConfirmDelete()}
onCancel={() => setConfirmDelete(null)}
/>
<SettingsDialog
open={isSettingsOpen}
onOpenChange={setIsSettingsOpen}
onImportSuccess={handleImportSuccess}
/>
refactor: migrate all MCP dialogs to shadcn/ui Dialog component Convert all MCP-related modal windows to use the unified shadcn/ui Dialog component for consistency with the rest of the application. Changes: - McpPanel: Replace custom modal with Dialog component - Update props from onClose to open/onOpenChange pattern - Use DialogContent, DialogHeader, DialogTitle components - Remove custom backdrop and close button (handled by Dialog) - McpFormModal: Migrate form modal to Dialog - Wrap entire form in Dialog component structure - Use DialogFooter for action buttons - Apply variant="mcp" to maintain green button styling - Remove unused X icon import - McpWizardModal: Convert wizard to Dialog - Replace custom modal structure with Dialog components - Use Button component with variant="mcp" for consistency - Remove unused isLinux and X icon imports - App.tsx: Update McpPanel usage - Remove conditional rendering wrapper - Pass open and onOpenChange props directly - dialog.tsx: Fix dialog overlay and content styling - Change overlay from bg-background/80 to bg-black/50 for consistency - Change content from bg-background to explicit bg-white dark:bg-gray-900 - Ensures opaque backgrounds matching MCP panel style Benefits: - Unified dialog behavior across the application - Consistent styling and animations - Better accessibility with Radix UI primitives - Reduced code duplication - Maintains MCP-specific green color scheme All dialogs now share the same base styling while preserving their unique content and functionality.
2025-10-16 16:20:45 +08:00
<McpPanel
open={isMcpOpen}
onOpenChange={setIsMcpOpen}
appType={activeApp}
/>
2025-08-04 22:16:26 +08:00
</div>
);
2025-08-04 22:16:26 +08:00
}
export default App;