refactor: make ui reactive for group changes

This commit is contained in:
zhom
2025-08-18 17:37:22 +04:00
parent 9bf7f39c0c
commit 9f68a21824
4 changed files with 147 additions and 297 deletions
+21 -9
View File
@@ -18,9 +18,11 @@ import { ProfileSelectorDialog } from "@/components/profile-selector-dialog";
import { ProxyManagementDialog } from "@/components/proxy-management-dialog";
import { SettingsDialog } from "@/components/settings-dialog";
import { useAppUpdateNotifications } from "@/hooks/use-app-update-notifications";
import { useGroupEvents } from "@/hooks/use-group-events";
import type { PermissionType } from "@/hooks/use-permissions";
import { usePermissions } from "@/hooks/use-permissions";
import { useProfileEvents } from "@/hooks/use-profile-events";
import { useProxyEvents } from "@/hooks/use-proxy-events";
import { useUpdateNotifications } from "@/hooks/use-update-notifications";
import { useVersionUpdater } from "@/hooks/use-version-updater";
import { showErrorToast, showToast } from "@/lib/toast-utils";
@@ -49,14 +51,23 @@ export default function Home() {
// Use the new profile events hook for centralized profile management
const {
profiles,
groups,
runningProfiles,
isLoading: profilesLoading,
error: profilesError,
loadProfiles,
clearError: clearProfilesError,
} = useProfileEvents();
const {
groups: groupsData,
isLoading: groupsLoading,
error: groupsError,
} = useGroupEvents();
const {
storedProxies,
isLoading: proxiesLoading,
error: proxiesError,
} = useProxyEvents();
const [createProfileDialogOpen, setCreateProfileDialogOpen] = useState(false);
const [settingsDialogOpen, setSettingsDialogOpen] = useState(false);
const [importProfileDialogOpen, setImportProfileDialogOpen] = useState(false);
@@ -194,7 +205,7 @@ export default function Home() {
);
// Auto-update functionality - use the existing hook for compatibility
const updateNotifications = useUpdateNotifications(loadProfiles);
const updateNotifications = useUpdateNotifications();
const { checkForUpdates, isUpdating } = updateNotifications;
useAppUpdateNotifications();
@@ -260,9 +271,8 @@ export default function Home() {
useEffect(() => {
if (profilesError) {
showErrorToast(profilesError);
clearProfilesError();
}
}, [profilesError, clearProfilesError]);
}, [profilesError]);
const checkAllPermissions = useCallback(async () => {
try {
@@ -644,6 +654,9 @@ export default function Home() {
return profiles.filter((profile) => profile.group_id === selectedGroupId);
}, [profiles, selectedGroupId]);
// Update loading states
const isLoading = profilesLoading || groupsLoading || proxiesLoading;
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen gap-8 font-[family-name:var(--font-geist-sans)] bg-background">
<main className="flex flex-col row-start-2 gap-6 items-center w-full max-w-3xl">
@@ -663,8 +676,8 @@ export default function Home() {
<GroupBadges
selectedGroupId={selectedGroupId}
onGroupSelect={handleSelectGroup}
groups={groups}
isLoading={profilesLoading}
groups={groupsData}
isLoading={isLoading}
/>
<ProfilesDataTable
profiles={filteredProfiles}
@@ -717,7 +730,6 @@ export default function Home() {
onClose={() => {
setImportProfileDialogOpen(false);
}}
onImportComplete={() => void loadProfiles()}
/>
<ProxyManagementDialog
+1 -16
View File
@@ -31,13 +31,11 @@ import { RippleButton } from "./ui/ripple";
interface ImportProfileDialogProps {
isOpen: boolean;
onClose: () => void;
onImportComplete?: () => void;
}
export function ImportProfileDialog({
isOpen,
onClose,
onImportComplete,
}: ImportProfileDialogProps) {
const [detectedProfiles, setDetectedProfiles] = useState<DetectedProfile[]>(
[],
@@ -140,9 +138,6 @@ export function ImportProfileDialog({
toast.success(
`Successfully imported profile "${autoDetectProfileName.trim()}"`,
);
if (onImportComplete) {
onImportComplete();
}
onClose();
} catch (error) {
console.error("Failed to import profile:", error);
@@ -168,7 +163,6 @@ export function ImportProfileDialog({
selectedDetectedProfile,
autoDetectProfileName,
detectedProfiles,
onImportComplete,
onClose,
]);
@@ -193,9 +187,6 @@ export function ImportProfileDialog({
toast.success(
`Successfully imported profile "${manualProfileName.trim()}"`,
);
if (onImportComplete) {
onImportComplete();
}
onClose();
} catch (error) {
console.error("Failed to import profile:", error);
@@ -217,13 +208,7 @@ export function ImportProfileDialog({
} finally {
setIsImporting(false);
}
}, [
manualBrowserType,
manualProfilePath,
manualProfileName,
onImportComplete,
onClose,
]);
}, [manualBrowserType, manualProfilePath, manualProfileName, onClose]);
const handleClose = () => {
setSelectedDetectedProfile(null);
+90
View File
@@ -0,0 +1,90 @@
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { useCallback, useEffect, useState } from "react";
import type { GroupWithCount } from "@/types";
/**
* Custom hook to manage group-related state and listen for backend events.
* This hook eliminates the need for manual UI refreshes by automatically
* updating state when the backend emits group change events.
*/
export function useGroupEvents() {
const [groups, setGroups] = useState<GroupWithCount[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Load groups from backend
const loadGroups = useCallback(async () => {
try {
const groupsWithCounts = await invoke<GroupWithCount[]>(
"get_groups_with_profile_counts",
);
setGroups(groupsWithCounts);
setError(null);
} catch (err: unknown) {
console.error("Failed to load groups:", err);
setError(`Failed to load groups: ${JSON.stringify(err)}`);
}
}, []);
// Clear error state
const clearError = useCallback(() => {
setError(null);
}, []);
// Initial load and event listeners setup
useEffect(() => {
let groupsUnlisten: (() => void) | undefined;
const setupListeners = async () => {
try {
// Initial load
await loadGroups();
// Listen for group changes (create, delete, rename, update, etc.)
groupsUnlisten = await listen("groups-changed", () => {
console.log("Received groups-changed event, reloading groups");
void loadGroups();
});
// Also listen for profile changes since groups show profile counts
const profilesUnlisten = await listen("profiles-changed", () => {
console.log(
"Received profiles-changed event, reloading groups for updated counts",
);
void loadGroups();
});
// Store both listeners for cleanup
groupsUnlisten = () => {
groupsUnlisten?.();
profilesUnlisten();
};
console.log("Group event listeners set up successfully");
} catch (err) {
console.error("Failed to setup group event listeners:", err);
setError(
`Failed to setup group event listeners: ${JSON.stringify(err)}`,
);
} finally {
setIsLoading(false);
}
};
void setupListeners();
// Cleanup listeners on unmount
return () => {
if (groupsUnlisten) groupsUnlisten();
};
}, [loadGroups]);
return {
groups,
isLoading,
error,
loadGroups,
clearError,
};
}