"use client"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { useTableSorting } from "@/hooks/use-table-sorting"; import { getBrowserDisplayName, getBrowserIcon } from "@/lib/browser-utils"; import type { BrowserProfile } from "@/types"; import { type ColumnDef, type SortingState, flexRender, getCoreRowModel, getSortedRowModel, useReactTable, } from "@tanstack/react-table"; import * as React from "react"; import { CiCircleCheck } from "react-icons/ci"; import { IoEllipsisHorizontal } from "react-icons/io5"; import { LuChevronDown, LuChevronUp } from "react-icons/lu"; import { Input } from "./ui/input"; import { Label } from "./ui/label"; interface ProfilesDataTableProps { data: BrowserProfile[]; onLaunchProfile: (profile: BrowserProfile) => void | Promise; onKillProfile: (profile: BrowserProfile) => void | Promise; onProxySettings: (profile: BrowserProfile) => void; onDeleteProfile: (profile: BrowserProfile) => void | Promise; onRenameProfile: (oldName: string, newName: string) => Promise; onChangeVersion: (profile: BrowserProfile) => void; runningProfiles: Set; isUpdating?: (browser: string) => boolean; } export function ProfilesDataTable({ data, onLaunchProfile, onKillProfile, onProxySettings, onDeleteProfile, onRenameProfile, onChangeVersion, runningProfiles, isUpdating = () => false, }: ProfilesDataTableProps) { const { getTableSorting, updateSorting, isLoaded } = useTableSorting(); const [sorting, setSorting] = React.useState([]); const [profileToRename, setProfileToRename] = React.useState(null); const [newProfileName, setNewProfileName] = React.useState(""); const [renameError, setRenameError] = React.useState(null); const [isClient, setIsClient] = React.useState(false); // Ensure we're on the client side to prevent hydration mismatches React.useEffect(() => { setIsClient(true); }, []); // Update local sorting state when settings are loaded React.useEffect(() => { if (isLoaded && isClient) { setSorting(getTableSorting()); } }, [isLoaded, getTableSorting, isClient]); // Handle sorting changes const handleSortingChange = React.useCallback( (updater: React.SetStateAction) => { if (!isClient) return; const newSorting = typeof updater === "function" ? updater(sorting) : updater; setSorting(newSorting); updateSorting(newSorting); }, [sorting, updateSorting, isClient], ); const handleRename = async () => { if (!profileToRename || !newProfileName.trim()) return; try { await onRenameProfile(profileToRename.name, newProfileName.trim()); setProfileToRename(null); setNewProfileName(""); setRenameError(null); } catch (err) { setRenameError(err as string); } }; const columns: ColumnDef[] = React.useMemo( () => [ { id: "actions", cell: ({ row }) => { const profile = row.original; const isRunning = isClient && runningProfiles.has(profile.name); const isBrowserUpdating = isClient && isUpdating(profile.browser); // Check if any TOR browser profile is running const isTorBrowser = profile.browser === "tor-browser"; const anyTorRunning = isClient && data.some( (p) => p.browser === "tor-browser" && runningProfiles.has(p.name), ); const shouldDisableTorStart = isTorBrowser && !isRunning && anyTorRunning; const isDisabled = shouldDisableTorStart || isBrowserUpdating; return (
{!isClient ? "Loading..." : isRunning ? "Click to forcefully stop the browser" : isBrowserUpdating ? `${profile.browser} is being updated. Please wait for the update to complete.` : shouldDisableTorStart ? "Only one TOR browser instance can run at a time. Stop the running TOR browser first." : "Click to launch the browser"}
); }, }, { accessorKey: "name", header: ({ column }) => { const isSorted = column.getIsSorted(); return ( ); }, enableSorting: true, sortingFn: "alphanumeric", }, { accessorKey: "browser", header: ({ column }) => { const isSorted = column.getIsSorted(); return ( ); }, cell: ({ row }) => { const browser: string = row.getValue("browser"); const IconComponent = getBrowserIcon(browser); return (
{IconComponent && } {getBrowserDisplayName(browser)}
); }, enableSorting: true, sortingFn: (rowA, rowB, columnId) => { const browserA = getBrowserDisplayName(rowA.getValue(columnId)); const browserB = getBrowserDisplayName(rowB.getValue(columnId)); return browserA.localeCompare(browserB); }, }, { accessorKey: "version", header: "Version", }, { id: "status", header: ({ column }) => { const isSorted = column.getIsSorted(); return ( ); }, cell: ({ row }) => { const profile = row.original; const isRunning = isClient && runningProfiles.has(profile.name); return (
{isClient ? (isRunning ? "Running" : "Stopped") : "Loading..."} {isClient && isRunning && profile.process_id && ( PID: {profile.process_id} )}
); }, enableSorting: true, sortingFn: (rowA, rowB) => { // If not on client, sort by name only to ensure consistency if (!isClient) { return rowA.original.name.localeCompare(rowB.original.name); } const isRunningA = runningProfiles.has(rowA.original.name); const isRunningB = runningProfiles.has(rowB.original.name); // Running profiles come first, then stopped ones // Secondary sort by profile name if (isRunningA === isRunningB) { return rowA.original.name.localeCompare(rowB.original.name); } return isRunningA ? -1 : 1; }, }, { id: "proxy", header: "Proxy", cell: ({ row }) => { const profile = row.original; const hasProxy = profile.proxy?.enabled; return (
{hasProxy && ( )} {hasProxy ? profile.proxy?.proxy_type : "Disabled"}
{hasProxy ? `${profile.proxy?.proxy_type.toUpperCase()} proxy enabled (${ profile.proxy?.host }:${profile.proxy?.port})` : "No proxy configured"}
); }, }, // Update the settings column to use the confirmation dialog { id: "settings", cell: ({ row }) => { const profile = row.original; const isRunning = isClient && runningProfiles.has(profile.name); const isBrowserUpdating = isClient && isUpdating(profile.browser); return (
Actions { onProxySettings(profile); }} disabled={!isClient || isBrowserUpdating} > Configure proxy { onChangeVersion(profile); }} disabled={!isClient || isRunning || isBrowserUpdating} > Change version { setProfileToRename(profile); setNewProfileName(profile.name); }} disabled={!isClient || isRunning || isBrowserUpdating} > Rename profile void onDeleteProfile(profile)} className="text-red-600" disabled={!isClient || isRunning || isBrowserUpdating} > Delete profile
); }, }, ], [ isClient, runningProfiles, isUpdating, data, onLaunchProfile, onKillProfile, onProxySettings, onDeleteProfile, onChangeVersion, ], ); const table = useReactTable({ data, columns, state: { sorting, }, onSortingChange: handleSortingChange, getSortedRowModel: getSortedRowModel(), getCoreRowModel: getCoreRowModel(), }); return ( <>
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { return ( {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext(), )} ); })} ))} {table.getRowModel().rows.length ? ( table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( {flexRender( cell.column.columnDef.cell, cell.getContext(), )} ))} )) ) : ( No profiles found. )}
{ if (!open) { setProfileToRename(null); setNewProfileName(""); setRenameError(null); } }} > Rename Profile
{ setNewProfileName(e.target.value); }} className="col-span-3" />
{renameError && (

{renameError}

)}
); }