diff --git a/src/app/page.tsx b/src/app/page.tsx index ca5984d..94988cf 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -15,6 +15,7 @@ import { ImportProfileDialog } from "@/components/import-profile-dialog"; import { PermissionDialog } from "@/components/permission-dialog"; import { ProfilesDataTable } from "@/components/profile-data-table"; import { ProfileSelectorDialog } from "@/components/profile-selector-dialog"; +import { ProxyAssignmentDialog } from "@/components/proxy-assignment-dialog"; import { ProxyManagementDialog } from "@/components/proxy-management-dialog"; import { SettingsDialog } from "@/components/settings-dialog"; import { useAppUpdateNotifications } from "@/hooks/use-app-update-notifications"; @@ -62,7 +63,11 @@ export default function Home() { error: groupsError, } = useGroupEvents(); - const { isLoading: proxiesLoading, error: proxiesError } = useProxyEvents(); + const { + storedProxies, + isLoading: proxiesLoading, + error: proxiesError, + } = useProxyEvents(); const [createProfileDialogOpen, setCreateProfileDialogOpen] = useState(false); const [settingsDialogOpen, setSettingsDialogOpen] = useState(false); @@ -75,10 +80,15 @@ export default function Home() { useState(false); const [groupAssignmentDialogOpen, setGroupAssignmentDialogOpen] = useState(false); + const [proxyAssignmentDialogOpen, setProxyAssignmentDialogOpen] = + useState(false); const [selectedGroupId, setSelectedGroupId] = useState("default"); const [selectedProfilesForGroup, setSelectedProfilesForGroup] = useState< string[] >([]); + const [selectedProfilesForProxy, setSelectedProfilesForProxy] = useState< + string[] + >([]); const [selectedProfiles, setSelectedProfiles] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [pendingUrls, setPendingUrls] = useState([]); @@ -559,12 +569,29 @@ export default function Home() { setSelectedProfiles([]); }, [selectedProfiles, handleAssignProfilesToGroup]); + const handleAssignProfilesToProxy = useCallback((profileIds: string[]) => { + setSelectedProfilesForProxy(profileIds); + setProxyAssignmentDialogOpen(true); + }, []); + + const handleBulkProxyAssignment = useCallback(() => { + if (selectedProfiles.length === 0) return; + handleAssignProfilesToProxy(selectedProfiles); + setSelectedProfiles([]); + }, [selectedProfiles, handleAssignProfilesToProxy]); + const handleGroupAssignmentComplete = useCallback(async () => { // No need to manually reload - useProfileEvents will handle the update setGroupAssignmentDialogOpen(false); setSelectedProfilesForGroup([]); }, []); + const handleProxyAssignmentComplete = useCallback(async () => { + // No need to manually reload - useProfileEvents will handle the update + setProxyAssignmentDialogOpen(false); + setSelectedProfilesForProxy([]); + }, []); + const handleGroupManagementComplete = useCallback(async () => { // No need to manually reload - useProfileEvents will handle the update }, []); @@ -730,6 +757,7 @@ export default function Home() { onSelectedProfilesChange={setSelectedProfiles} onBulkDelete={handleBulkDelete} onBulkGroupAssignment={handleBulkGroupAssignment} + onBulkProxyAssignment={handleBulkProxyAssignment} /> @@ -832,6 +860,17 @@ export default function Home() { profiles={profiles} /> + { + setProxyAssignmentDialogOpen(false); + }} + selectedProfiles={selectedProfilesForProxy} + onAssignmentComplete={handleProxyAssignmentComplete} + profiles={profiles} + storedProxies={storedProxies} + /> + setShowBulkDeleteConfirmation(false)} diff --git a/src/components/profile-data-table.tsx b/src/components/profile-data-table.tsx index 2d44b03..48a161a 100644 --- a/src/components/profile-data-table.tsx +++ b/src/components/profile-data-table.tsx @@ -13,6 +13,7 @@ import { invoke } from "@tauri-apps/api/core"; import { emit, listen } from "@tauri-apps/api/event"; import type { Dispatch, SetStateAction } from "react"; import * as React from "react"; +import { FiWifi } from "react-icons/fi"; import { IoEllipsisHorizontal } from "react-icons/io5"; import { LuCheck, @@ -662,6 +663,7 @@ interface ProfilesDataTableProps { onSelectedProfilesChange: Dispatch>; onBulkDelete?: () => void; onBulkGroupAssignment?: () => void; + onBulkProxyAssignment?: () => void; } export function ProfilesDataTable({ @@ -678,6 +680,7 @@ export function ProfilesDataTable({ onSelectedProfilesChange, onBulkDelete, onBulkGroupAssignment, + onBulkProxyAssignment, }: ProfilesDataTableProps) { const { getTableSorting, updateSorting, isLoaded } = useTableSorting(); const [sorting, setSorting] = React.useState([]); @@ -1928,6 +1931,15 @@ export function ProfilesDataTable({ )} + {onBulkProxyAssignment && ( + + + + )} {onBulkDelete && ( void; + selectedProfiles: string[]; + onAssignmentComplete: () => void; + profiles?: BrowserProfile[]; + storedProxies?: StoredProxy[]; +} + +export function ProxyAssignmentDialog({ + isOpen, + onClose, + selectedProfiles, + onAssignmentComplete, + profiles = [], + storedProxies = [], +}: ProxyAssignmentDialogProps) { + const [selectedProxyId, setSelectedProxyId] = useState(null); + const [isAssigning, setIsAssigning] = useState(false); + const [error, setError] = useState(null); + + const handleAssign = useCallback(async () => { + setIsAssigning(true); + setError(null); + try { + // Filter out TOR browser profiles as they don't support proxies + const validProfiles = selectedProfiles.filter((profileId) => { + const profile = profiles.find((p) => p.id === profileId); + return profile && profile.browser !== "tor-browser"; + }); + + if (validProfiles.length === 0) { + setError("No valid profiles selected."); + setIsAssigning(false); + return; + } + + // Update each profile's proxy sequentially to avoid file locking issues + for (const profileId of validProfiles) { + await invoke("update_profile_proxy", { + profileId, + proxyId: selectedProxyId, + }); + } + + // Notify other parts of the app so usage counts and lists refresh + await emit("profile-updated"); + onAssignmentComplete(); + onClose(); + } catch (err) { + console.error("Failed to assign proxies to profiles:", err); + const errorMessage = + err instanceof Error + ? err.message + : "Failed to assign proxies to profiles"; + setError(errorMessage); + toast.error(errorMessage); + } finally { + setIsAssigning(false); + } + }, [ + selectedProfiles, + selectedProxyId, + profiles, + onAssignmentComplete, + onClose, + ]); + + useEffect(() => { + if (isOpen) { + setSelectedProxyId(null); + setError(null); + } + }, [isOpen]); + + return ( + + + + Assign Proxy + + Assign a proxy to {selectedProfiles.length} selected profile(s). + + + +
+
+ +
+
    + {selectedProfiles.map((profileId) => { + const profile = profiles.find( + (p: BrowserProfile) => p.id === profileId, + ); + const displayName = profile ? profile.name : profileId; + const isTorBrowser = profile?.browser === "tor-browser"; + return ( +
  • + • {displayName} + {isTorBrowser && ( + + (TOR - no proxy support) + + )} +
  • + ); + })} +
+
+
+ +
+ + +
+ + {error && ( +
+ {error} +
+ )} +
+ + + + Cancel + + void handleAssign()} + > + Assign + + +
+
+ ); +}